Skip to content
design
Foundations

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.

The people this report is written for.
A composed field and its outlined recipe share the same labeling and validation semantics.
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>
  );
}
01Name the behaviorA navigation bar changes destinations. Tabs reveal panels. A combobox selects an item. Autocomplete accepts text.
02Keep the primitive contractBase UI part names, state props, event details and render composition carry through styled primitives.
03Assemble without duplicatingRecipes arrange primitive parts and supply convenient content props. They do not implement a second keyboard or focus model.

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

NameWhat it is
NauticalChartA nautical SVG chart with vessel overlays
NavigationBarLinks to application destinations, using aria-current
TabsA tablist and its associated panels
CommandPaletteA searchable dialog of commands and destinations
TaskDialogAn assembled focused task
DialogComposable dialog primitives
RadioCardsMutually exclusive choices drawn as cards
SegmentedRadioGroupMutually exclusive choices drawn as segments
Autocomplete / AsyncAutocompleteText suggestions; arbitrary text is valid
Combobox / AsyncComboboxSelection of a known item, with a separate query
MenuComposable commands and choices
ActionMenuA menu assembled from entries
FieldShared label, description and validation semantics
FormFieldAn 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

StateControlled propInitial valueCallback
Text or selectionvaluedefaultValueonValueChange(value, details)
Checkbox or switchcheckeddefaultCheckedonCheckedChange(checked, details)
Overlay visibilityopendefaultOpenonOpenChange(open, details)
Finished slider/splitter adjustmentvalueAs supported by the controlonValueCommitted(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

Compose a menu with a persistent option. Export previews the choice without downloading a file.
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 Action prop returns a Promise; the component shows its pending state, and never shows a safety-relevant result before bosund confirms it.
  • ref is an ordinary prop, with no forwardRef.
  • testId sets data-testid on the root; a part is ${testId}--<part>. Tests find elements by role and name first.
  • State the CSS reacts to is a data-* attribute, and className is added to the component's own classes.
  • motion is a boolean on overlays and a motion value on display components.
Do
Use NavigationBar, Tabs, Autocomplete and Combobox respectively.
Choose names from the actual interaction: destination navigation, panel tabs, text suggestions or item selection.
Don't
Separate control naming, selected data and query text.
Do not add a boolean that changes what value means or reuse label for selected display text.
  • 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.