This post pushes customizable <select> to an extreme. I think it turned out nice.
It's mostly CSS! A li'l JS sprinkle just to position the ::picker() and the selected option together; the JS is nice to have, CSS does the real work.
superellipse forced colors light/dark theme slick scrollbar scroll driven animations scroll-state stuck query scroll-state scrollable query overscroll behavior smooth scroll RTL support text-box trim spring easing anchor & anchor fallbacks color-mix()
Chrome only atm, and I keep mobile with the native select. Still cool!
Core Architecture #
This component builds on the new appearance: base-select foundation, combining native accessibility with sweet visual control. Progressive enhancement ensures the custom experience only loads on capable devices.
The examples in this post just look like regular select elements in Firefox and Safari. Firefox is working on it though!
appearance: base-select
unlocks full customization of native<select>elements while retaining browser controlled accessibility and keyboard navigation- Conditional enhancement
Only enables custom styling for devices with hover and fine pointer (@media (hover) and (pointer: fine)) - JavaScript + CSS hybrid
JS calculates an anchor offset for alignment while CSS handles all visual presentation and animation
Alignment & Positioning #
Anchor positioning plus some special JS sauce to write an inline CSS variable unlocks the picker to position near the selected option (obviously this can't work at the viewport edges). This creates a pleasant morphing effect that feels connected.
Maybe I should have used AIM?
- CSS Anchor Positioning
Picker usesanchor(start)to position relative to the trigger button - Selected option alignment
When dropdown opens, the currently selected option aligns vertically with the button text via--_select-anchor-offsetcustom property - Syncs
transform-origin
With50% var(--_select-anchor-offset)ensures scale animations line up better with the selected option's position - Anchor fallbacks with
position-try
flip-block, flip-inlineautomatically repositions the anchor position if the picker would overflow the viewport
Animation & Transitions #
Scale effects with spring physics to scroll-driven option reveals, every interaction respects user motion preferences while providing rich feedback.
@starting-style
Defines entry animation initial state for the popover- Spring easing
Uses Open Props--ease-spring-3for playful, physics-based motion on scale and rotation - Discrete property transitions
display and overlay useallow-discretefor proper::picker()popover animation support - Chevron rotation
::picker-iconrotates 180° on open with spring easing - Scroll-driven option fade-in
Options animate in from the bottom usinganimation-timeline: view() - Respects reduced motion
Animations and transitions wrapped in@media (prefers-reduced-motion: no-preference) - Button press feedback
Active state scales down to0.98for tactile feedback - Open state button feedback
Select scales up to1.04when:open, scaling back down on close with a soft bounce
Theming & Color Handling #
Automatic light/dark adaptation leverages system colors and modern color functions for seamless theme switching. Definitely check the support for light/dark forced colors too, a really great way to debug spacing and skeletal aspects of the UI.
color-scheme: light dark
Enables automatic system color adaptationlight-dark()function
Used for backgrounds that need different light/dark values- System colors
Canvas,CanvasText,Highlight,HighlightTextfor semantic, colors color-mix()
Creates semi-transparent overlays and borders- Data URI icons
Chevron and checkmark icons embedded as SVG data URIs with variants for each color scheme - Forced colors support
@media (forced-colors: active)provides high-contrast mode adaptations (even the sticky scroll state respects this!)
Layout & Spacing #
Logical properties enable true internationalization with automatic RTL support, while text-box trim delivers pixel-perfect vertical alignment. Custom properties create a flexible design token system.
- CSS Custom Properties design tokens
All spacing, sizes, and timing values defined as--_select-*variables - Logical properties throughout
inline-size,block-size,inset-block-start,margin-inline,padding-blockfor full RTL support text-box: trim-both cap alphabetic
Precise vertical text alignment in legends- Flexible option content
Options use flexbox with gap for icon + text layout - Checkmark auto-positioned
order: 2andmargin-inline-start: autopushes checkmark to end of option
Scrolling & Overflow #
Scroll-state queries dynamically detect when content is scrollable and when sticky headers become stuck. Custom scrollbar styling and smooth scrolling enhance the browsing experience within long option lists.
- Container scroll-state queries
container-type: scroll-stateenables@container scroll-state(stuck)detection - Sticky legends
Group headers stick to top when scrolling, with color change when stuck - Custom scrollbar styling
scrollbar-width: thinandscrollbar-colorwith fade on hover-out - Scroll containment
overscroll-behavior-block: containprevents scroll chaining - Smooth scroll
scroll-behavior: smoothfor programmatic scrolling (motion-safe) - Dynamic scrollable detection
JS adds.scrollableclass when content exceeds max height, CSS adjusts padding accordingly
Customizable Select Features #
New pseudo-elements provide surgical styling control over picker components, while superellipse corners deliver the modern aesthetic popularized by iOS. State selectors enable precise targeting of open and selected states.
::picker(select)
Pseudo-element for styling the dropdown picker container::picker-icon
Pseudo-element for the dropdown chevron indicator::checkmark
Pseudo-element for the selected option indicator<selectedcontent>
Native element that mirrors selected option content in the button- Squircle corners
corner-shape: superellipse(1.25)for modern rounded rectangles (with fallback) :openpseudo-class
State selector for when picker is visible:checkedon options
State selector for selected option
Accessibility & UX #
Accessibility is built-in with proper focus indicators, keyboard navigation, and semantic markup thanks to the web platform. Touch-friendly targets and overflow handling ensure the component works elegantly across all input methods.
- Focus ring styling
Distinct:focus(subtle) and:focus-visible(prominent) - Disabled option support
option[disabled]styling and hover prevention - Text overflow handling
text-overflow: ellipsisin button display - Minimum touch target
36pxitem height matches mobile guidelines - Hidden option support
JS skips[hidden]options in offset calculations - RTL example included
Arabic language demo validates bidirectional layout
Performance Optimizations #
Careful optimization strategies keep animations smooth while minimizing layout thrash. Smart caching and compositor hints ensure the component performs well even on lower-end devices.
will-change: scale
Hints to browser for compositor optimization (also fixes text shift bug)- WeakMap offset cache
Avoids expensive DOM measurements on every interaction;
auto-garbage-collected - Hardcoded layout constants
AvoidsgetComputedStyleforced reflows during initialization. Tried dynamically computing these in a hidden instance, but couldn't get it as close as just "hardcoding / speficying them ahead of time" in a config - Background clip
background-clip: padding-boxprevents background bleeding under border - Parallel icon variants
Pre-defined black/white SVGs avoid runtime color manipulation
Example Variations #
The component architecture supports diverse use cases, from simple toggles to rich content pickers. Each variation demonstrates how the base patterns adapt to different content and interaction models.
- Toggle with status indicators
Colored dots (green/red/gray) for on/off/disabled states - Avatar select
Circular images with person names - Multi-line options
Title + description layout for status picker - Grouped options
Fieldsets with sticky legend headers - Label above value
Color space picker with small label text above the selected value - Flag emoji select
Country picker organized by region
Try It #
Experience the component in action on CodePen:
Wrapping Up #
There's room for improvement, please fork and offer feedback!
I tried many variants of animation, trying to reduce some of the shift of the positioning, but never found something that worked across all the variants. Help me?! 🙏
This select component intends to showcase what's possible when we combine modern CSS features with thoughtful progressive enhancement. The appearance: base-select property provides the foundation, while anchor positioning, scroll-state queries, and entrance animations create a polished, accessible experience.
The real power lies in how these features compose together—each enhancement builds on platform primitives rather than fighting against them. The result is a component that's maintainable, accessible, and ready for the next generation of web interfaces.
Most importantly, this approach lets us create custom experiences without sacrificing the keyboard navigation, focus management, and screen reader support that come free with native elements.
For more, checkout nice details!

