Edit Profile

Dark Mode

Copy config

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

DISCLAIMER: This component is in
Beta
status. That means that it is ready for production, but the API might change.

Select

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

✨ Features

  • Supports single and multiple selection
  • Supports reactive and initial value changes
  • Disabled item support
  • Stop focus management via the Tab key
  • Grouped items support
  • Looping support
  • Support for custom scroll behavior
  • Listbox UI is placed above everything else. (Select.Popover)
  • Custom Positioning (Select.Popover)
  • item selection and focus management by typing (typeahead)
  • Keyboard support for item navigation via arrow keys and focus management
  • Automatic focus management for both selection modes.
  • Supports a custom placeholder
  • Roadmap

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

Building blocks

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

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

  return (
    <Select.Root class="select">
      <Select.Label>Logged in users</Select.Label>
      <Select.Trigger class="select-trigger">
        <Select.Value placeholder="Select an option" />
      </Select.Trigger>
      <Select.Popover class="select-popover">
        <Select.Listbox class="select-listbox">
          <Select.Group>
            <Select.GroupLabel>People</Select.GroupLabel>
            {users.map((user) => (
              <Select.Item key={user}>
                <Select.ItemLabel>{user}</Select.ItemLabel>
                <Select.ItemIndicator>{/* Icon */}</Select.ItemIndicator>
              </Select.Item>
            ))}
          </Select.Group>
        </Select.Listbox>
      </Select.Popover>
    </Select.Root>
  );
});

// internal
import styles from '../snippets/select.css?inline';

🎨 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.Value

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 has been working towards filling in some of these gaps with a new HTML element called selectlist.

It is still not supported by any major browsers, and there is still an ongoing debate about its future, including a possible attribute to the select element to fix some of the problems instead.

Philosophy

Qwik UI's Select component is designed to solve these pain points out of the box, along with a focus on accessibility and usability.

It intends to provide intuitive APIs and behavior.

Passing data

The select component accepts data similar to the native select element. Item data can be passed in directly to <Select.ItemLabel />.

Basic example

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

Mapping over data

You are in full control of how the data is rendered. Map over the data, or render the items however you like.

Object example

Passing a distinct value

The selected value is: null

Sometimes we want to display one thing to the user, but pass another value to the item.

By adding the value prop to the <Select.Item /> component, we can pass a distinct value to the select component.

Handling selection changes

You have changed 0 times

We can listen to changes in the selected value by using the onChange$ prop. It provides an argument that is the new selected value.

The above example increments a count when the user selects a new item.

Component state

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

Uncontrolled / Initial value

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.

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.

Programmatic changes

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

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

Disabled items

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.

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

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:display signal.

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

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

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.

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

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.

  • 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.Value /> component by adding the placeholder prop.

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

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);
  border-radius: calc(var (--border-radius) / 2);
  min-height: 44px;
  max-width: var(--select-width);
  padding-block: 0.5rem;
  display: flex;
  justify-content: center;
  align-items: center;
}

.select-popover {
  width: 100%;
  max-width: var(--select-width);
}

.select-listbox {
  width: 100%;
  background-color: hsl(var(--background));
  padding: 0.5rem;
  border: 2px dotted hsla(var(--foreground) / 0.6);
  border-radius: calc(var(--border-radius) / 2);
  max-width: var(--select-width);
  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] {
  outline: 2px dotted hsla(var(--primary) / 1);
  border-radius: calc(var(--border-radius) / 2);
}

[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.Value, 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:display
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.Value

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.