UISelect
is a native
select
with
appearance: base-select: the browser brings the list, the keyboard and the form value, and UI paints it with its tokens.
UICombobox
covers what the native one cannot: searching, filtering on a server and choosing several options. The type of the model is inferred from
options.
One option in a form
With
name, a native form sends the value without extra code. Open the list with
Enter,
Space
or a click and type a letter to jump to the option that starts with it: it is the keyboard of the browser.
<script lang="ts">
const plans = ['Personal', 'Team', 'Business', 'Education']
const topics = ['Accessibility', 'Design', 'Documentation', 'Performance']
const plan = signal<string | null>(null)
const chosen = signal<string[]>([])
</script>
<template>
<form method="post" action="/subscriptions">
<label for="plan">Plan</label>
<UISelect id="plan" model="plan" bind:options="plans"
name="plan" placeholder="Choose a plan" required />
<label for="topics">Topics</label>
<UICombobox id="topics" model="chosen" bind:options="topics"
name="topics" multiple searchable bind:max="2" />
<UIButton type="submit">Send selection</UIButton>
</form>
</template>required, a form without a value is not sent: the
select
receives the focus and
aria-invalid, without the browser message. Write a visible message too with
UIField, as in the example. With
useForm, pass
bind:field="form.field('plan')"
and the field brings the id, the name, the value and the error state.
Browsers
Chrome, Edge and Safari 27 paint the list with the tokens of UI, with an animated entrance. Firefox and older browsers show their own list: they work the same, and there
value,
group
and the rich content of
option
stay as their text. A hidden option at the start stands for the
placeholder, or for a value that is no longer in
options: the model keeps it and the form keeps sending it.
Objects, groups and custom options
Options can be objects.
by
decides how they are compared,
labelBy
which text is shown and searched,
groupBy
adds a heading per group and an option with
disabled: true
cannot be chosen. The
option
and
value
snippets receive the option with its type.
No owner assigned.
<script lang="ts">
interface Member { id: number; name: string; team: string; role: string; disabled?: boolean }
const members: Member[] = [
{ id: 1, name: 'Lucía Martín', team: 'Design', role: 'Art director' },
{ id: 4, name: 'Mateo Gil', team: 'Engineering', role: 'On vacation', disabled: true },
]
const owner = signal<Member | null>(null)
</script>
<template>
<UISelect model="owner" bind:options="members" by="id"
labelBy="name" groupBy="team" label="Owner" clearable>
<snippet name="option" params="member, state">
<strong>{member.name}</strong>
<small>{state.disabled ? 'Unavailable' : member.role}</small>
</snippet>
</UISelect>
</template>UISelect API
| Name | Type / default | Description |
|---|---|---|
options
|
readonly T[]
| Required. Primitives or objects. |
model
|
Signal<T | null>
null
| Chosen value. |
by / labelBy / disabledBy / groupBy
|
key or function
| Comparison, label, disabled state and group of each option. |
clearable / clearLabel
|
boolean / string
false / Clear selection
| Accessible button that clears the selection and returns focus to the select. The default label follows the language of the document (see Theming). |
name / required / disabled
|
string / boolean
| Form submission and validation. |
field
|
SelectField
| A useForm field: id, name, value, aria-invalid and aria-describedby. |
id / label / describedBy / invalid
|
string / boolean
| Identity and accessible state of the select. |
placeholder
|
string
| Text without a value. |
placement / offset
|
PopoverPlacement / number
bottom-start
| Position of the list, where the browser paints it with the tokens. |
class / listClass
|
string
| Classes of the root and of the select; style the list with .my-class::picker(select). |
It accepts the
option,
value
and
group
snippets, and the
change
event.
multiple,
searchable,
filter
and
max
are a type error that points to
UICombobox.
UICombobox
A list of its own on top of
UIPopover, with more JavaScript: use it only if you need
searchable,
filter,
multiple
or
max. The focus stays on the trigger while
aria-activedescendant
walks the list. The Topics field of the first example is a
UICombobox. With
multiple, each option toggles, the list stays open and the model is an array;
max
disables the rest at the limit.
<UICombobox id="topics" model="chosen" bind:options="topics"
name="topics" multiple searchable bind:max="2" placeholder="Search for a topic" />
To search on a server, pass a
filter
function that returns a promise and uses its
AbortSignal. Without results it shows
empty: by default,
No results.
| Name | Type / default | Description |
|---|---|---|
model
|
Signal<T | null> or Signal<T[]>
null
| Chosen value; an array with multiple. |
multiple
|
boolean
false
| Allows choosing several options. |
searchable / filter
|
boolean / ComboboxFilter<T>
| Search in the list, synchronous or asynchronous. |
max
|
number
| With multiple, maximum number of chosen options. |
The other props are those of
UISelect. It also accepts the
empty
and
loading
snippets. The types
UISelectProps,
UIComboboxProps,
SelectModel
and
ComboboxFilter
are exported from
@meekojs/ui/runtime.