Select field
One choice from a list that opens on click.
Installation
npx shadcn@latest add @formdef/select-fieldRegister it under widgets in lib/formdef/index.tsx, as widgets: { select: SelectField }. The
widget serves string values.
Usage
export const = createForm({
: .({ : .().(1, 'Choose a country') }),
: { : '' },
fields: {
country: {
: 'select',
: {
: 'Country',
: 'Choose a country',
: [
{ : 'at', : 'Austria' },
{ : 'de', : 'Germany' },
],
},
},
},
})'' stands for no choice, and the trigger shows the placeholder then. The schema decides whether
no choice is allowed; here min(1) rejects it.
A value the options do not carry stays in the form: the widget never clears what it cannot show. This keeps a stored value intact while its option is loading or has been removed.
To let people type to find an option in a long list, take the combobox field.
Props
Prop
Type
Source
import { type FieldOf, fieldParts } from '@formdef/core'
import type { ComponentProps } from 'react'
import { FieldFrame } from '@/components/formdef/field-frame'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import type { FieldLabels } from '@/lib/formdef/field-labels'
import type { Option } from '@/lib/formdef/options'
type Attrs = Pick<ComponentProps<typeof Select>, 'disabled' | 'readOnly' | 'required'> &
Pick<ComponentProps<typeof SelectTrigger>, 'className'>
type SelectFieldProps = FieldOf<string> &
FieldLabels &
Attrs & {
options: Option[]
placeholder?: string | undefined
}
export const SelectField = ({
field,
label,
labelAside,
description,
options,
placeholder,
className,
...attrs
}: SelectFieldProps) => {
const parts = fieldParts(field, { description })
const { name: _carriedByTheForm, ...trigger } = parts.control
const value = field.state.value ?? ''
return (
<FieldFrame
parts={parts}
label={label}
labelAside={labelAside}
description={description}
disabled={attrs.disabled}
>
<Select
{...attrs}
items={options}
value={value}
onValueChange={(chosen) => field.handleChange(chosen ?? '')}
>
<SelectTrigger {...trigger} className={className}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{options.map((option) => (
<SelectItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</FieldFrame>
)
}