Skip to content

Select

Choose one option from a popup list.

import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use Select for one value from a list, such as a region or role. For a short list, a Radio Group keeps every option visible. Use a Command list when people need to search.

In the Next.js App Router, render this from a Client Component, a file that starts with 'use client': it takes functions (onValueChange), and a Server Component can't pass those. Server rendering has the rest.

import { Field, FieldLabel, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@youj/react'

const roles = [
  { value: 'admin', label: 'Admin' },
  { value: 'member', label: 'Member' },
  { value: 'viewer', label: 'Viewer' },
]

export function RoleSelect() {
  return (
    <Field>
      <FieldLabel>Role</FieldLabel>
      <Select items={roles} defaultValue="member" name="role">
        <SelectTrigger className="w-full">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          {roles.map((role) => (
            <SelectItem key={role.value} value={role.value}>
              {role.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </Field>
  )
}

Pass items, either an array of { value, label } or an object that maps values to labels, so the trigger can show the chosen label before the popup has ever opened. Without it, the labels are read from the SelectItems written inside the Select. Items that a component of your own renders can't be read, so there SelectValue would show the raw value, and development warns once to pass items.

Reference

Propssize, presentation (2)

Props

Select

Select adds no props of its own. They pass through to SelectPrimitive.Root.Props<Value, Multiple>.

It takes Base UI's items, value, defaultValue, onValueChange, open, onOpenChange, multiple, disabled, readOnly, required and name. The required default comes from the surrounding Field.

SelectTrigger

SelectTrigger props
PropType
size'sm' | 'default'

default 'default'

'default'
variant'default' | 'ghost' | 'soft'

ghost: no field fill or edge, a hover step instead, at the button corner: a select that sits in a toolbar or composer as a quiet button (a model or sort picker). soft: no edge, filled with the theme's quiet band (a hover step under the pointer) at the field's own corner and height: a picker inside a card, which already frames it. The fill is a little under 3:1 against the card, so the chevron and the value identify the control, not an edge; keep the default where the select must read on its own.

default 'default'

'default'
hideIconboolean

Leaves out the chevron, for a dense row of selects (a table cell, a property list) where the value alone shows it opens. The trigger keeps its role, name and aria-expanded.

default false

false

SelectContent

SelectContent props
PropType
presentationOverlayPresentation
'auto''sheet''popover'

auto (the default) is a popover under a pointer and a bottom sheet on touch; force either one.

default 'auto'

'auto'

The popup's position comes from side, sideOffset, align, alignOffset and alignItemWithTrigger, which default to bottom, 6, center, 0 and true.

MotionOpen, open over the trigger, close… · reduced motion

Motion

Motion tokens
MomentTokens
Openscales from 96% and fades in, from its transform origin
duration.fastease.enter

140ms · cubic-bezier(0.16, 1, 0.3, 1)

140mscubic-bezier(0.16, 1, 0.3, 1)
Open over the triggerfades only
duration.fastease.enter

140ms · cubic-bezier(0.16, 1, 0.3, 1)

140mscubic-bezier(0.16, 1, 0.3, 1)
Close
duration.instantease.exit

80ms · cubic-bezier(0.4, 0, 1, 1)

80mscubic-bezier(0.4, 0, 1, 1)
Chevronturns over while open
spring.snappy

0.2s, bounce 0

0.2s, bounce 0
Scroll arrowsfade in and out on long lists
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

The popup appears and closes at once, and the chevron flips without turning.

The popup wears motion's yj-pop preset. When it opens lined up over the trigger, it only fades: growing out of the option you're already looking at reads as a jump.

Reduced motion

The popup opens and closes without scaling or fading, and the chevron flips straight to its new direction. The highlighted and chosen options still show the same way.

AccessibilityKeyboard, ARIA, focus

Accessibility

  • The trigger is a combobox that opens a listbox. Space, Enter and ArrowDown open it, and typing jumps to a matching option.
  • In the popup, the arrow keys, Home and End move the highlight, Enter chooses, and Escape closes and returns focus to the trigger.
  • A FieldLabel names the trigger, and a FieldDescription or FieldError describes it. Outside a Field, give the trigger an aria-label.
  • A FieldError renders as an alert, so it's announced when it appears.