remix/ui/select · DemoView Source
Select Deconstructed
Build a fully custom select by composing the select, popover, and listbox primitives directly.
Environment
Local
Staging
Production
Archived
value=local
import { css, type Handle } from 'remix/ui'
import * as button from 'remix/ui/button'
import { Glyph } from 'remix/ui/glyph'
import * as listbox from 'remix/ui/listbox'
import * as popover from 'remix/ui/popover'
import * as select from 'remix/ui/select'
import { theme } from 'remix/ui/theme'
export default function Example(handle: Handle) {
let label = 'Local'
let value = 'local'
let triggerId = `${handle.id}-trigger`
return () => (
<div
mix={[
stackCss,
select.onSelectChange((event) => {
label = event.label ?? 'Select an environment'
value = event.value ?? 'null'
void handle.update()
}),
]}
>
<p mix={labelCss}>Environment</p>
<select.Context
defaultLabel="Local"
defaultValue="local"
name="environment"
>
<button
id={triggerId}
type="button"
mix={[
button.baseStyle,
select.triggerStyle,
select.trigger(),
selectCss,
]}
>
<span mix={button.labelStyle}>{label}</span>
<Glyph mix={button.iconStyle} name="chevronVertical" />
</button>
<popover.Context>
<div mix={[popover.surfaceStyle, select.popover()]}>
<div
aria-labelledby={triggerId}
mix={[popover.contentStyle, listbox.listStyle, select.list()]}
>
{environmentOptions.map((option) => (
<div
key={option.value}
mix={[listbox.optionStyle, select.option(option)]}
>
<Glyph mix={listbox.glyphStyle} name="check" />
<span mix={listbox.labelStyle}>{option.label}</span>
</div>
))}
</div>
</div>
</popover.Context>
<input mix={select.hiddenInput()} />
</select.Context>
<p mix={valueCss}>{`value=${value}`}</p>
</div>
)
}
const environmentOptions = [
{ label: 'Local', value: 'local' },
{ label: 'Staging', value: 'staging' },
{ label: 'Production', value: 'production' },
{ disabled: true, label: 'Archived', value: 'archived' },
] as const
const selectCss = css({
width: '16rem',
})
const stackCss = css({
display: 'flex',
flexDirection: 'column',
gap: theme.space.sm,
width: '100%',
})
const labelCss = css({
margin: 0,
fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.semibold,
color: theme.colors.text.primary,
})
const valueCss = css({
margin: 0,
fontFamily: theme.fontFamily.mono,
fontSize: theme.fontSize.xs,
color: theme.colors.text.secondary,
})