WWDC Quick Look đź’“ By SwiftGGTeam
Rediscover the HTML select element

Rediscover the HTML select element

Watch original video

Highlight

Safari 27 and Chrome 135 add pseudo-elements such as appearance: base-select and ::picker(select) to the native <select> element. Developers can now fully customize dropdown styling with pure CSS, place images and SVGs inside <option>, and still retain native keyboard navigation and screen-reader support.

Core Content

Anyone who builds web forms has wrestled with <select>. A designer hands over a dropdown with icons, rounded corners, and shadows, and the front-end work immediately gets painful. The native <select> appearance is controlled by the operating system: macOS shows one pull-down button style, while Windows shows another. Want to customize it? You either bring in a JavaScript library such as Headless UI or Select2, adding tens of kilobytes and simulating dropdown behavior with many <div> elements, or you build your own component and hand-write keyboard navigation, focus management, and ARIA labels. Once it works, you still need to test it one by one with VoiceOver and NVDA.

That path is now much more practical. Safari 27 and Chrome 135 ship Customizable Select with a simple idea: keep the HTML element and add CSS. Add one line, appearance: base-select, to <select>, and the browser unlocks styling for the button and dropdown panel, returning control to developers. An <option> can directly contain <img>, <svg>, or even <video>. More importantly, all of this is built on semantic HTML, so arrow-key navigation, Enter-to-select, and screen-reader announcements remain native without extra code.

The session demonstrates a photographer portfolio site. The client wants sorting and category filtering on the page, implemented with <select>. The native control looks completely out of place beside the site’s visual design. With Customizable Select, the button background, border, radius, and padding are all customized in CSS; the dropdown arrow is replaced with a custom SVG; and the button color can change while the menu is open. The whole transformation takes only a few lines of CSS.

Details

Enable customization mode

(02:50)

The first step is adding appearance: base-select to <select>. This tells the browser not to use the system default style and instead apply a minimal base style that developers can override.

body {
    font-family: Gill Sans, sans-serif;
}

select {
    appearance: base-select;
}

Key points:

  • appearance: base-select inherits the parent font, so text in the button automatically aligns with the rest of the page.
  • This property is not Safari-only; Chrome 135 supports it too, with no prefix required.

Customize button styling

(03:07)

Once the base style is enabled, you can write CSS as if you were styling a regular button.

select {
    appearance: base-select;
    background-color: var(--green-10);
    border: none;
    padding: 0.6em 1em;
}

Key points:

  • Standard properties such as background-color, border, padding, and border-radius now fully apply to <select>.
  • You do not need !important or browser default stylesheet hacks.

Replace the dropdown arrow

(03:08)

The browser’s default dropdown arrow can be replaced with the ::picker-icon pseudo-element.

select::picker-icon {
    content: url(icons/arrow.svg);
    width: 0.65em;
}

When the menu is open, both the button and arrow can switch states. Use the :open pseudo-class:

select:open {
    background-color: var(--green-100);
    color: white;
}

select:open::picker-icon {
    content: url(icons/arrow-white.svg);
}

Key points:

  • ::picker-icon is a new Customizable Select pseudo-element specifically for controlling the dropdown arrow.
  • The :open pseudo-class matches while the menu is expanded, removing the need to listen for focus or blur in JavaScript.

Customize the dropdown panel

(04:08)

The dropdown menu itself can also be customized. Select the panel with ::picker(select), and also add appearance: base-select there to fully remove system styling.

::picker(select) {
    appearance: base-select;
    padding: 4px;
    margin-top: 0.5em;
    border: 1px solid rgba(0, 0, 0, 0.2);
    border-radius: 9px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}

Key points:

  • appearance: base-select inside ::picker(select) is required; otherwise the panel remains the system default white box with a black border.
  • margin-top controls spacing between the panel and the button.

Customize option styling

(04:36)

Selected state and the default checkmark for options can also be customized.

option:checked {
    font-weight: 600;
}

option:not(:checked) {
    color: #777;
}

option::checkmark {
    content: url(checkmark.svg);
    width: 0.65em;
}

Key points:

  • option:checked selects the currently selected option, and option:not(:checked) selects the other options.
  • The ::checkmark pseudo-element controls the checkmark before the selected item, and content can replace it with any SVG.

Put images inside options

(05:31)

Customizable Select allows rich content inside <option>, such as images and labels.

<option value="flower">
    <img src="flowers.svg" alt="">
    <span class="text">Flowers</span>
</option>

Key points:

  • <option> can now contain arbitrary HTML elements.
  • alt="" is required; otherwise a screen reader will announce both the image description and the “Flowers” text.

Remove the default checkmark and use a background color to highlight the selected item:

option::checkmark {
    display: none;
}

option:checked {
    background: #00857e;
    color: white;
}

Arrange options with Grid layout

(06:20)

When there are many options, a single-column dropdown becomes long. ::picker(select) supports display: grid, so the dropdown panel can become a grid directly.

::picker(select) {
    display: grid;
    grid-template: 1fr 1fr / 1fr 1fr 1fr;
    gap: 1rem;
}

Key points:

  • grid-template: 1fr 1fr / 1fr 1fr 1fr means 2 rows and 3 columns.
  • This Grid layout only affects the dropdown panel; it does not affect other page elements.

Show rich selected content on the button

(07:11)

The native <select> button shows plain text by default. If an <option> includes an image, that image will not appear on the button after selection. To solve this, place a custom <button> inside <select> and insert a <selectedcontent> element inside it.

<select>
    <button>
        <selectedcontent></selectedcontent>
    </button>
    <option value="anywhere">
        <img src="icons/all.svg" alt="">
        <span class="text">Everything</span>
    </option>
    <option value="buildings">
        <img src="icons/buildings.svg" alt="">
        <span class="text">Buildings</span>
    </option>
    <option value="flowers">
        <img src="icons/flower.svg" alt="">
        <span class="text">Flowers</span>
    </option>
</select>

Key points:

  • <button> must be the first child of <select>.
  • <selectedcontent> is an empty element. The browser automatically clones the DOM structure of the currently selected <option> into it.
  • HTML previously did not allow <button> inside <select>, but Customizable Select relaxes that restriction.

Progressive enhancement

(07:53)

In browsers that do not support Customizable Select, <select> falls back to the system-native dropdown. Users still see a familiar platform control, and keyboard navigation plus screen-reader behavior remain intact. Because the underlying element is semantic HTML, no fallback code is required.

Key Ideas

  • Build a country/region picker with icons: Put flag SVGs inside <option>, and use <selectedcontent> so the flag also appears on the selected button. Entry point: appearance: base-select plus ::picker(select).

  • Build a color picker: Put a color swatch <div> inside each <option>, then use display: grid to arrange the dropdown panel as a 4 x 4 palette. The radial color picker shown at the end of the session uses the same principle. Entry point: grid-template layout plus rich-content <option>.

  • Build a category filter component: A common ecommerce “filter by category” dropdown can be implemented with Customizable Select. Each option includes a category icon and name, and the selected button shows the icon as well. Entry point: <selectedcontent> plus a custom ::picker-icon.

  • Replace JavaScript select libraries in an existing project: Move purely presentational dropdowns built with React-Select or Headless UI back to native <select>, cutting dependency size and ARIA maintenance cost. Entry point: globally search for import { Select } and evaluate which instances can be replaced.

  • Build an image-size picker: Photography or design tools often include filters such as thumbnail, medium, and original size. Each option can contain a preview image and size label, arranged in a grid. Entry point: display: grid plus <img> inside <option>.

Comments

GitHub Issues · utterances