Option List

Picker panel with option rows, search, checks, and footer actions. Rows compose List Item. Configure root width and panel match. Select composes this.

Also known as: options list, select list, picker list, option rows

Installation

Install with the CLI, or copy the component source into your project.

Add option-list (npm)

npx --yes -p @default-file/ui df-ui add option-list

Panel

The open list surface. Item rows are documented separately below.

Card panel

chrome panel owns the bordered card. OptionListHeader adds a label, description, and edge-to-edge bottom rule. Width defaults to hug. Theme the surface with background, foreground, borderColor, borderWidth, borderStyle, dividerColor, and radius (CSS token values).

Width

Configure OptionList width (hug, fill, or a CSS length) for triggers and always-visible panels. OptionListContent alignItemWithTrigger controls whether the open menu matches the trigger; fill and fixed default to match, hug defaults to content.

Item states

List item alone: default, hover, selected (active), and disabled.

Item content

Leading checkbox, primary label, and muted secondary text across states.

Leading icon

Custom icon in the OptionListItem leading slot. Selected rows keep the trailing check indicator.

Stacked description

Heading on top, description underneath. The whole block is one selectable option.

Stacked with leading icon

Stacked title and description with a custom node in the leading slot. The icon aligns to the title line, not the full title and description block.

Stacked with checkbox

Same stacked layout with a leading checkbox for multi-select.

Trailing badge counter

Number counter at the end of the row via the trailing slot.

Hover flyout

openOnHover opens the panel from the trigger on pointer enter. Stay over the trigger, panel, or nested submenu panels to keep it open. Compose OptionListTrigger onto List Item for rail and sidebar rows. Use width fill so trailing icons stay end-aligned.

Nested menu

Hover a parent row to open the next level. Badge plus chevron marks nested parents; leave the branch to close it.

Groups and separators

Section labels and dividers inside the panel. Pass inset on OptionListSeparator so the rule follows content padding; omit inset to span the full content width.

Search, scroll, and actions

Full panel chrome: search, scrollable body, and footer actions. Use searchBackground to override the search field fill.

Properties

Open panel and option rows for pickers. Select composes this list for its menu. Row chrome is List Item: use that page for size, muted or accent variants, asChild hosts, and standalone rows outside a picker. Configure root width for the trigger shell; open-list width follows that mode unless OptionListContent overrides alignItemWithTrigger. Full keyboard support: arrow keys, Home and End, Enter, typeahead, and focus return to the trigger on close.

OptionList

Root selection, open state, and width.

PropTypeDefaultNotes
selectionMode"single" | "multiple""single"Single value or multiple values.
valuestring | null-Controlled value in single mode.
defaultValuestring | nullnullUncontrolled initial value in single mode.
onValueChange(value: string | null) => void-Called when the single value changes.
valuesstring[]-Controlled values in multiple mode.
defaultValuesstring[][]Uncontrolled initial values in multiple mode.
onValuesChange(values: string[]) => void-Called when multiple values change.
openboolean-Controlled open state.
defaultOpenbooleanfalseUncontrolled initial open state.
onOpenChange(open: boolean) => void-Called when the panel opens or closes.
openOnHoverbooleanfalseOpen when the pointer enters the trigger. Stay open while the pointer is over the trigger, the panel, or nested OptionListSubContent panels. Pointer leave closes after hoverCloseDelay. Click, Enter, and Space open when closed; they do not toggle closed. Escape, pointer leave, or outside dismiss closes. Nested OptionListSubmenu hover remains independent for child levels.
hoverCloseDelaynumber60Milliseconds before closing after pointer leave when openOnHover is true. Shares the default delay used by OptionListSubmenu closeDelay.
closeOnSelectboolean-Close after choosing a row. Defaults to true in single mode, false in multiple.
width"hug" | "fill" | string"hug"Root width for triggers and always-visible panels (including chrome panel). hug sizes to content. fill stretches the root and trigger hosts to the parent, including OptionListTrigger render hosts such as List Item or Badge. Pass a CSS length such as 12rem for a fixed width. Select triggers inside follow the same width. Fill and fixed also default the open menu to match the trigger; hug defaults the menu to option content. Override with OptionListContent alignItemWithTrigger. Use fill for sidebar and rail rows so List Item trailing icons stay end-aligned.
itemChromeListItemChromeProps-Default List Item chrome for every OptionListItem (padding, gap, type size/family/weight/color, radius, fills, and stroke). Per-item chrome props on OptionListItem win. See List Item for the full chrome contract.
submenuAnimatedbooleantrueDefault motion for all nested OptionListSubmenu panels. true plays open/close animations; false makes nested menus appear and disappear instantly. Overridden by OptionListSubmenu or OptionListSubContent animated.
submenuOpenDurationnumber180Default open animation duration in milliseconds for nested submenus. Ignored when submenuAnimated is false. Overridden by OptionListSubmenu or OptionListSubContent openDuration.
submenuCloseDurationnumber90Default close animation duration in milliseconds for nested submenus. Ignored when submenuAnimated is false. Overridden by OptionListSubmenu or OptionListSubContent closeDuration.

OptionListTrigger

Opens and closes the option list. Use render to compose onto List Item, Badge, or another host. When OptionList width is fill or fixed, the render host stretches to that width so List Item trailing slots stay end-aligned.

PropTypeDefaultNotes
renderReactElement-Host element to clone trigger behavior onto (for example List Item or Badge). Owns click, keyboard, hover open when openOnHover is set, and the anchor ref. Marks the host with data-df-option-list-trigger.
childrenReactNode-Trigger content (label, chevron, etc.).
classNamestring-Additional styles.

OptionListContent

Portaled or inline panel with optional header, search, scroll, footer, and dismissOnScroll.

PropTypeDefaultNotes
align"start" | "center" | "end" | "auto""start"Horizontal anchor relative to the trigger. auto picks the best fit.
alignItemWithTriggerboolean-Configure open-list width. true matches the trigger. false hugs the longest option (floored at --df-menu-min-width). Omit to follow the root width prop: hug defaults to false; fill and fixed default to true.
side"top" | "bottom" | "left" | "right""bottom"Preferred placement side. Flips when there is not enough space.
sideOffsetnumber4Gap between trigger and panel.
alignOffsetnumber0Shift along the alignment axis.
portalbooleantrueRender in a portal. Set false for inline previews.
chrome"menu" | "plain" | "panel""menu"menu keeps popover fill, shadow, and radius. plain drops those so the list can sit inside a host surface. panel is a bordered card for always-visible lists.
backgroundCSS color-Surface fill for any chrome mode. Sets --df-option-list-surface-bg. Prefer a token such as var(--card) or var(--popover).
foregroundCSS color-Surface text color for any chrome mode. Sets --df-option-list-surface-fg. Header label inherits this color.
borderColorCSS color-Outer surface stroke only. Sets --df-option-list-surface-border-color. Does not change header, search, or footer rules; use dividerColor for those.
borderWidthCSS length-Outer surface stroke width. Sets --df-option-list-surface-border-width. Prefer var(--border-width-hairline).
borderStyleCSS border-style-Outer surface stroke style. Sets --df-option-list-surface-border-style. Default solid.
dividerColorCSS color-Hairline rules on header, search, and footer. Sets --df-option-list-divider-color. Defaults to var(--border).
radiusCSS length-Surface corner radius for any chrome mode. Sets --df-option-list-surface-radius. Prefer a radius token such as var(--radius-xl).
dismissOnScrollbooleantrueClose when the page scrolls. Scrolling inside the list stays open. Set false to keep the panel following the trigger (it can cover sticky chrome).
headerReactNode-Panel header above the options. Prefer OptionListHeader for a label, description, and edge-to-edge bottom rule. Stacks the panel like search and footer.
searchbooleanfalseShow a search field above the options.
searchPlaceholderstring"Search"Placeholder for the search field.
searchBackgroundstring-Search field fill. Prefer tokens such as var(--muted). Forwards to SearchInput background. Named separately because background themes the panel surface.
searchValuestring-Controlled search query.
defaultSearchValuestring-Uncontrolled initial search query.
onSearchChange(value: string) => void-Called when the search query changes.
scrollableboolean-Wrap options in the kit ScrollArea. Omit to default true when the panel has no submenu, and false when it contains a submenu.
scrollMaxHeightstring | number-Max height of the scrollable body before it scrolls. Omit for min(60vh, var(--df-menu-max-height)), or var(--df-menu-stacked-max-height) when header, search, or footer is present. When the panel opens, the selected option scrolls into the list scrollport while list order stays the same.
scrollThumbGapnumber-Space on each side of the scrollbar thumb, in pixels. Omit to use --df-option-list-scroll-thumb-gap.
footerReactNode-Footer actions under the options (for example Reset).
classNamestring-Panel surface styles.

OptionListItem

Selectable List Item with value, search filter, and submenu trigger behavior. Forwards List Item presentation props (size, variant, leading, trailing, layout, and full chrome: padding, type, fills, stroke, radius). Selection, highlight, and open state stay owned by Option List. Nested submenu triggers pass ListItemSubmenuChevron (or Badge plus that chevron) through trailing. Inside OptionListSubmenu (trigger zone) the row opens the submenu on hover instead of selecting. For standalone rows and asChild links, see List Item.

PropTypeDefaultNotes
valuestring-Option value stored in selection state.
size"sm" | "md" | "lg""md"List Item density. Forwarded to List Item. Use sm for compact nested submenu rows.
variant"accent" | "muted""accent"List Item fill recipe. Forwarded to List Item. accent is the picker default; muted is for quiet nav-style rows.
disabledboolean-Not selectable row.
readOnlyboolean-Presentational row. Forwarded to List Item. Not selectable and no hover chrome. Keeps resting and selected appearance.
leading"checkbox" | "check" | ReactNode | false-Leading slot before the label. Works with inline, stacked, and columns layouts. On stacked rows, icons align to the title line. For Avatar or other non-icon marks, set leadingFit to content. On columns rows, leading hugs text. Set aria-hidden on decorative icons. Pass an icon or other node; use "checkbox" for multi-select, "check" for a leading selected mark, or false to omit.
leadingFit"icon" | "content""icon"Forwarded to List Item. icon keeps the mark box. content sizes the track to Avatar and other non-icon nodes. See List Item leadingFit.
secondaryReactNode-Supporting copy beside or under the label.
layout"inline" | "stacked" | "columns""inline"inline places secondary beside the label; stacked places it underneath; columns is leading, main, and trailing text with the main label horizontally centered in the row and height from content. With leading on stacked rows, the icon aligns to the title line. Select mirrors stacked layout in the trigger value.
trailingReactNode-End of row slot: badge counter, nested menu chevron, measure text, or both. When set, the selected check is hidden unless indicator is forced on. Submenu triggers pass the chevron here; List Item renders the trailing slot.
indicatorboolean-Selected check in single mode. Defaults off when leading is checkbox or trailing is set; otherwise on.
childrenReactNode-Primary label text for the row.
classNamestring-Additional row styles.
styleCSSProperties-Host style. Merged after List Item chrome CSS variables.
paddingstring-Padding on all sides. Forwarded to List Item. Sets each --df-list-item-padding-* side.
paddingXstring-Horizontal padding. Forwarded to List Item. Overrides padding on those sides.
paddingYstring-Vertical padding. Forwarded to List Item. Overrides padding on those sides.
paddingTopstring-Top padding. Forwarded to List Item. Wins over paddingY and padding.
paddingRightstring-Right padding. Forwarded to List Item. Wins over paddingX and padding.
paddingBottomstring-Bottom padding. Forwarded to List Item. Wins over paddingY and padding.
paddingLeftstring-Left padding. Forwarded to List Item. Wins over paddingX and padding.
gapstring-Minimum space between leading, label, trailing, and indicator. Forwarded to List Item.
fontSizestring-Label type size. Forwarded to List Item. Prefer type tokens such as var(--df-text-11).
fontFamilystring-Label font family. Forwarded to List Item.
fontWeightstring-Label font weight. Forwarded to List Item.
radiusstring-Corner radius of the row fill and stroke. Forwarded to List Item.
backgroundstring-Resting fill. Forwarded to List Item. Root itemChrome supplies defaults; per-item props win.
foregroundstring-Resting label color. Forwarded to List Item.
hoverBackgroundstring-Hover, highlight, and focus fill. Forwarded to List Item.
hoverForegroundstring-Hover, highlight, and focus label color. Forwarded to List Item.
selectedBackgroundstring-Selected fill. Forwarded to List Item.
selectedForegroundstring-Selected label color. Forwarded to List Item.
selectedHoverBackgroundstring-Selected row hover fill. Forwarded to List Item.
activeBackgroundstring-Pressed fill. Forwarded to List Item.
borderWidthstring-Stroke width. Forwarded to List Item. Prefer var(--border-width-hairline). Default 0.
borderColorstring-Stroke color. Forwarded to List Item. Prefer var(--border).
borderStylestring-Stroke style such as solid or dashed. Forwarded to List Item.

OptionListSubmenu

Hover open nested branch. Wrap a trigger OptionListItem and an OptionListSubContent. The trigger is a normal OptionListItem, so List Item size, variant, trailing chevron, and chrome props apply. Nest again inside the content for deeper levels. Motion defaults inherit from OptionList and can be overridden here.

PropTypeDefaultNotes
openboolean-Controlled submenu open state.
defaultOpenbooleanfalseUncontrolled initial open state (useful for docs previews).
onOpenChange(open: boolean) => void-Called when the submenu opens or closes.
animatedbooleaninherits OptionList.submenuAnimated (true)true plays fade/slide open and close for this branch. false opens and closes instantly with no motion. Prefer OptionList submenuAnimated=false to turn off motion for every nested level at once.
openDurationnumberinherits OptionList.submenuOpenDuration (180)Open animation duration in milliseconds for this branch. Only applies when animated is true.
closeDurationnumberinherits OptionList.submenuCloseDuration (90)Close animation duration in milliseconds for this branch. Only applies when animated is true. Keep this shorter than openDuration for a snappy dismiss.
closeDelaynumber60Milliseconds to wait after the pointer leaves the branch before starting close. Gives time to move into the child panel. Exit animation (if animated) starts after this delay.
childrenReactNode-Trigger OptionListItem plus OptionListSubContent (and nested submenus).

OptionListSubContent

Child panel for a submenu. Opens on hover of the parent row; nest further OptionListSubmenu trees inside. Placement and motion can be set per panel.

PropTypeDefaultNotes
side"top" | "bottom" | "left" | "right""right"Preferred placement relative to the parent row. Flips when there is not enough space.
sideOffsetnumber4Gap between the parent row and the child panel.
align"start" | "center" | "end" | "auto""start"Alignment along the parent row.
alignOffsetnumber0Shift along the alignment axis.
portalbooleantrueRender in a portal. Set false for inline nested previews in docs.
animatedbooleaninherits OptionListSubmenu.animatedPer panel motion override. false makes this panel appear and disappear instantly even if parent submenus are animated.
openDurationnumberinherits OptionListSubmenu.openDurationPer panel open duration in milliseconds. Ignored when animated is false.
closeDurationnumberinherits OptionListSubmenu.closeDurationPer panel close duration in milliseconds. Ignored when animated is false.
classNamestring-Panel surface styles.
childrenReactNode-Option rows and nested OptionListSubmenu branches.

OptionListBody

Scrollable or static body wrapping option rows.

PropTypeDefaultNotes
scrollablebooleanfalseWrap children in ScrollArea.
maxHeightstring | number"var(--df-menu-stacked-max-height)"Height constraint when scrollable.
scrollThumbGapnumbertoken (--df-option-list-scroll-thumb-gap)Space on each side of the scrollbar thumb, in pixels.
childrenReactNode-Option rows.

OptionListHeader

Panel title block with an edge-to-edge bottom rule. Distinct from OptionListLabel, which is an in-list group heading.

PropTypeDefaultNotes
labelReactNode-Primary panel title (uppercase label style). When set, OptionListContent names the listbox with aria-labelledby.
descriptionReactNode-Supporting copy under the label.
childrenReactNode-Optional content below the description.
classNamestring-Additional styles.

OptionListGroup

Groups related items.

PropTypeDefaultNotes
classNamestring-Additional styles on the group.
childrenReactNode-Grouped rows and labels.

OptionListLabel

Group heading inside the panel. Renders ListItemLabel with variant menu.

PropTypeDefaultNotes
childrenReactNode-Heading copy.
classNamestring-Additional styles on the heading.

OptionListSeparator

Horizontal rule between in-list sections. For a panel header rule that touches both edges, use OptionListHeader.

PropTypeDefaultNotes
insetbooleanfalseWhen true, inset the rule to match group padding plus default row horizontal padding. Override distance with --df-option-list-separator-inset. When false, span the full content width.
classNamestring-Additional styles on the rule.

OptionListScrollUpButton and OptionListScrollDownButton

Optional scroll affordances for long lists.

PropTypeDefaultNotes
classNamestring-Additional styles on the button.

useOptionListContext

Hook for composed parts. Returns open state, selection, labels, and search from the nearest OptionList.

Layout and appearance

Fixed width (width CSS length): Pass a CSS length on OptionList such as 12rem or 200px. Sets --df-option-list-width on the root. Hug menu content (alignItemWithTrigger={false}): Open menu width follows option content. Default when root width is hug, or pass false to override fill and fixed. Match trigger width (alignItemWithTrigger): Lock open menu width to the trigger. Default when root width is fill or fixed. Inline previews (portal={false}): Render the panel in place for docs and nested layouts. Trailing badge counter (trailing={<Badge>}): Number badge at the end of the row. Right padding matches checkbox rows; selected check stays hidden. Hover flyout (openOnHover): Open the root panel from a trigger on pointer enter. Keep the pointer over the trigger, panel, or nested submenu panels to stay open. Click, Enter, and Space open when closed without toggle-close. Compose OptionListTrigger onto List Item for sidebar and rail rows. Set width fill so the trigger row spans the rail and trailing icons stay end-aligned. Nested submenu row (OptionListSubmenu + SubContent): Hover the parent row to open the child panel on the right. Nest Submenu trees for three or more levels. Use trailing for Badge plus ListItemSubmenuChevron. Control motion with submenuAnimated / animated (boolean) and submenuOpenDuration / submenuCloseDuration or openDuration / closeDuration (ms). Instant nested menus (submenuAnimated={false}): Disable open/close animation for every nested level from OptionList. Equivalent per branch: OptionListSubmenu animated={false}. Custom nested speed (submenuOpenDuration / submenuCloseDuration): Set open and close durations in milliseconds on OptionList (defaults 180 / 90). Override on a single Submenu or SubContent when one branch should feel different. Item spacing (CSS): Rows use kit gap and hover/selected/disabled states via data attributes. List Item props (ListItem): OptionListItem and submenu trigger rows render List Item. size, variant, slots, and chrome props forward through. Open and selected appearance are controlled by Option List. Open the List Item page for the full contract and standalone use.

Keyboard and accessibility

Applies to portaled panels. Inline panels (portal={false}) keep the page focus flow unchanged. Open and focus (built-in): Opening moves focus into the panel, or the search field when one is present. Closing by keyboard or selection returns focus to the trigger. Arrow keys (ArrowUp / ArrowDown): Move the active option with wrap around the ends. Home and End jump to the first and last option. Select (Enter / Space): Enter selects the active option. Space selects when focus is on the panel; inside the search field Space types a space. Typeahead (printable keys): Jump to the first option that starts with the typed query, then to any option that contains it. The buffer clears after a short pause. Disabled while a search field is present. Submenus (ArrowRight): Opens the submenu when the active row is a submenu trigger. ARIA (listbox): The panel is a listbox with aria-activedescendant on the active option. When OptionListHeader has a label, the listbox uses aria-labelledby. The trigger exposes aria-controls to the panel while open. High contrast (forced-colors): Under forced-colors, selected and active rows use the system Highlight and HighlightText colors.

Tokens

Theme defaults live on :root. Override these globally or on a local host.

TokenTypeDefaultNotes
--df-option-list-widthCSS length-Fixed root width when width is a CSS length. Set by the width prop.
--df-input-bgCSS colorvar(--background)Built-in search field fill. Override with searchBackground or OptionListSearch background.
--df-option-list-surface-bgCSS color-Panel surface fill. Defaults follow chrome (popover or card). Set via background.
--df-option-list-surface-fgCSS color-Panel text color. Set via foreground.
--df-option-list-surface-border-colorCSS color-Outer surface stroke. Set via borderColor.
--df-option-list-surface-border-widthCSS length-Outer surface stroke width. Set via borderWidth.
--df-option-list-surface-border-styleCSS border-stylesolidOuter surface stroke style. Set via borderStyle.
--df-option-list-divider-colorCSS colorvar(--border)Header, search, and footer hairline rules. Set via dividerColor.
--df-option-list-surface-radiusCSS length-Surface corner radius. Set via radius.
--df-option-list-min-widthCSS lengthvar(--df-menu-min-width)Minimum open-list width. Defaults to var(--df-menu-min-width).
--df-option-list-group-paddingCSS lengthcalc(1 * var(--spacing-unit))Padding on OptionListGroup. Used by inset separators.
--df-option-list-separator-insetCSS lengthgroup pad + 3 spacing unitsHorizontal inset when OptionListSeparator inset is true.
--df-option-list-scroll-thumb-gapCSS lengthcalc(1 * var(--spacing-unit))Space on each side of the scrollbar thumb. Overridable via scrollThumbGap.
--df-option-list-scroll-track-widthCSS lengthcalc(2.5 * var(--spacing-unit))Track width used to reserve the option list scroll gutter.
--df-menu-min-widthCSS length9remMinimum menu width.
--df-menu-max-heightCSS length24remMaximum panel height before scrolling.
--df-menu-stacked-max-heightCSS length16remMaximum body height when header, search, or footer stacks the panel.
--df-submenu-min-widthCSS length18remMinimum nested submenu width.
--df-submenu-max-heightCSS length28remMaximum nested submenu height.
--popoverCSS colorvar(--df-neutral-0)Panel fill.
--popover-foregroundCSS colorvar(--df-neutral-900)Panel text color.
--foregroundCSS colorvar(--df-neutral-900)Option label color.
--muted-foregroundCSS colorvar(--df-neutral-500)Secondary label and muted chrome.
--accentCSS colorvar(--df-neutral-50)Hover and highlight row fill.
--accent-foregroundCSS colorvar(--df-neutral-850, var(--df-neutral-900))Hover and highlight row text.
--primaryCSS colorvar(--df-neutral-850, var(--df-neutral-900))Checked checkbox fill and border.
--primary-foregroundCSS colorvar(--df-neutral-0)Checked checkbox glyph color.
--inputCSS colorvar(--df-neutral-100)Checkbox border.
--backgroundCSS colorvar(--df-neutral-0)Checkbox unchecked fill.