import Dropdown from '@cronocode/react-box/components/dropdown';<Dropdown label="Choice" defaultValue={1}>
<Dropdown.Item value={1}>Option 1</Dropdown.Item>
<Dropdown.Item value={2}>Option 2</Dropdown.Item>
</Dropdown>role="combobox" that keeps DOM focus the entire time; the popup is a role="listbox" of role="option" rows, and the option the arrows are on is named by aria-activedescendant rather than focused. Add isSearchable and the combobox becomes the text field instead of a button. You write none of that — and if you were writing it by hand before, delete it.label and the component renders it and wires aria-labelledby — or pass your own aria-label in props. Without either, the control has no accessible name, exactly like an input with only a placeholder.Dropdown.Unselect and Dropdown.SelectAll sit inside the listbox, so they carry role="option" and the arrow keys reach them like any other row.isSearchable the text field is the combobox — the role, the ARIA and anything you pass in props live on the input, so nothing focusable sits inside anything else focusable. It is a different keyboard map, in the second table below: the printable keys type instead of navigating, and only Down and Up reach the listbox.| Key | What happens |
|---|---|
Down / Up (closed) | Opens, with the highlight on the selected option — or the first / last when nothing is chosen. |
Alt + Down (closed) | Opens without moving the highlight. |
Enter / Space (closed) | Opens. The browser own activation is suppressed, so it does not shut again. |
Home / End (closed) | Opens at the first or the last option. |
A printable character | Opens with the first option starting with it already highlighted. |
Down / Up (open) | Moves the highlight, wrapping at the ends and skipping disabled options. |
Home / End (open) | Jumps to the first or last option. |
Typing (open) | Typeahead. A longer buffer narrows; the same letter twice cycles through the options sharing it. |
Enter / Space (open) | Chooses the highlighted option and closes. In multiple mode it toggles and stays open. |
Alt + Up (open) | Chooses the highlighted option and closes. |
Escape | Closes, changing nothing. Focus never left the trigger, so nothing has to be restored. |
Tab (open) | Chooses the highlighted option, then moves on to the next control. |
| Key | What happens |
|---|---|
A printable character | Types into the field, which opens the listbox and filters it. No typeahead — the field owns the keys. |
Down / Up (closed) | Opens, with the highlight on the selected option — or the first / last when nothing is chosen. |
Alt + Down (closed) | Opens without highlighting anything. |
Down / Up (open) | Moves the highlight through what the filter left, wrapping and skipping disabled options. |
Home / End, Left / Right | Move the caret, and hand the highlight back to the field — no option is where you are any more. |
Space | Types a space. Only Enter chooses in this mode. |
Enter (open) | Chooses the highlighted option and puts its text in the field. With nothing highlighted it does nothing. |
Escape | Closes the listbox, keeping what was typed. Pressed again on a closed one, it clears the field. |
Tab (open) | Chooses the highlighted option, then moves on to the next control. |
Clicking away | Closes, and the field goes back to the value — a query left behind would describe a filter that is gone. |
const [selectedValue, setSelectedValue] = useState<number>(2);
<Dropdown value={selectedValue} onChange={(value) => setSelectedValue(value!)}>
<Dropdown.Item value={1}>Option 1</Dropdown.Item>
<Dropdown.Item value={2}>Option 2</Dropdown.Item>
<Dropdown.Item value={3}>Option 3</Dropdown.Item>
</Dropdown><Dropdown>
<Dropdown.Unselect>Select</Dropdown.Unselect>
<Dropdown.Item value={1}>Option 1</Dropdown.Item>
<Dropdown.Item value={2}>Option 2</Dropdown.Item>
</Dropdown><Dropdown disabled width={50}>
<DropdownUnselect>Select</DropdownUnselect>
<DropdownItem value={1}>Option 1</DropdownItem>
<DropdownItem value={2}>Option 2</DropdownItem>
</Dropdown><Dropdown variant="compact" width={40}>
<DropdownUnselect>Select</DropdownUnselect>
<DropdownItem value={1}>Option 1</DropdownItem>
<DropdownItem value={2}>Option 2</DropdownItem>
</Dropdown>// boxExtends.ts — register the variant, and export what you registered.
export const components = Box.components({
dropdown: {
variants: {
outlined: {
bgColor: 'transparent', b: 2, borderColor: 'indigo-500', color: 'indigo-600',
},
},
children: {
item: { variants: { outlined: { hover: { bgColor: 'indigo-50' }, selected: { bgColor: 'indigo-100' } } } },
items: { variants: { outlined: { b: 2, borderColor: 'indigo-500' } } },
},
},
});
// box.d.ts — teach TypeScript the name, or variant="outlined" stays a type error.
import { ExtractComponentsAndVariants } from '@cronocode/react-box/types';
import { components } from './boxExtends';
declare module '@cronocode/react-box/types' {
namespace Augmented {
interface ComponentsTypes extends ExtractComponentsAndVariants<typeof components> {}
}
}
// Use — the variant propagates to all children automatically.
<Dropdown variant="outlined">
<Dropdown.Item value={1}>Option 1</Dropdown.Item>
<Dropdown.Item value={2}>Option 2</Dropdown.Item>
</Dropdown><Dropdown
label="User"
isSearchable
searchPlaceholder="Search users..."
width={50}
>
<DropdownUnselect>Select</DropdownUnselect>
<DropdownItem value={1}>John Doe</DropdownItem>
<DropdownItem value={2}>Joe Smith</DropdownItem>
<DropdownItem value={3}>Alice</DropdownItem>
<DropdownItem value={4}>Bob</DropdownItem>
</Dropdown><Dropdown
label="User"
isSearchable
searchPlaceholder="Search users..."
width={50}
>
<DropdownEmptyItem>No options</DropdownEmptyItem>
<DropdownUnselect>Select</DropdownUnselect>
<DropdownItem value={1}>John Doe</DropdownItem>
<DropdownItem value={2}>Joe Smith</DropdownItem>
<DropdownItem value={3}>Alice</DropdownItem>
<DropdownItem value={4}>Bob</DropdownItem>
</Dropdown><Dropdown multiple width={50}>
<DropdownEmptyItem>No options</DropdownEmptyItem>
<DropdownUnselect>Unselect All</DropdownUnselect>
<DropdownSelectAll>Select All</DropdownSelectAll>
<DropdownItem value={1}>John Doe</DropdownItem>
<DropdownItem value={2}>Joe Smith</DropdownItem>
<DropdownItem value={3}>Alice</DropdownItem>
<DropdownItem value={4}>Bob</DropdownItem>
</Dropdown><Dropdown multiple showCheckbox width={50}>
<DropdownEmptyItem>No options</DropdownEmptyItem>
<DropdownUnselect>Unselect All</DropdownUnselect>
<DropdownSelectAll>Select All</DropdownSelectAll>
<DropdownItem value={1}>John Doe</DropdownItem>
<DropdownItem value={2}>Joe Smith</DropdownItem>
<DropdownItem value={3}>Alice</DropdownItem>
<DropdownItem value={4}>Bob</DropdownItem>
</Dropdown><Dropdown multiple showCheckbox width={50}>
<DropdownDisplay />
<DropdownEmptyItem>No options</DropdownEmptyItem>
<DropdownUnselect>Unselect All</DropdownUnselect>
<DropdownSelectAll>Select All</DropdownSelectAll>
<DropdownItem value={1}>John Doe</DropdownItem>
<DropdownItem value={2}>Joe Smith</DropdownItem>
<DropdownItem value={3}>Alice</DropdownItem>
<DropdownItem value={4}>Bob</DropdownItem>
</Dropdown>import Select from '@cronocode/react-box/components/select';const users = [
{ id: 1, name: 'John Doe', role: 'Admin' },
{ id: 2, name: 'Joe Smith', role: 'Editor' },
{ id: 3, name: 'Alice Brown', role: 'Viewer' },
];
<Select
label="User"
data={users}
def={{ valueKey: 'id', displayKey: 'name', placeholder: 'Pick a user...' }}
width={50}
/><Select
data={users}
def={{
valueKey: 'id',
display: (user) => `${user.name} — ${user.role}`,
placeholder: 'Pick a user...',
}}
/><Select
data={users}
def={{
valueKey: 'id',
displayKey: 'name',
placeholder: 'Pick users...',
selectAllText: 'Select all',
emptyText: 'No users found',
selectedDisplay: (rows) =>
rows.length === 0 ? 'Pick users...' : `${rows.length} selected`,
}}
label="Users" multiple showCheckbox isSearchable searchPlaceholder="Search users..."
/>