LEARNING OBJECTIVES ⌵
- Understand why native
<select>elements were historically impossible to style with custom HTML and icons. - Trace the OpenUI standardization path from the experimental
<selectlist>to the modern Customizable<select>. - Master the
<selectedcontent>element and how it dynamically mirrors rich markup from the active<option>. - Implement fully styled, accessible dropdowns with images, badges, and grid layouts using
appearance: base-select. - Maintain 100% native form submission, mobile OS touch fidelity, and keyboard typeahead behavior.
📖 The Mental Model & Story (Intuitive Foundation)
For over 25 years, the humble HTML <select> tag was the undisputed villain of front-end web development.
If a UI designer handed you a mockup showing a country picker with national flag icons, currency symbols, and styled subtitles, you were forced to abandon native HTML. You had to construct a fake dropdown out of nested <div> tags, custom JavaScript click listeners, manually calculated z-index overlays, and hundreds of lines of fragile ARIA keyboard navigation code.
THE OLD FAKE DROPDOWN NIGHTMARE (<div onClick>)
+---------------------------------------------------------------------------------+
| ✕ 15 KB of JavaScript boilerplate to manage focus and keyboard arrows. |
| ✕ Breaks native mobile OS picker wheels (iOS Wheel / Android Bottom Sheet). |
| ✕ Requires hidden <input type="hidden"> fields to submit in standard HTML forms.|
| ✕ Inaccessible to screen readers if ARIA state isn't perfectly synchronized. |
+---------------------------------------------------------------------------------+
THE MODERN CUSTOMIZABLE <select> (OpenUI / WHATWG Standard)
+---------------------------------------------------------------------------------+
| ✓ 100% Native HTML form submission (<select name="country">). |
| ✓ Rich HTML inside <option>: SVGs, images, badges, styled typography. |
| ✓ Native keyboard typeahead, Enter/Esc handling, and Top Layer popover picker. |
| ✓ <selectedcontent> automatically clones the selected option's visual layout. |
+---------------------------------------------------------------------------------+
The OpenUI Group and WHATWG solved this with the Customizable Select standard. By applying appearance: base-select in CSS and placing a <selectedcontent> element inside the dropdown trigger, the browser unlocks complete visual styling while retaining 100% of the battle-tested, native accessibility and form mechanics.
Technical Deep Dive & Specifications
The Anatomy of the Customizable Select
A customizable <select> decomposes the monolithic native control into modular, stylable DOM primitives:
<select style="appearance: base-select">
│
├── <button type="popover"> ────────> The custom button trigger that opens the menu
│ └── <selectedcontent> ────────> Mirrors the rich inner HTML of the active <option>
│
└── <div popover> (or ::picker) ─────> The Top Layer dropdown popover containing options
├── <option value="us"> ───────> Rich Option 1 (Contains <img>, <span>, etc.)
├── <option value="eu"> ───────> Rich Option 2
└── <option value="jp"> ───────> Rich Option 3
How <selectedcontent> Operates
The <selectedcontent> element is a specialized projection container:
- When a user selects an
<option>, the browser automatically clones the child DOM nodes of that<option>into the<selectedcontent>element. - If an
<option>contains an<img>, a<strong>title, and a<small>subtitle, all three elements are mirrored inside the button trigger. - If an
<option>contains elements with the attributedata-selected-label, only the designated summary content is mirrored, allowing you to display a condensed summary in the closed button and full detail in the open menu.
ACTIVE OPTION IN DROPDOWN MIRRORED INSIDE TRIGGER BUTTON
+------------------------------------+ +------------------------------------+
| <option value="btc"> | | <button> |
| <img src="btc.svg"> Bitcoin | =======> | <selectedcontent> |
| <span class="price">$65,000</span>| | <img src="btc.svg"> Bitcoin |
| </option> | | </selectedcontent> |
+------------------------------------+ +------------------------------------+
The CSS appearance: base-select Property
To opt into the customizable select model, you must explicitly opt out of the operating system's default native rendering engine using CSS:
/* Enables custom styling of the <select> trigger and options */
select {
appearance: base-select;
}
/* Styles the Top Layer popover picker surface */
select::picker(select) {
appearance: base-select;
background: #1e293b;
border: 1px solid #334155;
border-radius: 8px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
padding: 0.5rem;
}
Feature Comparison Matrix
| Feature | Legacy <select> |
Custom <div> Dropdown |
Modern <select> + <selectedcontent> |
|---|---|---|---|
| Rich HTML in Options | ✕ (Plain text only) | ✓ | ✓ (SVGs, images, badges) |
| Zero JS Form Submit | ✓ | ✕ | ✓ |
| Native A11y & ARIA | ✓ | ✕ (Manual work) | ✓ |
| Keyboard Typeahead | ✓ | ✕ (Manual work) | ✓ |
| Top Layer Rendering | ✕ | ✕ (Z-index bugs) | ✓ (Built on Popover engine) |
| Mobile Sheet Support | ✓ | ✕ | ✓ |
💻 Interactive Code Playground
Starter Code: Production Cryptocurrency Selector
Line-by-Line Code Breakdown
- Lines 31–44 (
select.crypto-picker): Appliesappearance: base-select, instructing the browser to strip legacy OS widget styling and allow custom layout properties. - Lines 49–54 (
selectedcontent): Uses CSS Flexbox to align the mirrored active option content inside the closed button. - Lines 56–64 (
::picker(select)): Styles the Top Layer popover container generated by the browser when the select expands. - Lines 98–101 (
<button><selectedcontent>...): Declares the trigger button housing the<selectedcontent>projection node and a dropdown arrow. - Lines 104–126 (
<option>): Defines rich option items containing nested<div>,<span>, and badges. - Lines 134–138: Demonstrates standard
FormDataextraction. The nativename="currency"andvalue="BTC"are sent directly without hidden inputs.
Expected Browser Render Output
Payment Asset
Select your settlement currency:
+---------------------------------------------+
| [ ₿ ] Bitcoin (BTC) $64,200 ▼ |
+---------------------------------------------+
[When Clicked / Expanded]:
+---------------------------------------------+
| [ ₿ ] Bitcoin (BTC) $64,200 |
| [ Ξ ] Ethereum (ETH) $3,450 |
| [ ◎ ] Solana (SOL) $145 |
+---------------------------------------------+
(Full keyboard arrow navigation, typeahead filtering, and form submission work natively.)🏋️ Hands-On Exercise
🎯 The Challenge: Build a Team Member Assignee Picker
Instructions:
- Create a
<select name="assignee">element configured withappearance: base-select. - Provide a
<button>trigger enclosing a<selectedcontent>element. - Add three
<option>entries representing team members. Each option must contain:- An avatar emoji circle (e.g., 👩💻, 👨🎨, 👨🚀).
- The member's full name.
- An online/offline status badge (styled with green or gray backgrounds).
- Verify that selecting any team member updates the trigger button with their avatar and name automatically.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Using Obsolete
<selectlist>Syntax:<selectlist>was an early Chromium experimental prototype that was superseded by the WHATWG standardizing on<select>+appearance: base-select. Always author standard<select>tags. - Forgetting
appearance: base-selecton::picker(select): If you only applyappearance: base-selectto the parentselectbut forget the picker pseudo-element, the open dropdown menu may still render in the default OS skin. - Placing Interactive Elements Inside
<option>: Options may contain rich visual elements (<img>,<span>,<strong>), but they MUST NOT contain nested interactive elements like<button>or<a href>.
💡 Pro Tips
- Progressive Enhancement Fallback: If a legacy browser does not support
appearance: base-select, it gracefully degrades into a standard operating system<select>menu using the text contents of each<option>. - Leverage CSS Grid Inside Options: You can create multi-column data tables inside dropdown options by declaring
option { display: grid; grid-template-columns: auto 1fr auto; }.
📌 Key Takeaways
- The Customizable Select specification allows rich HTML markup (icons, subtitles, badges) inside native
<option>tags. - Setting
appearance: base-selectopts out of legacy OS dropdown styling and unlocks full CSS control. - The
<selectedcontent>element automatically mirrors the active<option>markup into the trigger button. - Customizable selects retain 100% of native accessibility: keyboard typeahead, screen reader ARIA roles, and form submissions.
- The dropdown popover renders inside the browser Top Layer via
select::picker(select). - --