Select
A real <select>, opted into the stylable form with appearance: base-select.
The listbox, the keyboard, typeahead, form participation and the mobile picker
are the platform's.
import { Select } from '@apostel/bedrock'
Sourceselect.tsx
import { useState } from 'react'
import { Select } from '../../src/index'
/**
* A real `<select>`. On a browser with `appearance: base-select` the trigger
* and the list are styleable in place; everywhere else it falls back to the
* platform control, which is a downgrade in looks and not in function.
*
* That is the whole component: 0.84 kB against Radix's 31.5.
*/
export default function SelectDemo() {
const [value, setValue] = useState('medium')
return (
<>
<Select.Root value={value} onValueChange={setValue} aria-label="Size">
<Select.Trigger>
<Select.Value />
</Select.Trigger>
<Select.Item value="small">Small</Select.Item>
<Select.Item value="medium">Medium</Select.Item>
<Select.Item value="large">Large</Select.Item>
</Select.Root>
<output>value: {value}</output>
</>
)
}This is the largest single saving in the library: 0.84 kB against Radix's 31.5 kB, because almost all of it is an element that already exists.
Anatomy
<Select.Root>
<Select.Trigger>
<Select.Value />
</Select.Trigger>
<Select.Item />
<Select.Group />
<Select.Separator />
</Select.Root>
Select.Content exists and is optional; items may sit directly inside the
root, which is what a <select> expects.
Select.Root
Renders <select data-bedrock-select>. Takes every <select> prop.
| prop | type | notes |
|---|---|---|
value |
string |
Controlled. |
defaultValue |
string |
Uncontrolled. |
onValueChange |
(value: string) => void |
Alongside onChange. |
asChild |
boolean |
Supported. |
Because it is a
<select>,name,required,disabledandformdo what they say, and the control submits with the form without a hidden input.
The other parts
| part | renders | notes |
|---|---|---|
Trigger |
<button> |
Part of the stylable-select markup Chrome added, not a bedrock invention. |
Value |
<selectedcontent> |
The same. |
Item |
<option> |
Takes a required value. |
ItemText |
<span> |
Inside the option. |
ItemIndicator |
nothing | Style option::checkmark instead. Warns in development if given a className. |
Group |
<optgroup> |
Valid inside a <select> in the stylable form. |
Separator |
<hr> |
The same. |
Degrading
Where appearance: base-select is missing, the control falls back to the
platform select: the native dropdown, unstyled by your CSS.
That is a downgrade in looks, not in function: every option is still reachable, still typeahead-searchable, still submits. See browser support for the current state.
This is the one primitive where the visual gap between engines is large, and it is worth deciding deliberately whether that is acceptable for your product before adopting it. See should you switch?.
What is not here
- Arbitrary markup in an option. A
<select>restricts what may go inside it, and stylable select loosens that but does not remove it. - Multi-select as a separate component. Set
multipleon the root; it is a<select>. - A JavaScript-positioned listbox. The browser positions it, including on mobile where it is a native picker.