Dropdown, select and command menu
Dropdown menu, Combobox and Command menu show a list of actions or options in a floating panel. Use Dropdown menu for actions on the current item or view, Combobox to choose one value with search, and Command menu for app-wide ⌘K search.
Beta until design supplies 3:1 boundaries for the toggle off track and the empty checkbox frame (C20, FC-06), confirms the 4px container gap (C16) and designs an item focus state (C10). Item sizes, selected styling, text colours and nested controls are resolved (C17–C19, C21).
Preview
All three components share one item anatomy, taken from Figma “Dropdown Items”. Configure an item, then open the menu to try it. The code updates with the controls.
import { DropdownMenu, DropdownMenuContent, DropdownMenuLabel, DropdownMenuTrigger, DropdownMenuItem, Button } from '@nuton/ui'
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" appearance="stroke" trailingIcon={<RiArrowDownSLine />}>Lesson actions</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>Photosynthesis</DropdownMenuLabel>
<DropdownMenuItem
leading={<RiShareLine />}
onSelect={copyLink}
>
Copy share link
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>The shortcut is display text only. Bind the key yourself; the Item options menu below shows a working one.
Installation
Availability
@nuton/ui and @nuton/tokens are private workspace packages. They aren't published to a registry yet (D4: registry deferred; D12: distribution waits on the provenance review). Inside the Nuton monorepo, add them as workspace dependencies. The commands below apply once the packages are published.
1. Add the packages
pnpm add @nuton/ui @nuton/tokens @remixicon/reactnpm install @nuton/ui @nuton/tokens @remixicon/reactyarn add @nuton/ui @nuton/tokens @remixicon/reactPeer dependencies: react and react-dom 18 or later. @nuton/ui brings its own primitives: Radix Dropdown Menu, Popover and Dialog (D5) and cmdk. No provider is needed.
2. Load tokens and component styles once
// app/layout.tsx (or your app's entry file)
import '@nuton/tokens/tokens.css' // --nu-* design tokens
import '@nuton/ui/styles.css' // component styles (uses the tokens)Menus, the Combobox list and the Command menu render in a portal at the end of document.body, so a parent with overflow: hidden doesn't clip them. They stack with --nu-z-popover and --nu-z-modal.
Usage
Dropdown menu
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@nuton/ui'
import { RiEditLine, RiMore2Line } from '@remixicon/react'
export function LessonMenu({ title, onRename }: { title: string; onRename: () => void }) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" appearance="ghost" iconOnly aria-label={`Options for ${title}`} leadingIcon={<RiMore2Line />} />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem leading={<RiEditLine />} onSelect={onRename}>Rename</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}Combobox
import { Combobox, Field } from '@nuton/ui'
<Field label="Reviewer">
<Combobox options={students} value={reviewer} onValueChange={setReviewer} placeholder="Select a student" searchPlaceholder="Search…" />
</Field>Command menu
import { CommandMenu, CommandMenuEmpty, CommandMenuGroup, CommandMenuItem } from '@nuton/ui'
<CommandMenu open={open} onOpenChange={setOpen} label="Search Nuton" placeholder="Search lessons, spaces and commands…">
<CommandMenuEmpty>No results</CommandMenuEmpty>
<CommandMenuGroup heading="Lessons">
<CommandMenuItem onSelect={() => openLesson('photosynthesis')}>Photosynthesis</CommandMenuItem>
</CommandMenuGroup>
</CommandMenu>- Use Dropdown menu for actions and view options; use Combobox when the result is a value in a form.
- Open a dropdown from a
ButtonwithDropdownMenuTrigger asChild. An icon-only trigger needs anaria-labelthat names its object: “Options for Photosynthesis”. - Put Combobox inside a
Field. The trigger is named by the field label and its current value. - Every visible and accessible string is passed in: placeholders,
emptyText,searchLabeland footer hints. Pass translated strings. - Item labels are sentence case and start with a verb when they act: “Copy share link”, “Move to space”.
Variants and sizes
Figma's item types are not separate components: the leading visual (icon, flag, avatar or logo) goes in leading, and the row size comes from size.
Item types
Every Figma item type in both sizes, as real menus. Choose an option to select it; “Unavailable” is disabled. At Large, the leading visual sits in a 40px circular frame and a description shows on a second line.
// One menu per type: Basic, Country, Avatar, Provider, Brand, Company
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" appearance="stroke" trailingIcon={<RiArrowDownSLine />}>Basic: {value}</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuRadioGroup value={value} onValueChange={setValue}>
<DropdownMenuRadioItem value="English" size="sm" leading={<RiGlobalLine />}>English</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="French" size="sm" leading={<RiGlobalLine />}>French</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="Spanish" size="sm" leading={<RiGlobalLine />}>Spanish</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
<DropdownMenuItem size="sm" leading={<RiGlobalLine />} disabled>Unavailable</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>// Avatar type at Large: a 40px avatar and a description line
<DropdownMenuRadioItem
value="Sophia Williams"
size="lg"
leading={<Avatar name="Sophia Williams" color="blue" size={40} />}
description="Avatar option"
>
Sophia Williams
</DropdownMenuRadioItem>Item options
One real menu with every Figma item option: checkboxes (with a tri-state “All topics”), toggles, sublabel, badge, description, a working shortcut (⌥N toggles notifications even when the menu is closed), a submenu and disabled items.
<DropdownMenuCheckboxItem checked={all} onCheckedChange={toggleAll} onSelect={(e) => e.preventDefault()}>
All topics
</DropdownMenuCheckboxItem>
<DropdownMenuSwitchItem leading={<RiBellLine />} shortcut="⌥N" checked={notify} onCheckedChange={setNotify}>
Notifications
</DropdownMenuSwitchItem>
<DropdownMenuItem leading={<RiUser3Line />} sublabel="(@sophia)">Sublabel</DropdownMenuItem>
<DropdownMenuItem leading={<RiShareLine />} badge={<span className="status">New</span>}>Badge</DropdownMenuItem>
<DropdownMenuItem leading={<RiBookOpenLine />} size="lg" description="Shown under the label at Large size">Description</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger leading={<RiFolderLine />}>Right icon (submenu)</DropdownMenuSubTrigger>
<DropdownMenuSubContent>…</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuCheckboxItem leading={<RiGlobalLine />} checked disabled>Disabled with checkbox</DropdownMenuCheckboxItem>Gap: the Figma “Button” option (C21)
Figma allows a link or button inside an item. Interactive controls nested inside a menu item break keyboard and screen-reader behaviour, so the API doesn't support it: trailing is decorative and hidden from assistive technology, and development builds warn if it contains a button, a link or an onClick. Use a separate item, or a submenu for grouped actions. Figma correction to review: FC-07.
Sizes
| Item | Figma label | Height | Padding | Gap | Radius | Leading |
|---|---|---|---|---|---|---|
Dropdown item, sm | 36 | 40 | 8 | 8 | 8 | 20 |
Dropdown item, lg | 56 | 56 (matches) | 8 | 12 | 10 | 40 frame |
Command item, sm | 48 | 44 | 10 / 12 | 12 | 10 | 20 |
Command item, md | 64 | 64 (matches) | 12 | 12 | 10 | 40 frame |
Code uses the geometry measured in Figma. Where a Figma variant label disagrees with its own frame, the measured value wins and the label is flagged for correction in Figma (C17; FC-02, FC-03). Figma itself has not been changed. Combobox options use the dropdown item sizes through optionSize.
| Container | Values |
|---|---|
| Dropdown menu and Combobox list | Min width 280 (Combobox: the trigger's width), padding 8 including the 1px Stroke/Soft-200 border, gap 4, radius 16, regular-shadow/medium. Opens 10px from the trigger; submenus 8px. |
| Combobox | Trigger is a Text box: lg 48, sm 36. List scrolls after 320. |
| Command menu | Width 600 (narrower on small screens), search row 52, footer 52, radius 16. Opens 15% from the top; at most 70% of the viewport tall. |
| Kbd | Padding 2 / 6 including the 1px border, radius 4. Muted: Weak-50, at least 20 × 20. |
Container gap: the DS documentation screenshot says 8px; all three composed menus on product screens use 4px, which code follows. Awaiting confirmation (C16).
Combobox trigger and option sizes
<Field label="Student (large trigger)">
<Combobox options={students} placeholder="Select a student" searchPlaceholder="Search…" />
</Field>
<Field label="Student (small trigger)">
<Combobox size="sm" options={students} placeholder="Select a student" searchPlaceholder="Search…" />
</Field>Keyboard shortcut chip
<Kbd>⌘J</Kbd> // item and search-row shortcuts
<Kbd variant="muted">esc</Kbd> // Command menu footer hintsStates and interactions
Menu items have no state props: Radix and cmdk set the state while people use the menu. Open each menu to see it. There are no component tokens to show these states statically.
Item states
Combobox states
Choose a reviewer.
Field error or invalid. The trigger uses the Text box error style and the message is linked.Only owners can change this.
State tokens
| State | Hook | Style |
|---|---|---|
| Highlighted | [data-highlighted] (Radix), [data-selected="true"] (cmdk) | Bg/Weak-50 |
| Selected | .nu-menu-item--radio[data-state="checked"], [data-checked] (Combobox) | Bg/Soft-200 + check mark Icon/Strong-950 |
| Selected and highlighted | Both of the above | Bg/Soft-200 + 1px inset Stroke/Sub-300 ring (C18) |
| Disabled | [data-disabled] | Text/Disabled-500; icons and secondary text Icon/Disabled-300 at 60% opacity |
| Checkbox | .nu-checkbox-indicator[data-state] | Checked and indeterminate: State/Primary/Base. Unchecked: Bg/Soft-200 frame (C20) |
| Toggle | .nu-switch-indicator[data-state] | On: --nu-switch-on (State/Primary/Base); highlighted --nu-switch-on-hover (blue/700). Off: Bg/Soft-200; highlighted Bg/Sub-300 |
Composition examples
Lesson list with overflow menus
The View menu filters and sorts the list; each lesson's menu renames, duplicates, shares, moves, completes or deletes it. After Rename or Delete, focus moves to the lesson's menu button or the next one.
Invite to a space
Combobox in forms: a search field, group headings (“Admins”), a small trigger with large options, disabled, and a required choice validated when the list closes.
Search lessons, spaces and people
Press ⌘J / Ctrl J, or use the button. On this site ⌘K opens the documentation search, which is also a CommandMenu, so the demo uses ⌘J. CommandMenu doesn't bind a key itself; shortcut only shows it.
The footer link sits outside the item list, so it doesn't break C21. Items themselves never contain links.
API reference
Parts
Dropdown menu wraps Radix Dropdown Menu; Combobox and Command menu are built on cmdkinside a Radix Popover and Dialog. Parts not listed in the props tables below take their primitive's props unchanged.
| Part | Primitive | Nuton changes |
|---|---|---|
DropdownMenu, DropdownMenuTrigger, DropdownMenuGroup, DropdownMenuRadioGroup, DropdownMenuSub | Radix Root, Trigger, Group, RadioGroup, Sub | Re-exported unchanged |
DropdownMenuContent | Radix Content in a Portal | Defaults sideOffset=10, align="start", collisionPadding=8 |
DropdownMenuSubContent | Radix SubContent in a Portal | Defaults sideOffset=8, collisionPadding=8 |
DropdownMenuItem | Radix Item | Item props below |
DropdownMenuCheckboxItem | Radix CheckboxItem | Item props; checked (boolean | 'indeterminate', default false) draws a checkbox on the left |
DropdownMenuSwitchItem | Radix CheckboxItem | Item props; checked (boolean, default false) draws a toggle on the right; selecting never closes the menu |
DropdownMenuRadioItem | Radix RadioItem | Item props; value required; check mark when selected (C18) |
DropdownMenuSubTrigger | Radix SubTrigger | Item props without trailing; always shows a right chevron |
DropdownMenuLabel, DropdownMenuSeparator | Radix Label, Separator | Styles only (Figma group heading and “Content Divider”) |
CommandMenuGroup, CommandMenuEmpty, CommandMenuSeparator | cmdk Group, Empty, Separator | Styles only |
MenuItemContent, CheckboxIndicator, SwitchIndicator | None | The shared item anatomy and the decorative indicators, exported for custom menus. Indicators are aria-hidden. |
Item props (Dropdown menu items)
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | — | The label. Truncates with an ellipsis. |
size | 'sm' | 'lg' | 'sm' | Row height 40 / 56px. lg frames the leading visual at 40px. |
tone | 'default' | 'destructive' | 'default' | destructive sets the label and icon in the error colour, for Delete and Logout (Figma Dropdown Items). |
leading | ReactNode | — | Icon, flag, avatar or logo before the label (Figma Type). Rendered aria-hidden. |
sublabel | ReactNode | — | Secondary text after the label, e.g. “(@sophia)”. Text/Sub-600 (C19). |
description | ReactNode | — | Second line under the label. Use with size="lg". |
badge | ReactNode | — | A badge element after the content, e.g. “New”. |
shortcut | ReactNode | — | Shortcut hint; a string renders in a Kbd. Display only: bind the key yourself. |
trailing | ReactNode | — | Decorative trailing visual, rendered aria-hidden. Never a button or link (C21; development warning). Not on SubTrigger. |
className | string | — | Added to .nu-menu-item. |
ref | Ref<HTMLDivElement> | — | Forwarded to the Radix item. |
…rest | Radix item props | — | Everything else from the matching Radix part: onSelect, disabled, textValue; checked / onCheckedChange on checkbox and switch items; value on radio items. |
Combobox props
| Prop | Type | Default | Description |
|---|---|---|---|
optionsrequired | ComboboxOption[] | — | The list. See ComboboxOption below. |
value | string | null | — | Controlled value. Omit for uncontrolled. |
defaultValue | string | null | null | Initial value when uncontrolled. |
onValueChange | (value: string) => void | — | Called when an option is chosen. The list then closes. |
onOpenChange | (open: boolean) => void | — | Called when the list opens or closes, e.g. to validate a required choice. |
placeholder | string | — | Trigger text when nothing is selected. |
searchable | boolean | true | Shows the search field. Set false for short lists. |
searchPlaceholder | string | — | Placeholder of the search field. |
searchLabel | string | 'Search options' | Accessible name of the search field. Pass a translated string. |
emptyText | ReactNode | 'No results' | Shown when the search matches nothing. Pass a translated string. |
size | 'sm' | 'lg' | 'lg' | Trigger height 36 / 48px (Text box sizes). |
optionSize | 'sm' | 'lg' | 'sm' | Option row height 40 / 56px. |
invalid | boolean | — | Error style. Inherited from Field error when omitted. |
disabled | boolean | — | Disables the trigger. Inherited from Field when omitted. |
required | boolean | — | Sets required on the hidden input (needs name). Inherited from Field. |
id | string | — | Trigger id. Defaults to the Field control id. |
name | string | — | Renders a hidden input with the value, so the choice submits with a form. |
className | string | — | Added to the trigger. |
aria-label | string | — | Names the trigger and list when there is no Field label. |
aria-describedby | string | — | Extra description ids, merged with the Field hint and error. |
ref | Ref<HTMLButtonElement> | — | Forwarded to the trigger button. |
| Prop | Type | Default | Description |
|---|---|---|---|
valuerequired | string | — | Unique value passed to onValueChange. |
labelrequired | string | — | Visible label; also searched and shown in the trigger once selected. |
sublabel | ReactNode | — | Secondary text, e.g. “@sophia”. |
description | ReactNode | — | Second line; use with optionSize="lg". |
leading | ReactNode | — | Icon, flag or avatar. Also shown in the trigger when selected. |
badge | ReactNode | — | A badge element. |
disabled | boolean | — | Option can’t be chosen. |
group | string | — | Group heading, e.g. “Admins”. Groups appear in order of first use. |
keywords | string[] | — | Extra search terms, such as a handle. |
Command menu props
| Prop | Type | Default | Description |
|---|---|---|---|
labelrequired | string | — | Accessible name of the dialog and the search field, e.g. “Search Nuton”. |
childrenrequired | ReactNode | — | Groups, items and CommandMenuEmpty. |
open | boolean | — | Controlled open state. |
defaultOpen | boolean | — | Initial open state when uncontrolled. |
onOpenChange | (open: boolean) => void | — | Called when Escape, an outside click or your code opens or closes it. |
placeholder | string | — | Search field placeholder. |
shortcut | ReactNode | — | Shortcut chip in the search row; a string renders in a Kbd. Display only. |
footer | ReactNode | — | Footer content, usually CommandMenuFooter. |
search | string | — | Controlled search text. |
onSearchChange | (search: string) => void | — | Called as the search text changes. |
shouldFilter | boolean | — | Set false when your app filters the results, e.g. a server search. |
container | HTMLElement | null | — | Portal container. Defaults to document.body. Pass an element to keep the palette inside a frame or preview. |
modal | boolean | true | Set false for a palette shown inside a preview that must not lock the page. It then draws no overlay, so the host draws its own. |
className | string | — | Added to the dialog (.nu-command). |
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | — | The label. |
size | 'sm' | 'md' | 'sm' | Row height 44 / 64px (Figma Small / Medium). |
leading | ReactNode | — | Icon, avatar, logo or flag. Rendered aria-hidden. |
sublabel | ReactNode | — | Secondary text after the label. |
description | ReactNode | — | Second line; use with size="md". |
badge | ReactNode | — | A badge element. |
shortcut | ReactNode | — | Shortcut hint; strings render in a Kbd. Display only. |
trailing | ReactNode | — | Decorative, aria-hidden. Never a button or link (C21). |
className | string | — | Added to .nu-menu-item.nu-command__item. |
ref | Ref<HTMLDivElement> | — | Forwarded to the cmdk item. |
…rest | cmdk Item props | — | From cmdk: onSelect, value, keywords, disabled, forceMount. |
| Prop | Type | Default | Description |
|---|---|---|---|
hints | { keys: ReactNode[]; label: ReactNode }[] | [] | Keyboard hints; each key renders in a muted Kbd. Labels are yours to translate. |
children | ReactNode | — | Content on the end side, e.g. a Help Center link. |
Kbd props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'muted' | 'default' | default: white chip for item and search-row shortcuts. muted: Weak-50 chip for footer hints. |
ref | Ref<HTMLElement> | — | Forwarded to the native kbd. |
…rest | HTMLAttributes<HTMLElement> | — | Any native attribute (className, aria-*…). |
Styling hooks
Items render .nu-menu-item with data-size and the state attributes listed under State tokens; containers render .nu-menu-content, .nu-combobox__content or .nu-command. Use props rather than styling these. The supported override points are the component tokens --nu-menu-min-width, --nu-menu-item-min-height, --nu-menu-list-max, --nu-command-width, --nu-command-footer-height, --nu-switch-on and --nu-switch-on-hover.
Accessibility
Keyboard
| Key | Behaviour |
|---|---|
| Enter / Space / ↓ | On a dropdown trigger: opens the menu and focuses the first item. |
| Enter / Space | On a Combobox trigger: opens the list with focus in the search field. |
| ↑ / ↓ | Moves between items, skipping disabled ones. Combobox and Command menu wrap at the ends. |
| Home / End | First or last item. |
| → / ← | Opens or closes a submenu. |
| Enter | Chooses the highlighted item. In a dropdown, Space does too. |
| A–Z | Dropdown: jumps to the item starting with that letter. Combobox and Command menu: filters the list. |
| Esc | Closes and returns focus to the trigger, or to whatever opened the Command menu. |
Screen readers
Dropdown menus are WAI-ARIA menus (Radix): checkbox and toggle items are menuitemcheckbox, options are menuitemradiowith aria-checked. The check mark, checkbox and toggle visuals are aria-hidden, so state isn't announced twice. The Combobox trigger has aria-haspopup="listbox" and is named by the field label and its value; the search field controls a listbox (cmdk). The Command menu is a modal dialog named by label. Leading visuals are hidden, so the label must carry the meaning.
Focus
Focus moves into the menu on open and back to the trigger on close. In Combobox and Command menu it starts in the search field; the Command menu traps it and returns it to the opener. Figma has no focus state for items: the highlight marks the active item and the browser's focus ring is kept (C10). In forced-colours mode the highlighted item gets a Highlight outline.
Contrast and colour
Sublabels, descriptions and shortcuts use Text/Sub-600 instead of Figma's remote text/soft-400 (2.49:1): 6.69:1 on White, 6.24:1 on Weak-50, 5.61:1 on Soft-200 (C19). The selected check mark is 15.04:1, so selection doesn't rely on colour (C18). Toggle on is 5.59:1 on White. The toggle off track is State/Faded/Base, 4.23:1, and darker Icon/Sub-600 when highlighted, the same as Switch (C54; Figma's Soft-200 was 1.19:1). The empty checkbox frame now uses State/Faded/Base, 4.23:1 (C48).
Touch and pointer
Items are at least 40px tall (Command menu 44px) and fill the menu width, well above 24 × 24 (WCAG 2.5.8). Combobox triggers are 36 or 48px. Menus open on click or tap, never on hover; pointing at an item only highlights it.
You must: name icon-only triggers (“Options for Photosynthesis”); give CommandMenu a label; put Combobox in a Field or pass aria-label; never put buttons or links in an item (C21); bind any shortcut you show; pass translated strings for placeholders, emptyText, searchLabel and footer hints.
Figma mapping
| Figma | Variants | Code |
|---|---|---|
| Dropdown Items (57:16405) | Type: Basic · Country · Avatar · Provider · Brand · Company — State: Default · Hover · Selected · Selected Hover · Disabled — Size: Small · Large (60 variants in total) | leading (any icon/flag/avatar/logo), size="sm" | "lg"; Hover = highlighted, Selected = radio/checked option, Disabled = disabled |
| Item options | Checkbox · Sublabel · Description · Badge · Shortcut · Toggle · Button · Right icon | DropdownMenuCheckboxItem, sublabel, description, badge, shortcut, DropdownMenuSwitchItem, trailing (decorative; no Button, C21), DropdownMenuSubTrigger |
| Composition (57:16368) | Container, dividers, 10px from trigger, search | DropdownMenuContent, DropdownMenuSeparator, DropdownMenuLabel, Combobox |
| Command Menu Search Input (82:6468) | Default · Hover · Active; Shortcut; Button | CommandMenu placeholder, shortcut |
| Command Menu Items (82:5901) | Type: Basic · Avatar · Left Icon · Brand · Company · Country — Default · Hover — Small · Medium | CommandMenuItem leading, size="sm" | "md", sublabel, description, badge, shortcut |
| Command Menu Footer (100:4781) | Key hints + help link | CommandMenuFooter hints, children |
Deviations from Figma
| ID | Kind | What code does |
|---|---|---|
| C16 | Open question | Container gap 4 (product screens) rather than 8 (DS documentation screenshot). Awaiting confirmation. |
| C17 | Figma discrepancy | Measured heights: Dropdown 40 / 56, Command 44 / 64. Labels to fix in Figma (FC-02, FC-03). |
| C18 | Product decision (deviates from Figma) | Figma's Selected Hover lightens the item to Weak-50, which makes it look unselected. In code the selected item keeps Soft-200 and a check mark (15.04:1, so selection doesn't rely on colour), and hover adds a 1px inset Stroke/Sub-300 ring. The ring is decorative only (FC-04). |
| C19 | Product decision (D7) | Sublabels, descriptions and shortcuts use local Nuton Text/Sub-600 instead of Figma's external text/soft-400 (2.49:1). Measured: 6.69:1 on White, 6.24:1 on Weak-50, 5.61:1 on Soft-200. All pass AA (FC-05). |
| C20 | Brand override (deviates from Figma) | Toggle "on" uses Nuton State/Primary/Base #3559E9 instead of the external library's #335CFF. Still failing and flagged for design: the "off" track and the empty checkbox frame (Soft-200) are 1.19:1 against White, below 3:1 (FC-06). The standalone Switch component is tracked separately (CI-01). |
| C21 | Product decision (deviates from Figma) | No buttons or links inside a menu item. trailing is decorative and hidden from assistive technology. Development builds warn if it contains a button, a link or an onClick (FC-07). |
Guidelines and related components
Use these for
- Dropdown menu: secondary actions on a lesson or space, and view options such as sort and filters.
- Combobox: choosing one value from a long or rich list in a form, such as a student or a country.
- Command menu: jumping to any lesson, space or person, and running commands from anywhere.
Don't use them for
- One or two actions that fit on screen. Use Button.
- Choosing between 2–5 options people should see at once. Use Button group.
- A short, plain list in a form. Use the native
Selecton Inputs and fields. - Several values at once in a form. Combobox chooses one value.
Do and don't
Common mistakes
- Putting a button or link in
trailing(C21). Make it a separate item instead. - Showing
shortcutwithout binding the key. The prop only draws the hint. - Expecting a checkbox list to stay open. Checkbox items close the menu like any item; add
onSelect={(e) => e.preventDefault()}. Toggle items stay open on their own. - Relying on the leading icon or avatar for meaning. It's hidden from screen readers; the label must say it all.
- Forgetting
nameon a Combobox in a plain HTML form, so the value isn't submitted.