RSS FeedTwitterMastodonBlueskyShare IconHeart IconGithub IconArrow IconClock IconGUI Challenges IconHome IconNote IconBlog IconCSS IconJS IconHTML IconShows IconOpen Source Software IconSpeaking IconTools IconShuffle IconNext IconPrevious IconCalendar IconCalendar Edit IconNotebook IconObservable Notebooks IconSlash IconGoogle G IconYouTube IconEye IconComment Icon
Text emphasized alt text example
A cartoon skull with a hotpink hat on.8 min read

Nice Select

csshtml
699 views · 30 active

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!

Try it

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 uses anchor(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-offset custom property
  • Syncs transform-origin
    With 50% var(--_select-anchor-offset) ensures scale animations line up better with the selected option's position
  • Anchor fallbacks with position-try
    flip-block, flip-inline automatically 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.

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.

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.

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.

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.

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.

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
    Avoids getComputedStyle forced 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-box prevents 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!

18 comments #

208likes
52reposts
  • Jon Henshaw
  • MxFraud
  • Paddy Duke
  • Julian ????4215
  • Thibaultmol ???? ???? FOSDEM
  • Dave Rupert
  • Guillermo Cava
  • Eduardo.????????????
  • Michael Warren
  • Sue
  • Bart van de Biezen
  • Curioso ???? ???????? (jgg)
  • Aaron In Minnesota
  • Sid com brain rot incurável
  • Andrew Aquino
  • Thomas Broyer
  • Sasha Chudesnov
  • John Grogg
  • Jason Long
  • Dave Letorey
  • Chris
  • Gustav Hansen
  • Jacob Marshall
  • Bramus
  • Jacob Berglund
  • Phillip Upton
  • Peter Wooley
  • Assaf ????
  • Sid Vishnoi
  • Hubert Souchaud
  • Latte macchiato
  • Mary Branscombe
  • Marshall Bowers
  • Brian Kardell
  • ML | Merkyl
  • Zach Leatherman
  • Andrew Ek
  • Fyrd
  • Una Kravets
  • Paul Mara
  • Jake Lazaroff
  • Stacy London
  • Martin Grubinger
  • CodePen
  • Anthony Frehner
  • Fynn Ellie Be
  • Jonathan Dallas
  • Nilesh Prajapati
  • Bruce B Anderson

Join the conversation on

working on implementing a version of this in our design system as we speak! looks really cool! now let’s do date pickers and combo boxes :)
Michael WarrenMichael Warren
@nerdy.dev Looks lovely, I’m doing something similar atm. But I’m stuck with wanting to ”remote closing” the menu via JS also. Is there something similar to showPicker - but inverted - coming, do you know? Like hidePicker()
Anton Andreasson GistedtAnton Andreasson Gistedt
cc @una.im for advice here! picker() is a popover, curious if it can be targetted and closed as such? and sounds like you're already aware of the new showPicker(), not sure what's up with a hidePicker()
Adam ArgyleAdam Argyle
I once wrote: "browser-native solutions are almost always much more muture than JS-custom ones" This is so true, for example dialogs, expect for web components.
IbrahimharchicheIbrahimharchiche
@una.im, more of this please.
IbrahimharchicheIbrahimharchiche
That is rad! Excited to use it as browsers catch up with support. Thanks for showing the possibilities of what it can do ????
DiegoDiego
@nerdy.dev has got you covered!
Una KravetsUna Kravets
Sooo cool. I'm very excited for all this to come to multi select boxes so we can make nice tag selector inputs natively etc. Any idea if this is coming?
NeilNeil
Love it buddy and particularly the links to the upvote links
Dave LetoreyDave Letorey
joey is workin on it I believe, both a custom listbox and multiselect
Adam ArgyleAdam Argyle
Nice! Joey?
NeilNeil
Oh my. I shed a tear of web-joy. ???? what a time to be alive!
Jacob BerglundJacob Berglund
Those are some good looking selects.
Bill CriswellBill Criswell
::picker() is technically a popover but there is some interaction weirdness (see github.com/whatwg/html/...), which stretches to closePopover(). One way to programmatically close it would be to open (& close) another auto popover to close it (though this is super hacky & we need a better solution)
Una KravetsUna Kravets
This is really nice! Where do you get your icons, if you don’t mind my asking?
Keelin JacobsenKeelin Jacobsen
@nerdy.dev Another thing: I notice you use `fieldset` instead of `optgroup`, is that because styleability? I have had a hard time styling my optgroups.. but kept them becuase semantics, I guess.
Anton Andreasson GistedtAnton Andreasson Gistedt