Component API
Names describe behavior, primitives follow Base UI, and recipes compose those primitives without changing their meaning.
On this page
Use a short, specific title.
Code
import { Field } from "@bosunkit/components/field";
import { Input } from "@bosunkit/components/input";
import { FormField } from "@bosunkit/components/form-field";
import type { ReactElement } from "react";
export const description: string =
"Primitive parts and the outlined recipe connect labels and instructions to their controls through the same field semantics.";
export default function Showcase(): ReactElement {
return (
<div style={{ display: "grid", gap: "var(--space-xl)" }}>
<Field.Root>
<Field.Label>Report title</Field.Label>
<Input defaultValue="Weekly delivery" />
<Field.Description>Use a short, specific title.</Field.Description>
</Field.Root>
<FormField label="Audience" appearance="outlined" hint="The people this report is written for." required>
<Input defaultValue="Engineering team" />
</FormField>
</div>
);
}
Choose a primitive or a recipe
A primitive family exposes named parts: Dialog.Root, Dialog.Trigger, Dialog.Popup, Dialog.Title and
Dialog.Close. Compose these when the content or arrangement varies. Parts accept Base UI's render prop,
ordinary React refs, DOM attributes and state-dependent class names.
A recipe assembles these parts for a specific task. TaskDialog supplies a heading, description, scrolling
body, footer, actions and dismissal policy. ActionMenu accepts an entries array; CreateButton arranges creation
choices as tiles. SplitButton and NavUser compose ActionMenu.
Tabs, Checkbox and Switch are also callable as recipes that take an options array; custom composition uses their
named parts. Select is its named parts only, starting from Select.Root. RadioGroup and CheckboxGroup are
standalone groups, as in Base UI; passing children supports custom layouts without requiring an options array.
Names by behavior
| Name | What it is |
|---|---|
NauticalChart | A nautical SVG chart with vessel overlays |
NavigationBar | Links to application destinations, using aria-current |
Tabs | A tablist and its associated panels |
CommandPalette | A searchable dialog of commands and destinations |
TaskDialog | An assembled focused task |
Dialog | Composable dialog primitives |
RadioCards | Mutually exclusive choices drawn as cards |
SegmentedRadioGroup | Mutually exclusive choices drawn as segments |
Autocomplete / AsyncAutocomplete | Text suggestions; arbitrary text is valid |
Combobox / AsyncCombobox | Selection of a known item, with a separate query |
Menu | Composable commands and choices |
ActionMenu | A menu assembled from entries |
Field | Shared label, description and validation semantics |
FormField | An assembled field; appearance="outlined" places the name on the outline |
NavList is grouped destination navigation, not Base UI's popup NavigationMenu. NumberInput is a native
numeric input; its change value is text, unlike Base UI's numeric NumberField. OneTimeCodeInput is one input with
paste normalization, not a multi-slot OTPField. Report, maritime and instrument names describe domain behavior;
they are not renamed to unrelated primitives.
State and callbacks
| State | Controlled prop | Initial value | Callback |
|---|---|---|---|
| Text or selection | value | defaultValue | onValueChange(value, details) |
| Checkbox or switch | checked | defaultChecked | onCheckedChange(checked, details) |
| Overlay visibility | open | defaultOpen | onOpenChange(open, details) |
| Finished slider/splitter adjustment | value | As supported by the control | onValueCommitted(value) |
| Work the component waits for | — | — | <verb>Action(…) => Promise (acknowledgeAction, saveAction); the component shows its pending state until it settles |
The second argument is the original Base UI event detail object wherever a control uses that contract.
details.reason explains the change; details.cancel() rejects it. Controlled values stay with the caller.
Native input onChange(event) is also available; do not treat it as a string callback.
AsyncCombobox.value is a selected { value, label } item or null; inputValue is only the search query.
A visible label or aria-label names the input. Selected display text never doubles as an accessible label.
For tabs, Tabs.List.activateOnFocus controls whether arrow keys select as well as move focus. The primitive
follows Base UI's manual default. The options-based Tabs recipe defaults to automatic activation for immediate
panels. Set the behavior explicitly where panel loading changes the choice.
Composition
Code
import { Menu } from "@bosunkit/components/menu";
import { Button } from "@bosunkit/components/button";
import { Stack } from "@bosunkit/components/stack";
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"Compose a menu with a persistent option. Export previews the choice without downloading a file.";
export default function Showcase(): ReactElement {
const [notes, setNotes]: [boolean, Dispatch<SetStateAction<boolean>>] = useState(true);
const [status, setStatus]: [string, Dispatch<SetStateAction<string>>] = useState("");
return (
<Stack space="sm">
<Menu.Root>
<Menu.Trigger render={<Button>Report actions</Button>} />
<Menu.Portal keepMounted>
<Menu.Positioner sideOffset={8}>
<Menu.Popup aria-label="Report actions">
<Menu.Item
onClick={(): void => {
setStatus(notes ? "Export preview includes notes." : "Export preview excludes notes.");
}}
>
Preview export
</Menu.Item>
<Menu.CheckboxItem checked={notes} onCheckedChange={setNotes} closeOnClick={false}>
Include notes<Menu.CheckboxItemIndicator> ✓</Menu.CheckboxItemIndicator>
</Menu.CheckboxItem>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>
<p role="status">{status}</p>
</Stack>
);
}
Use render={<Button />} to give a primitive the kit's button appearance. The render target must forward its
ref and spread all received DOM props. This preserves keyboard interaction, accessible naming and merged handlers.
Do not nest a button inside another button.
Select.Label names the trigger. Select.GroupLabel names a group of choices. Place Select.Popup inside
Select.Positioner and Select.Portal; Select.List holds the items. Supply items on Select.Root when the
selected label must render before the popup mounts, including during server rendering.
Dialog.Popup.initialFocus and finalFocus accept Base UI's focus targets. TaskDialog passes these through,
keeps its close policies, and prevents an alert-zone interaction from discarding a task. Menus and dialogs copy
presentation scope onto portaled surfaces and use the shared layer tokens.
Field.Root, Field.Label, Field.Description and Field.Error share their relationships with Base UI controls.
FormField and OutlinedField use this same field root; their control context connects the kit's own inputs to the field's label, description and error.
Use the outlined appearance for visual treatment, not a separate validation model.
Prop conventions
- Display text in, translated by the caller. A component translates only its own built-in words.
- An
Actionprop returns a Promise; the component shows its pending state, and never shows a safety-relevant result before bosund confirms it. refis an ordinary prop, with noforwardRef.testIdsetsdata-testidon the root; a part is${testId}--<part>. Tests find elements by role and name first.- State the CSS reacts to is a
data-*attribute, andclassNameis added to the component's own classes. motionis a boolean on overlays and a motion value on display components.
Related
- Field: composable labels, descriptions and validation.
- Dialog: primitive parts and focus management.
- Menu: composable command and choice menus.
- Base UI composition: render targets, refs and event handling.