Forms & Inputs
Dropdown
A trigger that reveals a panel of options — single or multi-select, optionally searchable. Compose it from nana-option children (use nana-menu-label for groups). Flips above the trigger when it would overflow.
Preview
Section titled “Preview”<nana-dropdown label="Assign to" style="min-width: 16rem">
<nana-option value="ada">Ada Lovelace</nana-option>
<nana-option value="alan">Alan Turing</nana-option>
<nana-option value="paula">Paula Hawkins</nana-option>
</nana-dropdown> Installation
Section titled “Installation”A dropdown ships for all three targets, but they are not the same component — see the note below. Pick the tab for your project — the CLI detects which you are on and serves the matching implementation.
npx @nana-tec/ui add dropdownLands as src/components/ui/dropdown/dropdown.tsx, styled with Tailwind classes that
resolve to the design tokens. Requires Tailwind v4.
npx @nana-tec/ui add dropdownLands in src/lib/components/ui/dropdown/ as Dropdown.svelte, DropdownItem.svelte, DropdownLabel.svelte and DropdownSeparator.svelte, styled with Tailwind
classes that resolve to the design tokens. Requires Tailwind v4.
Install the package for a versioned dependency:
npm install @nana-tec/ui-componentsOr copy the source in and own it:
npx @nana-tec/ui add dropdownSearchable
Section titled “Searchable”Add the searchable attribute to filter options as you type.
<nana-dropdown label="Country" searchable style="min-width: 16rem">
<nana-option value="us">United States</nana-option>
<nana-option value="ca">Canada</nana-option>
<nana-option value="mx">Mexico</nana-option>
</nana-dropdown> Multi-select
Section titled “Multi-select”Set select="multiple" to allow multiple selections.
<nana-dropdown label="Frameworks" select="multiple" style="min-width: 16rem">
<nana-option value="react">React</nana-option>
<nana-option value="vue">Vue</nana-option>
<nana-option value="svelte">Svelte</nana-option>
</nana-dropdown> Grouped
Section titled “Grouped”Use nana-menu-label to group options.
<nana-dropdown label="Assign to" style="min-width: 16rem">
<nana-menu-label>Design</nana-menu-label>
<nana-option value="ada">Ada Lovelace</nana-option>
<nana-menu-label divider>Engineering</nana-menu-label>
<nana-option value="alan">Alan Turing</nana-option>
</nana-dropdown> import { Dropdown, DropdownItem, DropdownLabel, DropdownSeparator,} from "@/components/ui/dropdown";import { Button } from "@/components/ui/button";
export function Example() { return ( <Dropdown trigger={<Button variant="ghost">Actions</Button>} align="end"> <DropdownLabel>Manage</DropdownLabel> <DropdownItem onClick={rename}>Rename</DropdownItem> <DropdownItem onClick={duplicate}>Duplicate</DropdownItem> <DropdownSeparator /> <DropdownItem destructive onClick={remove}>Delete</DropdownItem> </Dropdown> );}This is an action menu: role="menu" with role="menuitem" children. It
closes on Escape, on an outside click, and after an item is chosen, and returns
focus to the trigger.
<script lang="ts"> import { Dropdown, DropdownItem, DropdownLabel, DropdownSeparator, } from '$lib/components/ui/dropdown';</script>
<Dropdown align="end" label="Actions"> {#snippet trigger()} <span>Actions</span> {/snippet}
<DropdownLabel>Manage</DropdownLabel> <DropdownItem onclick={rename}>Rename</DropdownItem> <DropdownItem onclick={duplicate}>Duplicate</DropdownItem> <DropdownSeparator /> <DropdownItem destructive onclick={remove}>Delete</DropdownItem></Dropdown>This is an action menu: role="menu" with role="menuitem" children. The
trigger is a snippet, open is $bindable, and it closes on Escape, on an
outside click, and after an item is chosen.
import "@nana-tec/ui-components/dropdown";<nana-dropdown label="Assign to"> <nana-option value="ada">Ada Lovelace</nana-option> <nana-option value="alan">Alan Turing</nana-option></nana-dropdown>Classes you pass to the React and Svelte components are merged with
tailwind-merge, so an override wins rather than fighting. See
Using Tailwind.
The React and Svelte props, read from the components’ own types:
Dropdown
| Prop | Type | Default |
|---|---|---|
triggerrequired | ReactNode | |
align | DropdownAlign | 'start' |
label | string | |
children | ReactNode |
Also accepts Omit<HTMLAttributes<HTMLDivElement>, 'children'>.
DropdownItem
| Prop | Type | Default |
|---|---|---|
destructive | boolean | false |
Also accepts ButtonHTMLAttributes<HTMLButtonElement>.
Dropdown
| Prop | Type | Default |
|---|---|---|
align | DropdownAlign | 'start' |
label | string | |
openbind: | boolean | false |
trigger | Snippet | |
children | Snippet |
Also accepts Omit<HTMLAttributes<HTMLDivElement>, 'class'>.
DropdownItem
| Prop | Type | Default |
|---|---|---|
destructive | boolean | false |
children | Snippet |
Also accepts Omit<
HTMLButtonAttributes,
'class'
>.
The attribute names, events and CSS hooks below are the Web Components reference.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
disabled | disabled | boolean | false | Disable the dropdown |
label | label | string | 'Select' | Trigger label / placeholder (default trigger only) |
open | open | boolean | false | Open state |
searchable | searchable | boolean | false | Show a search field that filters the options |
select | select | DropdownSelect | 'single' | Single or multiple selection |
value | value | string | '' | Selected value(s) — comma-separated for multiple |
Events
| Event | Description |
|---|---|
nana-change | Fired on selection (detail.value — string or string[]) |
Slots
| Slot | Description |
|---|---|
trigger | Custom trigger element (falls back to a default button) |
(default) | nana-option / nana-menu-label children |
CSS parts
| Part | Description |
|---|---|
::part(panel) | The floating options panel |
CSS custom properties
| Property | Description |
|---|---|
--nana-dropdown-max-height | Panel max height before scrolling |
--nana-dropdown-z | Stacking order for the floating panel |
Events
Section titled “Events”| Event | Type | Description |
|---|---|---|
nana-change | CustomEvent | Fired on selection (detail.value — string or string[]) |
| Slot | Description |
|---|---|
trigger | Custom trigger element (falls back to a default button) |
(default) | nana-option / nana-menu-label children |
CSS Variables
Section titled “CSS Variables”| Variable | Description |
|---|---|
--nana-dropdown-max-height | Panel max height before scrolling |
CSS Parts
Section titled “CSS Parts”| Part | Description |
|---|---|
panel | The floating options panel |
Keyboard
Section titled “Keyboard”The React and Svelte dropdown is a menu, and takes its keyboard from the shared menu rules:
| Key | On the trigger | In the menu |
|---|---|---|
↓ Enter Space | Opens, focus on the first item | Next item, wrapping |
↑ | Opens, focus on the last item | Previous item, wrapping |
Home End | — | First / last item |
| A–Z | — | Jumps to the next item starting with that letter; press again to cycle |
Escape | — | Closes, focus returns to the trigger |
Tab | — | Closes, focus carries on past the menu |
nana-dropdown is a listbox rather than a menu, so it reads the
listbox rules instead: arrows walk and wrap, Home and
End jump, Enter chooses, Escape closes. Its search field replaces
typeahead, and takes Home and End for the caret while it has focus.
Accessibility
Section titled “Accessibility”- Keyboard accessible
- Screen-reader labels
- Focus-visible states
- ARIA roles and states
- WCAG 2.2 AA contrast