Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

Select

A trigger that shows the chosen value and opens a list to pick another, with an optional footer for custom values.

import { Select } from "@butteryui/react";

Demo

When to use it

Use it for

  • A value a control shows and keeps, picked from a list: a canvas shape, an export format
  • A list of presets with a custom value beside them, through footer
  • A form field: inside a Field, which shows the label (label alone names it for screen readers), with variant="outline", or variant="soft" beside soft text fields

Not for

  • Two to four options that fit side by side: use SegmentedControl
  • Actions rather than a value: use Menu
  • A choice in a form that waits for submit with many options: a native select styled with bt-control

Examples

Canvas size

Preset shapes with width and height fields underneath; typing a size makes it custom.

Deck font

A select that keeps its own value, starting on the deck's sans font; the trigger shows each font picked.

Export format

A small select for an export format, typed to its values, with the file name it leads to.

Saves as title-slide.png

Variants and sizes

The ghost pill for toolbars, and the outline and soft boxes for forms, at their medium and small sizes.

Variants and states

Canvas shape

A ghost pill showing the value; the list lines up with its end and checks the chosen option.

Width and height fields under the list for a custom size; Tab moves between the list and the fields.

Small, placeholder and disabled

Outline and soft boxes

For forms, a box like a text field's that fills its column, at 34px or 28px: outline with a 1px edge, or soft on bg-control, its edge showing on hover, while it has focus and while its list is open.

Props

Select

PropTypeDefaultDescription
label

required

stringWhat the value is, such as "Canvas shape". It names the trigger, the list and the footer for screen readers and isn't shown; a Field around the select shows its label above the box.
options

required

readonly SelectOption<Value>[]The choices, in order.
valueValueThe chosen value. Pair with onValueChange to control it.
defaultValueValueThe value chosen at first when uncontrolled.
onValueChange((value: Value) => void)Called with the value of the option chosen.
openbooleanWhether the list is open. Pair with onOpenChange to control it.
defaultOpenbooleanfalseWhether the list starts open when uncontrolled. Defaults to false.
onOpenChange((open: boolean) => void)Called when the list opens or closes.
valueLabelstringWhat the trigger shows instead of the chosen option's label, such as a custom size from the footer.
placeholderstring"Choose"What the trigger shows while nothing is chosen. Defaults to "Choose".
footerReactNodeControls under the list, such as width and height fields for a custom value.
footerLabelstringlabelNames the footer for screen readers. Defaults to label.
align"start" | "center" | "end""end"Which edge of the trigger the list lines up with. Defaults to "end", or "start" for a box.
variant"ghost" | "outline" | "soft""ghost"How the trigger looks: "ghost" (the default), a pill for header bars and toolbars; "outline", a box like a text field's that fills its column, for forms; or "soft", the same box on bg-control, its edge showing only on hover, while it has focus and while the list is open. A box's list is at least as wide as the box.
size"sm" | "md""md"The trigger's height: "sm" (28px) or "md" (36px as a pill, 34px as a box). Defaults to "md".

It also takes Omit<ComponentPropsWithRef<"button">, "value" | "defaultValue" | "onChange" | "children">.

SelectOption

One choice in a `Select`.

FieldTypeDefaultDescription
value

required

ValueWhat choosing it sets the select's value to.
label

required

stringWhat the list and, once chosen, the trigger show.
disabledbooleanKeeps it from being chosen; arrow keys skip it.

Accessibility

The trigger is a select-only combobox, a button with role="combobox", named by the label and the value ("Canvas shape, 16:9"), with aria-haspopup="listbox", aria-expanded and aria-controls; in a required Field it has aria-required too. The list is a listbox of options with aria-selected; it opens with focus on the chosen option. Arrow keys, Home and End move, typing jumps, Enter or Space chooses, Escape closes, and focus goes back to the trigger as it closes. A footer is a group named by footerLabel, and Tab loops between the list and its controls.

KeysWhat they do
Enter, Space, Arrow Down or Arrow UpOpens the list from the trigger, with focus on the chosen option, or the first when none is chosen
Arrow Down or Arrow UpMoves to the next or previous option, skipping disabled ones
Home or EndMoves to the first or last option
LettersJumps to the next option that starts with them; letters typed within half a second search as one
Enter or SpaceChooses the focused option, closes the list and puts focus back on the trigger
EscapeCloses the list without choosing and puts focus back on the trigger
TabMoves between the list and the footer's controls when there's a footer, and stays on the list when there isn't

Motion

It appears with a `Presence` preset and leaves faster than it came, in about 60% of the time.

With reduced motion: The list fades in and out over 100ms, without growing from the trigger.

Layout

The ghost trigger is a pill --bt-h-button (36px) tall, or --bt-h-sm (28px) with size="sm", as wide as its value and chevron; the outline and soft boxes are --bt-h-control (34px) or --bt-h-sm tall and fill their column, a soft box keeping its edge transparent until hover, focus or its list opens. The value truncates with an ellipsis and shows in full as its title, and the whole trigger is the hit area. The list is at least --bt-w-menu-min (200px) wide, and a box's list at least as wide as the box; its options are --bt-h-button tall with labels that truncate, and it scrolls inside when the window leaves it less room than it needs.

See it in the Halfmoon shop demo. See it in the Deckhand demo.