Edit Profile

Dark Mode

Copy config

Copy and paste the following code into your global.css file to apply the styles.

Select

A control that allows the user to select multiple predefined items from a listbox.

Logged in users

✨ Features

  • WAI ARIA Listbox design pattern
  • Single and multiple selection
  • Reactive and initial value changes
  • Disabled items
  • Tab key focus management
  • Grouped items
  • Looping
  • Custom scroll behavior
  • Listbox UI above all (Popover)
  • Custom positioning (Popover)
  • Typeahead item selection and focus
  • Arrow key navigation and focus management
  • Custom placeholder
  • Roadmap

  • Opt-in native form support via a visually hidden select
  • RTL support

Building blocks

import { component$ } from '@builder.io/qwik';
import { Select } from '@qwik-ui/headless';

export default component$(() => {
  const users = ['Tim', 'Ryan', 'Jim', 'Jessie', 'Abby'];

  return (
    <Select.Root>
      <Select.Label>label</Select.Label>
      <Select.Trigger>
        <Select.DisplayValue placeholder="Select an option" />
      </Select.Trigger>
      <Select.Popover>
        {users.map((user) => (
          <Select.Item key={user}>
            <Select.ItemLabel>{user}</Select.ItemLabel>
            <Select.ItemIndicator>{/* Icon */}</Select.ItemIndicator>
          </Select.Item>
        ))}
      </Select.Popover>
    </Select.Root>
  );
});

🎨 Anatomy

ComponentDescription
Select.Root

The root container for the Select component.

Select.Label

A label that computes the accessible name of the select. If not present, the display value is used instead.

Select.Trigger

A button or similar control that opens the select menu when interacted with.

Select.DisplayValue

Displays the currently selected value.

Select.Popover

A wrapper of the Qwik UI Popover component. It places its content above everything else.

Select.Listbox

A list that displays selectable items.

Select.Item

A selectable item within the Select.Listbox.

Select.ItemLabel

The item's label. If there is no value prop, this is the item's value.

Select.ItemIndicator

Allows you to render an icon or other visual element that is displayed next to the item's label whenever an item is selected.

Select.Group

Groups related items within the Select.Listbox.

Select.GroupLabel

A label that computes the accessible name of the group.

Why use a headless Select?

The native <select> HTML element encounters significant obstacles in terms of styling flexibility, consistency across different browsers, and delivering a seamless user experience.

Native Select pain points

  • Styling Limitations
  • Multiselect Complexity
  • Limited Typeahead Support
  • Inconsistent Behavior across browsers and devices

Native effort

The Open UI group is creating a stylable select element to address similar use cases as headless components.

However, since it is built on the existing native select element, its customizability and out-of-the-box experience may be limited, even with upcoming enhancements.

Passing data

To add data, use the <Select.Item> component inside of the listbox.

Basic example

Logged in users

By default, the content inside of the <Select.ItemLabel /> component is the item's value.

Mapping over data

Logged in users

You control how the data is rendered. Map over the data or render the items as you like.

Object example

Logged in users

Passing a distinct value

Logged in users

The selected value is: null

A distinct value is when one thing is displayed to the user, but another value is passed to the item.

By adding the value prop to the <Select.Item /> component, a distinct value is created.

Handling selection changes

Logged in users

You have changed 0 times

Use the onChange$ prop to listen for changes in the selected value. It provides the new selected value as an argument.

The example above increments a count when a new item is selected.

Forms

To use the select in a form, we provide a visually hidden native select element inside of <Select.HiddenNativeSelect>.

Logged in users

The native select element is created when a form name has been given to <Select.Root />

Validation

Logged in users

The <Select.ErrorMessage /> component is used to display errors when the select is invalid.

To style based on the invalid state, use the data-invalid data attribute.

Descriptions

Provide more information to assistive technologies by adding a description to the select.

Logged in users
Select a user to see their profile

Component state

We can select an initial uncontrolled value by passing the value prop to the <Select.Root /> component.

Uncontrolled / Initial value

Logged in users

The above example passes one of the item values "Jessie" as the initial value. As a result, the matching value is selected and focused.

Controlled / Reactive value

We can pass reactive state by using the bind:value prop to the <Select.Root /> component.

Logged in users

Your favorite user is: Ryan

bind:value is a signal prop, it allows us to reactively control the selected value of the select component.

We can also reactively control the open state of the select component by using the bind:open signal prop.

Logged in users

Programmatic changes

We can also programmatically change the selected value by changing the value of the signal.

Logged in users

In the above example, we've changed the selected value by clicking on the "Change to Abby" button.

Disabled items

This one is the disabled

Logged in users

Items can be disabled by adding the disabled prop to the <Select.Item /> component.

Disabled items are not selectable or focusable. They are also skipped when using the arrow keys to navigate through the items.

Dynamically adding users

A common use case is the addition of items dynamically. For example, an infinite scrolling list of users.

Logged in users

Clicking the Add Users button adds a couple new users mapped to the list. Taking this further, we could grab more data from the server and add it to the list, or even hitting a database to get more users.

Item Indicators

Logged in users

We can add an indicator to the selected item by adding the <Select.ItemIndicator /> component inside the <Select.Item /> component.

This is useful for indicating the selected item in a list of items.

Multiple selections

Sometimes we want to allow the user to select multiple pre-defined items at a time. We can do that by setting the multiple prop to true.

If we want to configure the multiple display values, we can use the bind:displayValue signal.

Something to be aware of, is the <Select.DisplayValue /> component will now display whatever you put inside of it.

Taking this a step further, one could combine the bind:value and bind:displayValue props together to create a custom widget that displays the selected values in a list of pills.

Logged in users

The above example shows the selected values which can be filtered by clicking on the cancel icon.

Typeahead

The select offers a typeahead feature that allows users to quickly find items by typing.

Logged in users

It reduces the need to scroll through the available items. Typeahead is particularly handy for expected data sets, such as a list of countries.

Handling listbox open / close

We may want to handle the open / close of the listbox. For example, we may want to show a loading indicator when the listbox is open.

It is currently: closed
Logged in users

The listbox opened and closed 0 time(s)

To do that, we can use the onOpenChange$ prop. A parameter is passed to the handler, which is a boolean indicating whether the listbox is open or closed.

Looping

To loop through the items, we can use the loop boolean prop on the <Select.Root /> component.

Logged in users
  • Pressing the down arrow key will move focus to the first item in the list.

  • Pressing the up arrow key will move focus to the last item in the list.

Grouped items

The <Select.Group /> and <Select.GroupLabel /> components are used to group and provide an accessible name to the Grouped items.

Wrap the items in a group, add a Label, and you're good to go!

Scrolling

Because focus remains on the select trigger when the listbox is open, it's important to handle scrolling in the listbox.

The native scrollIntoView method is used to scroll the itemss into view when the user highlights an items.

To customize the scroll behavior, add the scrollOptions prop to the <Select.Root /> component.

Placeholder

We can provide a custom placeholder to the <Select.DisplayValue /> component by adding the placeholder prop.

Logged in users

When a value is not selected, the placeholder is displayed.

CSR

The select component can be rendered both server-side or client-side, same with the rest of the components.

Example CSS

Every code example uses the following CSS:

:root {
  --select-width: 14rem;
}

.select {
  min-width: var(--select-width);
}

.select-trigger {
  width: 100%;
  height: 100%;
  border: 2px dotted hsla(var(--primary) / 1);
  min-height: 44px;
  max-width: var(--select-width);
  padding-block: 0.5rem;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 0.25rem;
}

.select-trigger:hover {
  background-color: hsla(var(--primary) / 0.08);
}

.select-trigger:focus-visible {
  outline: 2px solid hsla(var(--primary) / 1);
  outline-offset: 2px;
}

.select-trigger[data-invalid] {
  border: 2px dotted #d2122e;
}

.select-popover {
  width: 100%;
  max-width: calc(var(--select-width));
  background-color: hsl(var(--background));
  padding: 0.5rem;
  border: 2px dotted hsla(var(--foreground) / 0.6);
  border-radius: calc(var(--border-radius) / 2);
  color: hsl(var(--foreground));
}

.select-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.select-item span {
  user-select: none;
}

.select-item svg {
  margin-inline-end: 0.25rem;
}

.select-group-label {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: hsla(var(--foreground) / 0.8);
  padding-top: 0.5rem;
}

.select-max-height {
  max-height: 15rem;
  overflow-y: auto;
}

.select-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin-inline: 0.25rem;
  padding: 0.25rem 0.5rem;
  background-color: hsl(var(--muted));
  margin-block: 0.25rem;
}

.select-pill > span {
  padding: 0.15rem;
  background: hsl(var(--foreground) / 0.3);
  border-radius: 50%;
}

.select-pill svg {
  stroke: white;
}

[data-highlighted] {
  background-color: hsla(var(--primary) / 0.08);
  outline: 2px dotted hsla(var(--primary) / 1);
}

[data-disabled] {
  opacity: 0.6;
  background: hsl(var(--foreground) / 0.05);
}

Some CSS variables are specific to the docs, feel free to plug in your own values or variables!

Keyboard Interaction

Key

Description

Space
Opens the menu and selects a highlighted item.
Enter
Opens the menu and selects a highlighted item.
ArrowDown
Opens the menu or moves focus down.
ArrowUp
Opens the menu or moves focus up.
Home
When focus is on an item, moves focus to first item.
End
When focus is on an item, moves focus to last item.
ArrowRight
When focus is on the trigger, change the selection to the next item.
ArrowLeft
When focus is on the trigger, change the selection to the previous item.
Esc
Closes the select and moves focus to the trigger.

Multi Select

When in multi select mode, additional keyboard interactions are available.

Key

Description

Space
Toggles the selection of the highlighted item without closing the menu.
Enter
Toggles the selection of the highlighted item without closing the menu.
Shift + ArrowDown
Extends the selection from the current focus to the next item.
Shift + ArrowUp
Extends the selection from the current focus to the previous item.
Ctrl + A
Selects all items in the list.
Esc
Closes the menu and moves focus back to the trigger.

API

Data Attributes

Select, Select.Trigger, Select.DisplayValue, Select.Popover, and Select.Listbox all have data attributes that are used to track state.

AttributeDescription
data-open

If the listbox is open (Boolean).

data-closed

If the listbox is closed (Boolean).

Select.Item has the following data attributes:

AttributeDescription
data-selected

If the item is selected. (Boolean)

data-highlighted

If the item is highlighted. (Boolean)

data-disabled

If item is disabled. (Boolean)

Select.Root

PropTypeDescription
value
string

Uncontrolled select value.

bind:value
signal

Controlled selected value, manages the selected item.

bind:displayValue
signal

Controlled display value, manages the display items.

onChange$
QRL

Function called when the selected value changes.

onOpenChange$
QRL

Function called when the listbox opens or closes.

loop
boolean

Determines if focus cycles from the last item back to the first, or vice versa.

Select.DisplayValue

PropTypeDescription
placeholder
string

Sets a placeholder instead of a selected value.

Select.Popover

Select.Popover is a wrapper of the Popover component, and has the Same API's.

Select.Item:

PropTypeDescription
value
string

Give the select a value other than what is displayed in the item.

disabled
boolean

When true, the item is disabled.