Dropdown
An anchored action menu with a shared hover highlight and keyboard navigation.
Installation
Import from the Reflow workspace package.
Playground
Open demoimport { Icon, Dropdown } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const items = [
{ value: 'Email', label: 'Email', icon: <Icon name="mail" />, type: 'radio', checked: selected === 'Email' },
{ value: 'Notifications', label: 'Notifications', icon: <Icon name="bell" />, type: 'radio', checked: selected === 'Notifications' },
{ value: 'Privacy', label: 'Privacy', icon: <Icon name="shield" />, type: 'radio', checked: selected === 'Privacy' },
{ value: 'General', label: 'General', icon: <Icon name="settings" />, type: 'radio', checked: selected === 'General' },
{ value: 'Theme', label: 'Theme', icon: <Icon name="palette" />, children: ['Light', 'Dark', 'System'].map((name) => ({ value: name, label: name, type: 'radio', checked: theme === name })) },
{ value: 'Display', label: 'Display', icon: <Icon name="monitor" />, type: 'radio', checked: selected === 'Display' },
]
let selected: string[] = []
let plan = 'free'
const select = (value: string) => { plan = value; handle.update() }
return () => (
<>
<Dropdown label={selected}
variant="ghost"
items={items}
onSelect={select} />
</>
)
}Basic
import { Icon, Dropdown } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const items = [
{ value: 'Teamspaces', label: 'Teamspaces', icon: <Icon name="square-library" /> },
{ value: 'Recents', label: 'Recents', icon: <Icon name="clock" /> },
{ value: 'Favorites', label: 'Favorites', icon: <Icon name="star" /> },
{ value: 'Shared', label: 'Shared', icon: <Icon name="users" /> },
{ value: 'Private', label: 'Private', icon: <Icon name="lock" /> },
].map((item) => ({ ...item, type: 'radio', checked: selected === item.value }))
let selected: string[] = []
const setSelected = (values: string[]) => { selected = values; handle.update() }
return () => (
<>
<Dropdown inline label="Library" items={items}
onSelect={(value) => setSelected(selected === value ? null : value)} />
</>
)
}Groups
Group labels and separators come from group and separatorBefore.
import { Dropdown, Icon } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
const icon = <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6h16M4 12h16M4 18h16" /></svg>
return () => (
<>
<Dropdown inline label="Settings" items={[
{ value: 'Email', label: 'Email', group: 'Account', icon: <Icon name="mail" /> },
{ value: 'Notifications', label: 'Notifications', group: 'Account', icon: <Icon name="bell" /> },
{ value: 'Privacy', label: 'Privacy', group: 'Account', icon: <Icon name="shield" /> },
{ value: 'General', label: 'General', group: 'Appearance', separatorBefore: true, icon: <Icon name="settings" /> },
{ value: 'Theme', label: 'Theme', group: 'Appearance', icon: <Icon name="palette" /> },
{ value: 'Display', label: 'Display', group: 'Appearance', icon: <Icon name="monitor" /> },
]} />
</>
)
}Multiple selection
Checkbox rows with type: "checkbox".
import { Dropdown } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<Dropdown label="Status" variant="ghost" chevron={false}
items={statuses.map((name) => ({ value: name, label: name, type: 'checkbox', checked: picked.includes(name) }))}
onSelect={toggle} />
</>
)
}Searchable multiple selection
Search and checkbox rows together.
import { Dropdown } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<Dropdown label={`Labels · ${labels.length}`} variant="ghost" chevron={false}
searchable searchPlaceholder="Search labels" emptyMessage="No labels found"
items={names.map((name) => ({ value: name, label: name, type: 'checkbox', checked: labels.includes(name) }))}
onSelect={toggle} />
</>
)
}Create from the query
An extra row appears when nothing matches the query.
import { Dropdown } from '@rtzll/reflow'
import { type Handle } from 'remix/component'
export function Example(handle: Handle) {
return () => (
<>
<Dropdown label={`Components · ${used.length}`} variant="ghost" chevron={false}
creatable searchPlaceholder="Search components" emptyMessage="No components found"
items={components.map((name) => ({ value: name, label: name, type: 'checkbox', checked: used.includes(name) }))}
onCreate={addComponent} onSelect={toggle} />
</>
)
}API Reference
Public props for the Reflow implementation. Native controls also accept their standard HTML attributes and Remix event mixes.
Dropdown
| Prop | Type | Default | Description |
|---|---|---|---|
label* | string | — | Accessible name or visible label for the control. |
items* | readonly DropdownItem[] | — | Items and their content. |
onSelect | ((value: string, checked?: boolean) => void) | — | Called when an item is selected. |
size | "default" | "compact" | — | Control density or icon-button dimensions. |
align | "start" | "end" | start | Horizontal alignment of the action or popup. |
variant | "ghost" | "outline" | outline | Visual or layout treatment. |
searchable | boolean | — | Enables filtering. |
searchPlaceholder | string | Search… | Placeholder for the menu search field. |
inline | boolean | — | Renders the menu without a popup trigger. |
creatable | boolean | — | Allows a new option to be created from the query. |
onCreate | ((value: string) => void) | — | Receives the newly created option. |
DropdownItem
| Prop | Type | Default | Description |
|---|---|---|---|
closeOnSelect | boolean | — | Whether selecting closes the menu. Defaults to false for checkbox items and true for other items. |
group | string | — | Label used to group related items. |
value* | string | — | Identifier passed to onSelect when this row is activated. |
label* | string | — | Accessible name or visible label for the control. |
icon | RemixNode | — | Icon content. |
shortcut | string | — | Keyboard hint or optional command-palette shortcut. |
disabled | boolean | — | Prevents interaction and conveys the disabled state. |
separatorBefore | boolean | — | Draws a separator before this item. |
type | "checkbox" | "radio" | — | Single or multiple expansion mode. |
checked | boolean | — | Selected state for a checkbox or radio menu row. |
children | readonly DropdownItem[] | — | Items in a nested submenu. |