Select

This page covers two components. Select renders its own listbox, so it can show things a native picker cannot, and drives the popup from script. NativeSelect keeps a real <select> and styles it with appearance: base-select, so the browser owns the popup, the keyboard behaviour and the placement.

Usage

The sections up to NativeSelect all describe Select.

Elderberry
Elderberry
<script lang="ts">
	import { Select } from 'noph-ui'
</script>

<Select
	label="Fruit"
	name="fruit"
	options={[
		{ value: '', label: '' },
		{ value: 'apple', label: 'Apple', selected: true },
		{ value: 'apricot', label: 'Apricot' },
		{ value: 'banana', label: 'Banana' },
		{ value: 'cherry', label: 'Cherry' },
		{ value: 'elderberry', label: 'Elderberry', disabled: true },
		{ value: 'fig', label: 'Fig' },
		{ value: 'grape', label: 'Grape' },
	]}
/>
<Select
	label="Fruit"
	variant="filled"
	name="fruit"
	options={[
		{ value: '', label: '', selected: true },
		{ value: 'apple', label: 'Apple' },
		{ value: 'apricot', label: 'Apricot' },
		{ value: 'banana', label: 'Banana' },
		{ value: 'cherry', label: 'Cherry' },
		{ value: 'elderberry', label: 'Elderberry', disabled: true },
		{ value: 'fig', label: 'Fig' },
		{ value: 'grape', label: 'Grape' },
	]}
/>

Disabled

<script lang="ts">
	import { Select } from 'noph-ui'
</script>

<Select label="Fruit" name="fruit" disabled options={[{ value: '', label: '' }]} />
<Select
	label="Fruit"
	variant="filled"
	name="fruit"
	disabled
	options={[{ value: '', label: '', selected: true }]}
/>

Validation

<script lang="ts">
	import { Button, Select } from 'noph-ui'
</script>

<form>
	<Select
		label="Genre"
		name="genre"
		required
		options={[
			{ value: '', label: '' },
			{ value: 'rock', label: 'Rock' },
			{ value: 'pop', label: 'Pop' },
			{ value: 'jazz', label: 'Jazz' },
		]}
	/>
	<div class="button-area">
		<Button type="submit">Send</Button>
	</div>
</form>

<style>
	.button-area {
		display: flex;
		justify-content: flex-end;
		margin-top: 1rem;
	}
</style>

Multiple selection

<script lang="ts">
	import { Select } from 'noph-ui'
</script>

<Select
	label="Favorite fruit"
	name="fruit"
	style="max-width: 300px"
	multiple
	options={[
		{ value: 'apple', label: 'Apple' },
		{ value: 'apricot', label: 'Apricot' },
		{ value: 'banana', label: 'Banana' },
		{ value: 'cherry', label: 'Cherry' },
		{ value: 'elderberry', label: 'Elderberry' },
		{ value: 'fig', label: 'Fig' },
	]}
/>

Virtual list

An option uses the Intersection Observer API to improve performance. This enables lists of up to 300 options. Beyond that the Select component uses a virtual list to render the options, which has the limitation of having a fixed height. The switchover point is virtualThreshold; lower it when your options are expensive to render.

<script lang="ts">
	import { Select } from 'noph-ui'
</script>

<Select
	label="Virtual list"
	name="virtual_list"
	style="max-width: 300px"
	options={[
		...Array.from({ length: 4001 }, (_, i) => ({
			value: `option${i + 1}`,
			label: `Option ${i + 1}`,
		})),
	]}
/>

Icons

<script lang="ts">
	import { Select } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'
</script>

<Select
	label="Favorite fruit"
	name="fruit"
	options={[
		{ value: '', label: '' },
		{ value: 'apple', label: 'Apple' },
		{ value: 'apricot', label: 'Apricot' },
		{ value: 'banana', label: 'Banana' },
	]}
>
	{#snippet start()}
		<Icon>favorite</Icon>
	{/snippet}
</Select>
<Select
	label="Favorite car"
	name="car"
	options={[
		{ value: '', label: '' },
		{ value: 'audi', label: 'Audi' },
		{ value: 'bmw', label: 'BMW' },
		{ value: 'mercedes', label: 'Mercedes' },
		{ value: 'vw', label: 'Volkswagen' },
	]}
>
	{#snippet end()}
		<Icon>favorite</Icon>
	{/snippet}
</Select>

NativeSelect

NativeSelect wraps a real <select> element styled with appearance: base-select, instead of building a custom listbox. Options are passed as children, either plain <option> elements or the Option component, rather than an options array.

An option with value="" is the placeholder: while it is the selected one the label stays inside the field instead of floating above it, so give it no text. Every other option floats the label, including the one the browser picks on its own when you pass no value - bind:value adopts that initial selection, so the label and the value the form submits always agree.

<script lang="ts">
	import { NativeSelect, Option } from 'noph-ui'
</script>

<NativeSelect label="Fruit" name="fruit">
	<Option value=""></Option>
	<Option value="apple">Apple</Option>
	<Option value="apricot">Apricot</Option>
	<Option value="banana">Banana</Option>
	<Option value="cherry">Cherry</Option>
	<Option value="elderberry" disabled>Elderberry</Option>
	<Option value="fig">Fig</Option>
	<Option value="grape">Grape</Option>
</NativeSelect>
<NativeSelect variant="filled" label="Fruit" name="fruit">
	<Option value=""></Option>
	<Option value="apple">Apple</Option>
	<Option value="apricot">Apricot</Option>
	<Option value="banana">Banana</Option>
	<Option value="cherry">Cherry</Option>
	<Option value="elderberry" disabled>Elderberry</Option>
	<Option value="fig">Fig</Option>
	<Option value="grape">Grape</Option>
</NativeSelect>

Disabled

<script lang="ts">
	import { NativeSelect, Option } from 'noph-ui'
</script>

<NativeSelect label="Fruit" name="fruit" disabled>
	<Option value="apple">Apple</Option>
</NativeSelect>
<NativeSelect variant="filled" label="Fruit" name="fruit" disabled>
	<Option value="apple">Apple</Option>
</NativeSelect>

Validation

<script lang="ts">
	import { Button, NativeSelect, Option } from 'noph-ui'
</script>

<form>
	<NativeSelect label="Genre" name="genre" required>
		<Option value=""></Option>
		<Option value="rock">Rock</Option>
		<Option value="pop">Pop</Option>
		<Option value="jazz">Jazz</Option>
	</NativeSelect>
	<div class="button-area">
		<Button type="submit">Send</Button>
	</div>
</form>

<style>
	.button-area {
		display: flex;
		justify-content: flex-end;
		margin-top: 1rem;
	}
</style>

Theming

Filled select tokens

TokenDefault value
--np-filled-select-text-field-container-shape--np-shape-corner-extra-small
--np-filled-select-text-field-container-color--np-color-surface-container-highest

Filled select example

<script lang="ts">
	import { Select } from 'noph-ui'
</script>

<Select
	variant="filled"
	label="Filled"
	options={[
		{ value: '', label: '', selected: true },
		{ value: 'apple', label: 'Apple' },
		{ value: 'apricot', label: 'Apricot' },
		{ value: 'banana', label: 'Banana' },
	]}
	--np-filled-select-text-field-container-shape="0"
	--np-filled-select-text-field-container-color="var(--np-color-surface-container)"
	--np-color-primary="var(--np-color-tertiary)"
/>

Outlined select tokens

TokenDefault value
--np-outlined-select-text-field-container-shape--np-shape-corner-extra-small

Outlined select example

<script lang="ts">
	import { Select } from 'noph-ui'
</script>

<Select
	options={[
		{ value: '', label: '', selected: true },
		{ value: 'apple', label: 'Apple' },
		{ value: 'apricot', label: 'Apricot' },
		{ value: 'banana', label: 'Banana' },
	]}
	label="Outlined"
	--np-outlined-select-text-field-container-shape="0"
	--np-color-primary="var(--np-color-tertiary)"
/>

Sizing tokens

These apply to both variants and control how wide the select is allowed to get.

TokenDefault value
--np-select-min-width210px
--np-select-max-width100%

These tokens apply to NativeSelect as well.

Accessibility

Select renders the field as a role="combobox" with aria-expanded, aria-controls and aria-activedescendant, and the popup as a role="listbox" whose options carry aria-selected. Arrow keys, Home, End, typeahead, Enter and Escape are wired to that, and the field is named by label.

Supporting text is tied to the field through aria-describedby, and an error from issues is announced through role="alert". Where none of the extras are needed, NativeSelect keeps a real <select>, which brings the platform picker with it, mobile included.

API

Select attributes

AttributeTypeDefaultDescription
optionsSelectOption[][]Required. The options to choose from. See the table below for their shape.
variant'outlined' | 'filled''outlined'Defines the visual style of the select component.
labelstring | undefinedundefinedSpecifies the floating label for the select field.
startSnippet | undefinedundefinedIcon displayed at the beginning of the select field.
endSnippet | undefinedundefinedReplaces the arrow
supportingTextstring''Provides additional information below the select, such as usage instructions.
issues{ message: string }[]undefinedDisplays error messages below the select. Optimized to use with remote form field issues.
requiredbooleanfalseIndicates whether the select field is required.
noAsteriskbooleanfalseDisables the asterisk on the floating label when the select field is required.
disabledbooleanfalseIndicates whether the select field is disabled.
multipleboolean | null | undefinedundefinedAllows multiple selections when set to true.
clampMenuWidthbooleanfalseRestricts the menu width to match the width of the select component.
virtualThresholdnumber300Number of options from which on the menu renders as a virtual list.
...attributesHTMLAttributes<HTMLDivElement>Attributes are passed to the component container.

SelectOption

PropertyTypeDescription
valuestring | numberRequired. Value submitted with the form.
labelstringRequired. Text shown for the option.
selectedboolean | null | undefinedPreselects the option, and is the state a form reset returns it to.
disabledboolean | undefinedMakes the option non-selectable.

Select bindables

AttributeTypeDescription
elementHTMLElementA reference to the root DOM element of the component. This variable is bound using bind:this, allowing direct access to the underlying HTML element for manipulation or querying within the component's logic.
valueanyValue of the underlying select

NativeSelect attributes

AttributeTypeDefaultDescription
variant'outlined' | 'filled''outlined'Defines the visual style of the select component.
labelstring | undefinedundefinedSpecifies the floating label for the select field.
supportingTextstring | undefinedundefinedProvides additional information below the select, such as usage instructions.
issues{ message: string }[] | undefinedundefinedDisplays error messages below the select. Optimized to use with remote form field issues.
requiredbooleanfalseIndicates whether the select field is required.
noAsteriskbooleanfalseDisables the asterisk on the floating label when the select field is required.
disabledbooleanfalseIndicates whether the select field is disabled.
idstring | undefinedundefinedId of the underlying <select>. Auto-generated when omitted.
childrenSnippet | undefinedundefinedThe <option> / Option elements to render inside the select.
...attributesHTMLSelectAttributesThe rest, such as name, form, and autofocus, are forwarded to the underlying <select>.

NativeSelect bindables

AttributeTypeDescription
valueanyValue of the underlying select.