Skip to content
Open UI

Stylable Select Element (Explainer)

Table of Contents

Background

The <select> element does not provide enough customization for web developers, which leads them to implement their own. These implementations can lead to reduced performance, reliability, and accessibility compared to the native form control elements. More on that is in Custom Control UI.

The 2020 MDN browser compatibility report had feedback that form controls are neither interoperable nor stylable. Stylable <select> will be fully interoperable and stylable.

Opting in to the new behavior

The <select> element will continue to behave as it currently does unless a <button> or <datalist> element is provided as a child of the <select>. <button> elements replace the native button which opens the listbox, and <datalist> replaces popup listbox. By replacing the native components with regular web content, the contents are fully stylable and customizable.

The current behavior in the HTML parser for <select> is to remove all tags which aren’t <option> or <optgroup>. We will change the HTML parser to allow <datalist> and <button> as a child of <select>, and allow any other content within that <datalist> or <button>.

Here is a basic <select> element today:

<select>
  <option>one</option>
  <option>two</option>
</select>

And here the same <select> with opt ins to the new stylability behavior which this explainer proposes:

<select>
  <button>
    <selectedoption></selectedoption>
  </button>
  <datalist>
    <option>one</option>
    <option>two</option>
  </datalist>
</select>

Use cases

Customizing basic styles

This example changes the fonts and colors of various parts of the button and listbox parts of the select element.

<select>
  <button>
    <selectedoption></selectedoption>
  </button>
  <datalist>
    <option>one</option>
    <option>two</option>
  </datalist>
</select>
<style>
select datalist,
select button {
  font-family: monospace;
  font-size: 12px;
}
</style>
The rendering of a stylable select element with monospaced font and red colors

Rich content in <option>s

This example adds “rich content” inside option elements in the form of country flags. This is a common pattern on the web which isn’t possible in the existing <select> element because <select>’s <option>s only render text content.

<select>
  <datalist>
    <option>
      <img src="usa.jpg" alt="flag of USA">
      USA
    </option>
    <option>
      <img src="germany.jpg" alt="flag of Germany">
      Germany
    </option>
    <option>
      <img src="spain.jpg" alt="flag of Spain">
      Spain
    </option>
  </datalist>
</select>
The rendering of a stylable select element with country flags next to the options

Replacing the button

This example replaces the button which opens the listbox with an author provided <button> element.

<select>
  <button>
    selected option: <selectedoption></selectedoption>
  </button>
  <option>one</option>
  <option>two</option>
</select>
The rendering of a stylable select element with an author-provided button

Rendering the <option> differently in the button

This example uses the stylable <select> element with custom CSS to target it which renders the option differently in the listbox vs in the button.

<select>
  <button>
    <selectedoption></selectedoption>
  </button>
  <datalist>
    <option>
      ❤️ <span class=description>heart</span>
    </option>
    <option>
      🙂 <span class=description>smile</span>
    </option>
  </datalist>
</select>
<style>
selectedoption .description {
  display: none;
}
</style>
The rendering of a stylable select element with a custom selectedoption element

Putting custom content in the listbox

This example has a <datalist> which enables it to put arbitrary content into the listbox rather than just <option>s.

<select>
  <datalist>
    <div class=container>
      <div>
        <optgroup label="1-2">
          <option>one</option>
          <option>two</option>
        </optgroup>
      </div>
      <div>
        <optgroup label="3-4">
          <option>three</option>
          <option>four</option>
        </optgroup>
      </div>
      <div>
        <optgroup label="5-6">
          <option>five</option>
          <option>six</option>
        </optgroup>
      </div>
      <div>
        <optgroup label="7-8">
          <option>seven</option>
          <option>eight</option>
        </optgroup>
      </div>
    </div>
  </datalist>
</select>
<style>
.container {
  display: grid;
  grid-template-rows: auto auto;
  grid-template-columns: 50px 50px;
  grid-column-gap: 10px;
  grid-row-gap: 10px;
}
.container > div {
  background-color: lightgray;
}
</style>
The rendering of a stylable select element with a custom listbox element

Here is another example with custom content in the listbox: codepen

Animations

This example uses view transitions to animate the opening and closing of the listbox. Link to codepen

A stylable select element with a listbox that animates open and closed

Split buttons

This example uses the type=popover attribute on one of the buttons in order to have a button which opens the select’s popup and another one which submits a form. The image is taken from a richer example here: link to codepen.

<select>
  <button><selectedoption></selectedoption></button>
  <button type=popover>⬇️</button>
  <datalist>
    <option>Create a merge commit</option>
    <option>Squash and merge</option>
    <option>Rebase and merge</option>
  </datalist>
</select>
A stylable select element with a split button

Button behavior and type=popover

The first child <button> of a <select> will open the popup list of options, unless one of them has type=popover on it. In that case, only the <button> with type=popover on it will open the popup. This enables use cases like split buttons but doesn’t require the author to add type=popover for most use cases where there is only one button.

Testing out the stylable select element

Stylable <select> is currently implemented behind a flag in Chromium. To use it, enable the Experimental Web Platform features flag in about:flags.

If you encouter bugs or limitations with the design of the control, please send your feedback by creating issues on the open-ui GitHub repository. Here is a list of open select bugs in open-ui.

Anatomy of the stylable select element

Because the various parts of the select element can be styled, it’s important to understand the anatomy of this UI control.

Currently selected option
Optgroup label
Option text
  • <select> - The root element that contains the button and listbox.
  • button (slot) - The portion of the element which is rendered in the position of the button which opens the listbox. It should contain a button to open the listbox.
  • <button> - The button which opens the listbox when clicked.
  • <selectedoption> - The element which contains the text of the currently selected option. Every time that the user selects an option, the browser will replace the text content of this element with the text content of the selected option.
  • <datalist> - The wrapper that contains the <option>(s) and <optgroup>(s).
  • <optgroup> - Optional element which groups <option>(s) together with a label.
  • <legend> - Used to provide a label to an <optgroup> element. It must be the first child of the <optgroup>.
  • <option> - Can have one or more and represents the potential values that can be chosen by the user.

Styling

Stylable <select> provides a variety of tools to help with styling, including pseudo-selectors for different states and pseudo-elements for different parts.

:open and :closed pseudo-selectors

The select element supports the :open and :closed pseudo selectors as specified by CSS here: https://drafts.csswg.org/selectors/#open-state

When the select’s listbox is showing, the select element will match :open, and when it is not showing, it will match :closed. Here is an example which makes the button red when the listbox is closed and green when it is open:

<select>
  <button id=custombutton>
    <selectedoption></selectedoption>
  </button>
  <option>one</option>
  <option>two</option>
</select>
<style>
select:open #custombutton {
  background-color: green;
}
select:closed #custombutton {
  background-color: red;
}
</style>

Additional examples

Here is an example which has a custom button with custom CSS:

<style>
  .my-custom-select button {
    display: flex;
    align-content: center;
  }
  .my-custom-select .open {
    padding: 5px;
    border: none;
    background: #f06;
    border-radius: 5px 0 0 5px;
    color: white;
    font-weight: bold;
  }
  .my-custom-select .label {
    padding: 5px;
    border: 1px solid #f06;
    border-radius: 0 5px 5px 0;
  }
</style>
<select class="my-custom-select">
  <button>
    <span class="open">Open</span>
    <span class="label">Choose an option</span>
  </button>
  <option>Option 1</option>
  <option>Option 2</option>
  <option>Option 3</option>
</select>

The above code snippet results in the following style:

The rendering of a select element with a custom button

Here is an example which has a custom listbox with custom CSS:

<style>
  .my-custom-select datalist {
    width: 300px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
    gap: 10px;
    padding: 10px;
    box-shadow: none;
    margin: 10px 0;
    border: 1px solid;
    background: #f7f7f7;
  }
</style>
<select class="my-custom-select">
  <datalist>
    <option>Option 1</option>
    <option>Option 2</option>
    <option>Option 3</option>
    <option>Option 4</option>
    <option>Option 5</option>
  </datalist>
</select>

The above code snippet results in the following style:

The rendering of a select element with a custom listbox

Extending the markup

Not only can you replace the default parts with your own, you can also extend the control’s markup by adding new elements. This can be useful to augment the listbox or button with extra information, or to add new functionality.

Consider the following example:

<style>
  .my-custom-select button {
    display: flex;
    align-items: center;
    gap: 1rem;
  }
  .my-custom-select .button {
    border: none;
    margin: 0;
    padding: 0;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    display: grid;
    place-content: center;
  }
  .my-custom-select .button::before {
    content: '\25BC';
  }
  .my-custom-select datalist {
    padding: 0;
  }
  .my-custom-select .section {
    padding: 1rem 0 0;
    background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%);
  }
  .my-custom-select h3 {
    margin: 0 0 1rem 0;
    text-align: center;
    color: white;
  }
  .my-custom-select option {
    text-align: center;
    padding: 0.5rem;
  }
</style>
<select class="my-custom-select">
  <button>
    <span class="label">Choose a plant</span>
    <selectedoption></selectedoption>
  </button>
  <div class="buttoncontainer">
    <span class="label">Choose a plant</span>
    <selectedoption></selectedoption>
    <span class="button"></span>
  </div>
  <datalist>
    <div class="section">
      <h3>Flowers</h3>
      <option>Rose</option>
      <option>Lily</option>
      <option>Orchid</option>
      <option>Tulip</option>
    </div>
    <div class="section">
      <h3>Trees</h3>
      <option>Weeping willow</option>
      <option>Dragon tree</option>
      <option>Giant sequoia</option>
    </div>
  </datalist>
</select>

Using custom markup to wrap the list of options, the above example creates sections with their own styles and content as seen below:

The rendering of a select element with custom HTML content

Demo page

You can find multiple examples of stylable select on our demo page.

Keyboard Behavior

When a custom <datalist> element is provided, the <select> element has unified keyboard behavior. The new behavior is inspried by both the existing <select> element and the Aria Authoring Practices Guide Combobox Pattern. This behavior was decided in issue 386 and issue 433.

When the listbox is closed and the button is focused:

  • Spacebar opens the listbox.
  • Enter submits the form associated with the select if one exists. Otherwise, enter does nothing.
  • The up, down, left, and right arrow keys all open the listbox without changing the selected value.

When the listbox is open:

  • Escape closes the listbox without changing the selected value.
  • Spacebar is used for typeahead.
  • Enter changes the selected value to the currently focused option and closes the listbox.
  • The up arrow key moves focus backwards in the list of options and changes the selected value to the newly selected option.
  • The down arrow key moves focus forwards in the list of options and changes the selected value to the newly selected option.
  • The left and right arrow keys do nothing.

multiple and size attributes

The HTML parser will not allow <button> or <datalist> children when the multiple or size attributes are present on <select>. This will ensure that the old rendering behavior of multiple and size is used. If the author adds a <datalist> or <button> via script to a <select multiple> or <select size>, the old rendering will still be used. In the future, we would like to upgrade <select multiple> to become stylable, at which time we will change the HTML parser again to allow <button> and <datalist> children. This also allows feature detection to know whether <select multiple> improvements are ready to be used. More detail in this issue.

Design decisions