Hanzo GUI

Select

Show a menu of items that users can select from

  • Comes with styling, yet completely customizable and themeable.
  • Accepts animations, themes, size props and more.
  • Accessible, keyboard navigable, full-featured. Select provides a dropdown menu for choosing from a list of options. It's fully accessible with keyboard navigation, supports typeahead search, and automatically stacks above other content.

Installation

Select is already installed in @hanzo/gui, or you can install it independently:

npm install @hanzogui/select

For native apps, we recommend setting up native portals to preserve React context inside Select content.

Anatomy

import { Select } from '@hanzo/gui' // or '@hanzogui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>
    {/* Optional: Control focus behavior */}
    <Select.FocusScope loop trapped focusOnIdle={true}>
      <Select.Content>
        <Select.ScrollUpButton />
        <Select.Viewport>
          <Select.Group>
            <Select.Label />
            <Select.Item>
              <Select.ItemText />
            </Select.Item>
          </Select.Group>
        </Select.Viewport>
        <Select.ScrollDownButton />
      </Select.Content>
    </Select.FocusScope>
  </Select>
)

Note that Select only works on Native using the Adapt component to adapt it to a Sheet. See below for docs.

API Reference

Select

Contains every component for the select:

PropTypeDefaultRequired
idstring--
sizeSizeTokens--
childrenReact.ReactNode--
valuestring--
defaultValuestring--
onValueChange(value: string) => void--
openboolean--
defaultOpenboolean--
onOpenChange(open: boolean) => void--
dirDirection--
namestring--
nativeNativeValue--
renderValue(value: string) => ReactNode--
lazyMountbooleanfalse-
zIndexnumber--

Select.Trigger

Extends ListItem to give sizing, icons, and more.

Select.Value

Extends Paragraph, adding:

PropTypeDefaultRequired
placeholderstring--

Select.Content

Main container for Select content, used to contain the up/down arrows, no API beyond children.

Select.ScrollUpButton

Inside Content first, displays when you can scroll up, stuck to the top.

Extends YStack.

Select.ScrollDownButton

Inside Content last, displays when you can scroll down, stuck to the bottom.

Extends YStack.

Select.Viewport

Extends ThemeableStack. Contains scrollable content items as children.

PropTypeDefaultRequired
disableScrollboolean--
unstyledboolean--

Make sure to not pass height prop as that is managed internally because of UX reasons and having a fixed height will break that behavior.

Select.Group

Extends YStack. Use only when grouping together items, alongside a Label as the first child.

Select.Label

Extends SizableText. Used to label Groups. Includes size-based padding and minHeight for consistent appearance with other Select items.

Select.Item

Extends ListItem. Used to add selectable values to the list. Must provide an index as React Native doesn't give any escape hatch for us to configure that automatically.

PropTypeDefaultRequired
indexnumber-
valuestring--

Select.ItemText

Extends Paragraph. Used inside Item to provide unselectable text that will show above once selected in the parent Select.

Select.Indicator

An animated indicator that highlights the currently focused item. Place it inside Select.Viewport to enable a smooth sliding highlight animation as users navigate through options.

<Select.Viewport>
  <Select.Indicator transition="quick" />
  <Select.Group>{/* items */}</Select.Group>
</Select.Viewport>

Use the transition prop to control the animation speed. You can use any animation name from your config like quick, quicker, or quickest.

By default, Select uses the item's hoverStyle and pressStyle for hover feedback. Add Select.Indicator for a smoother animated effect. If using the indicator, you may want to set items to have transparent hover styles to avoid visual conflict.

Select.FocusScope

Provides access to the underlying FocusScope component used by Select for focus management. Can be used to control focus behavior from a parent component.

PropTypeDefaultRequired
enabledbooleantrue-
loopbooleanfalse-
trappedbooleanfalse-
focusOnIdleboolean | numberfalse-
onMountAutoFocus(event: Event) => void--
onUnmountAutoFocus(event: Event) => void--

Performance

For pages with many Select components, you can significantly improve initial render performance by using the lazyMount prop combined with renderValue:

const labels = {
  apple: 'Apple',
  orange: 'Orange',
  banana: 'Banana',
}

<Select
  lazyMount
  renderValue={(value) => labels[value]}
  defaultValue="apple"
>
  {/* Items are not mounted until the Select is opened */}
  <Select.Trigger>
    <Select.Value />
  </Select.Trigger>
  <Select.Content>
    <Select.Viewport>
      {/* These items mount in a startTransition when first opened */}
      <Select.Item index={0} value="apple">
        <Select.ItemText>Apple</Select.ItemText>
      </Select.Item>
      {/* ... more items */}
    </Select.Viewport>
  </Select.Content>
</Select>

How it works:

  • lazyMount defers mounting all Select items until the dropdown is first opened
  • The mount happens inside React's startTransition, keeping the trigger responsive
  • renderValue provides the display text synchronously, avoiding the need to mount items just to show the selected value
  • Once mounted, items stay mounted for fast subsequent opens

This is especially useful when rendering many Selects on a single page, as each Select with 20+ items would otherwise mount all those items on initial page load.

Select.Sheet

When used alongside <Adapt />, Select will render as a sheet when that breakpoint is active.

This is the only way to render a Select on Native for now, as mobile apps tend to show Select very differently from web and Hanzo GUI wants to present the right abstractions for each platform.

See Sheet for more props.

Must use Select.Adapt.Contents inside the Select.Sheet.Frame to insert the contents given to Select.Content

import { Select } from '@hanzo/gui' // or '@hanzogui/select'

export default () => (
  <Select defaultValue="">
    <Select.Trigger>
      <Select.Value placeholder="Search..." />
    </Select.Trigger>

    <Adapt when="max-md" platform="touch">
      {/* or <Select.Sheet> */}
      <Sheet>
        <Sheet.Frame>
          <Adapt.Contents />
        </Sheet.Frame>
        <Sheet.Overlay />
      </Sheet>
    </Adapt>

    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
          </Select.Item>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton />
    </Select.Content>
  </Select>
)

Last updated on

On this page