Customisable and the friends we made along the way
Introduction and Bruce Swirsky's Pun Problem
Host Bruce introduces Jake Archibald with characteristic irreverence, prompting a gradual audience chant to bring him to the stage. Jake opens with an anecdote about the peculiar cognitive challenge of listening to Bruce tell stories — never knowing whether a long build-up will culminate in an elaborate pun or simply turn out to be a real life experience — illustrating the difficulty of being primed for the wrong kind of listening.
1993 and the Select Element's 33-Year Styling Dream
Jake traces the origins of the select element to a 1993 email exchange between Tim Berners-Lee and Dave Raggett, who proposed the input and select elements and — even before CSS existed — made developer control over select rendering his very first requirement. Jake then showcases Stylable Select now landing in browsers, highlighting practical demos by Adam Argyle and Una Kravets, and introduces his own programming language picker as the talk's running example.
Why Select Stayed Unstyled: Mobile Wins and Microsoft's Engine Switch
Jake explains why leaving select rendering as a browser implementation detail was initially validated — especially by the iPhone's radically different but device-appropriate picker UI, which demonstrated that platform control served users well. He then covers Microsoft's 2018 decision to abandon EdgeHTML for Chromium, explaining how engineers who joined the Chromium project initially improved select accessibility before being redirected to commercial products, though Microsoft has since become the second-largest Chromium contributor.
Developer Frustration, Open UI, and the Layering Challenge
Jake introduces Greg Whitworth's landmark developer survey, which confirmed that select was the most frustrating form control to recreate, having spawned an ecosystem of large, accessibility-questionable JavaScript libraries. Greg responded by founding Open UI — a W3C community group bridging the CSS Working Group and WHATWG — alongside Googlers Mason Fried and Joey Aja. Jake then frames the two distinct sub-problems of a stylable select: button and picker markup and styling, plus the picker's layering challenge, demonstrating how z-index solutions inevitably break under overflow:hidden.
Popovers and the Top Layer: A History of Breaking Out of Containers
Jake covers the 2021 popover proposal by Melanie Richards and its journey to cross-browser baseline in 2024, explaining the popover attribute, invoker commands, and built-in behaviors like escape-to-close, outside-click dismissal, and focus management. He then traces the origin of the top layer concept back to the Fullscreen API in 2011 (developed at Mozilla and Opera), through the dialog element in 2022, to popovers — noting how each era's needs shaped a progressively more generic layering solution that view transitions have already had to extend further.
CSS Anchor Positioning: Attaching Popovers to Their Triggers
With layering solved by popovers, Jake introduces CSS Anchor Positioning — baseline in 2026 — as the solution to the picker's placement problem, crediting Tab Atkins, Elika Etemad, and Emilio Cobos. He demonstrates how anchor-name and position-area work together to bind a popover's position to its triggering button, including implicit anchor behavior via the command attribute. Jake then dives into the position-area grid model — a CSS-grid-like system extending from the anchor to the containing block — covering spanning, logical values, default alignment, and the new anchor-center value.
Building a Smart Dropdown: Dynamic Sizing and Viewport Fallbacks
Jake walks through building a realistically-behaved dropdown picker, covering anchor-size() for matching the button width, position-try for flipping the picker when it approaches the viewport edge, and the stretch keyword for adaptive height. He introduces calc-size() — needed because intrinsic sizes cannot be used inside min() or max() — to implement sensible minimum and maximum height constraints, producing a picker that shrinks near the viewport boundary before flipping. Jake notes calc-size is currently Chrome-only and recommends a CSS fallback for Firefox and Safari until broader adoption.
Anchor Positioning's Rough Landing and the Opt-In Mechanism Debate
Jake frankly discusses anchor positioning's unstable browser implementation — with demos breaking between Chrome stable releases just months apart and inconsistent behavior across all three engines — reassuring developers that confusion is the feature's fault, not theirs. He then recounts the multi-year debate over how to opt into a stylable select: Greg's CSS appearance:base proposal was initially rejected because it would have CSS trigger a DOM restructure; a new selectmenu element was proposed but pushed back on by Anne van Kesteren and Henri Sivonen who argued for progressive enhancement on the existing select; and ultimately a CSS-based opt-in was adopted, vindicating Greg's original approach.
Implementing Stylable Select: appearance:base and Core Styling
Jake demonstrates the final implementation: appearance:base select opts the button into custom styling, while a separate picker rule independently opts in the dropdown — enabling partial adoption such as styling only the button on smaller screens via media queries. He shows the picker's minimal but cross-browser-consistent default styles, demonstrates styling the picker icon using an animated CSS shape, and covers removing and replacing the default option checkmark using the :checked pseudo-class.
Rich Option Content, selectedContent Element, Option Groups, and Conclusion
Jake demonstrates the expanded content model allowing images and arbitrary HTML inside option elements, then introduces the selectedContent element — a uniquely auto-populating element that clones the selected option into the button for rich display, independently styleable via the cascade. He covers the option group label workaround using a legend element combined with the attr() CSS function (part of Interop 2026), and closes by listing the many platform features — popovers, anchor positioning, calc-size, invoker commands, discrete display animations, and more — that the stylable select story brought into existence, finally fulfilling Dave Raggett's requirement from 1993.
I've always had a good word for our next speaker. Unfortunately, the code of conduct and the law of libel forbid me from from repeating this word. He's quite shy. Earlier, I called his name when I was trying to introduce you to, browser vendors, and he didn't show.
Where were you actually, Jake? Well
I was having
a poo. Okay. So Jeremy Keith informs me that there's a way to summon him to the stage, and, let's hope it works. We're gonna start off really quietly and chant his name and just get louder and louder and louder because he's been working out.
He's a bit of an MMA guy these days. So it's by the way, it is Jank Architects. So Jank Jank Jank Jank Jank Jank Jank Jank. Jank. Jank. Jank architect.
Hurry up. Thank you, Bruce. That was lovely. Right. Let me get my windows in order. I was was thinking about Bruce at the pub last night, and I was realizing one of the He was there, so those are good reasons to be thinking about him. One of the things that I find really difficult, I would say, about talking to Bruce is quite often he's joker, as heard.
But he'll sometimes go on these long jokes that are just a sort of build up to an elaborate pun. So you'll start off and you'll say something like, did you know in my twenties, I was dating a girl from Cambodia who was a trapeze artist? I'm like, oh, fuck's sake. Okay. Yeah. So I followed her around in her traveling circus.
I yeah. Okay. And we had to go to Saint Petersburg, and I'm trying to figure out where the pun is going. I'm trying to beat him to it, you know, so I'm adding up all of the ingredients. But very occasionally then, the story will just stop, and I'll realize, no, this wasn't one of the pun ones. This was just his life experience.
And I And it's it's horrible because I then have to sort of because I'm not in the mindset of listening to the story. I'm trying to figure out the puns, I have to repause the entire conversation to to get back to talking, talking to him. Anyway, okay. That's not what I'm here to talk about. Gonna start with a fact.
The year is 2026, But it was not always so. For instance, it was once 1993. And in 1993, a man wrote an email. And this man was Tim Berners Lee.
He was looking to build out HTML too. Specifically, was interested in graphics, tables, and forms. He talked about adding a field element, and he got a reply from this man. This is Dave Raggett, who along with Tim is one of the people who shaped the early design of HTML. Now Dave wasn't too keen on this field element.
And instead, he proposed an input element for text input and a select element for choosing from a list of options. They didn't know it, but they had just invented ecommerce. A lot of the API laid out in this email is what we have today, but Dave made one thing very, very clear. The very first requirement he laid out for the select element was, as an author, I want control over how a selection menu appears.
The first time a developer asked to be able to style the Select element predated the Select element itself, predated even CSS. So Dave was going to have to wait a little bit for that one. But a mere thirty three years later and I checked on Wikipedia, Dave is still alive.
Stylable Select is landing in browsers. It's currently shipped in Chrome, but it's being actively worked on in Firefox and Safari. It's in preview versions of both of those browsers. Here's some demos. They just show the kinds of things that we couldn't do before. These demos are by Adam Argyle. You might have heard of him.
He'll be the star of the stage tomorrow. We've got images. We've got icons. We've got multiple lines of text. We couldn't do these things before. These are very practically useful demos, think, just nicely designed selects. We've got these ones by Unigravitz, you might have heard of her, another star of the stage tomorrow, showing the different kinds of layouts you can have. We've got a row here.
We've got a grid up there. It all builds up to this demo, I think, by Tamani. This lets you select a fruit if you can. There you go. There's no JavaScript here. This is just a select element in CSS. It's absolutely incredible. For the sake of this talk, though, I'm going use this as an example.
This is the one I've built. It's very derivative of Adam's examples, really. It's just a programming language picker. This is just a select element enhanced with CSS. There's no JavaScript going on here. And because of that, if you look at it in a browser that doesn't support these enhancements, you get this just regular usable fallback. It still works. So I want to cover not just how custom select works and how you can style it and how you can be used as an enhancement today, but just how we got there and the other features that we got along the way.
I'm not sure I can justify it taking thirty three years, but maybe it will become clear why it wasn't just a simple problem to solve. So back to 1993. Dave Raggett wanted control over how the select element was rendered. But the decision was made to make how it was rendered an implementation detail. And as a result, select elements looked different depending on what browser you were using, what operating system you were running.
And in many ways, was a good thing because the looks and interactions of the select elements were immediately familiar to users because it was what they'd already seen in their operating system. Although the interactions of all of these across the operating systems are all pretty similar, That would change in around 2007 with the release of the iPhone. It wasn't the first mobile device to display the web, but it marked the rise of smartphones being general consumer items, I think. The closed nature of the select element meant that smartphones could render them in a very particular way, radically different to what we'd seen before, ways that work much better for devices of that size. We've got the original iPhone skeuomorphic one here on the left, if you remember that.
The middle, we've got what Android does as a kind of overlay. And on the right there is a more modern iPhone, which is a little bit more similar to a typical Select, but still rendering quite different to how it appears on desktop. So this was seen as proof that the decision was correct, the strategy was correct. The developers didn't have control over this rendering.
Devices could do smart platform specific things, and that was a good thing. And that's kind of where things got stuck. For the next phase of the story, we need to travel to 2018, when Microsoft announced they would be abandoning their own Edge HTML rendering engine in favor of Chromium. Satya Natala, this is the Microsoft CEO.
He saw that they had a bunch of engineers working on browser stuff, but they weren't really making anything new. They were just constantly playing catch up. And he didn't feel this was an efficient use of resources when there's another engine out there that was open source that they could just use. But this wouldn't be Microsoft abandoning browser developments like they've been there before. They weren't going to do that again.
So they said that the engineers that were working on Edge HTML would now become significant contributors to the Chromium project. It didn't quite work out like that. This is a graph of Microsoft contributions to Chromium. Started off pretty good.
And then they thought, wait a minute, these engineers could be working on something we sell, like Teams or Office or something. And there we go. Although to be fair, things have gotten way better. And Microsoft is now the second biggest contributor to Chromium behind Google. So all's well that ends well.
But in this part of the story, we're actually in the first set of good times, Microsoft actively working in Chromium. Now Edge HTML was actually pretty good. It did some things that were better than Chromium, some SVG stuff in particular, some accessibility stuff, particularly accessibility around the select element. And Microsoft didn't want to regress on the engine.
They were switching engine. They didn't want to regress on things. So Microsoft engineers set about improving the select elements, particularly the accessibility. But then someone in leadership at Microsoft said, why are we doing this? I've looked around the web and no one's using the select element. So why are we spending time improving it? And they weren't far wrong.
The lack of a stylable select had led to an explosion of libraries that reimplemented the element, some of significant size and questionable accessibility. And that brings us on to Greg's survey. Meet Greg. This is Greg Whitworth. He worked at Microsoft at the time on the Edge team. And he wanted to dig into what was going on with the select element and form fields in general.
And he ran a survey. Some of you may remember it. And he found that the select element was the one most recreated by developers, but not by much. Folks were also recreating checkbox date pickers, radio buttons, that kind of thing. But you also asked developers which was the most frustrating, and that's where select won. Developers were frequently recreating checkbox and radio elements, but it's quite easy to do that. That's not true for date pickers and select boxes, and select is one of the things that comes up more often in designs. This convinced standards folks that this was a problem worth solving. Giving browsers control over the rendering of select had worked out to some extent, especially on mobile.
But the workarounds that developers were now using were ultimately compromising user experience and accessibility. So there needed to be some kind of middle ground where the browser provided the accessibility and the core interactions, but developers could style them to fit their needs. But it was tricky to find a place for that to happen, for that work to happen.
Because like styling, that happens in the W3C, the CSS working group. We've already talked about that. But form elements, they're an HTML thing, and that all happens in the HTML spec. That's the WG. So the nitty gritty work was going to have to be spread across not just two working groups, but two standards organizations. And how the specs work in between them is pretty different.
We've seen it a few times today. By the way, this is the new W3C logo. It's new last year, I think, came out. Some people don't like it. They look at the logo and say, where's the C? Where's the c in the logo? I'm like, yeah, sure. Okay. We've lost the c, but we've gained a big d, haven't we?
Actually, it divides people into people who can see it and those who can't. Let me If I spin it around, does does that can you guys can you see it now? A little peek behind the curtain. There was an internal consultation about this logo before it was released, where lots of people said, don't use this logo. It looks like genitals.
And WPC leadership, they actually had a good counterpoint. They said, no, it doesn't. Alright. And that was that. Anyway, where I was like, yes, Greg. So he knew that working between these groups was going to be hard and there needed to be some sort of bridge between them. So he started his own group. He started Open UI.
And that was formed in partnership with folks from Google and Mozilla. It became a W3C community group, which means people could join it even if they're not W3C members. So it attracted people from all over the industry that worked on this thing. But I will specifically call out Mason Fried and Joey Aja. They're both Googlers, and there's going to be many people involved in this process. But Mason, Joey, along with Greg were kind of the central characters in making this whole thing happen.
They took a look at this and they saw a series of challenges that would benefit from being solved independently. It's two separate pieces of UI for a start. The top part is the button. We call it the button because that's all it is really. You press it, stuff happens. And the bottom part we call the picker. Both of these have a markup and general styling problem to solve.
What will developers type into their HTML and what CSS things like pseudo elements and pseudo classes will be exposed. But the picker has some additional challenges around positioning and layering. And we're going to look first at layering. Take a mock up like this, where the user can choose what format a code example will be in or whatever.
If you were going to solve this, you might use a zed index based solution. And there we go. It will appear to work at first. But at some point, someone is going to add overflow hidden to something and break it. And this wasn't just a problem with custom select implementations, but all kinds of overlays. It was another problem that had spawned an entire ecosystem of libraries and hacks.
Again, some with questionable accessibility and performance. So we needed something to allow an element to stay where it is in the DOM, where it made sense for CSS and accessibility. But it needed to visually break out of its containers and render on top of everything. So a proposal was drawn up in 2021 by Melanie Richards, who was then at Microsoft.
And that was for a new element called the pop up element. This was iterated on. And in 2024, popovers became supported across all major browsers, along with invoker commands in 2025. They are commonly used together, so we're going to look at both. So here's a div.
To make it a popover, you give it a popover attribute. That's it, done. The original proposal was for a new element, but it ended up as an attribute that you can put on pretty much any element. This means the browser can look at the semantics of the elements and apply sensible defaults to the accessibility tree, which is a great thing.
I've used a div here, but you should use whatever fits best. And if there isn't a good fit, have a look at ARIA stuff, ARIA roles, attributes. You can use those too. Popovers, they are initially hidden. You can show them with JavaScript. But the best way is with a button. So there we go. There's a button, in case you didn't know.
I'm going to link it to the popover using the command for attribute and reference the ID. And then I'm going to use the command attribute, which says what I want to happen when the button is pressed. And in this case, it's going to be popover toggle. So we press the button, and the popover appears. There we go, all without JavaScript.
Now it doesn't look like much, but that's kind of great because it doesn't have any default styles that get in the way. It has some that in get the way. But it doesn't have many that get in the way. But you can style it yourself. It comes with default interactions as well. Like pressing Escape will hide the popover.
Clicking outside of it hides the popover. And all of this stuff is configurable as well, like showing one popover hides over popovers. Again, configurable. It has a smart handling of focus. If something has the autofocus attribute inside it or the popover itself has that, it will get focus when it appears. And when you close it, whatever had focus beforehand will get focus again, like just sensible defaults.
The browser also exposes a relationship between the button and the popover in the accessibility tree, which is one of the reasons it's good to use a button rather than just JavaScript, because that link up is really useful for screen readers. If you want a real deep dive into the accessibility of popovers, is the article you want. This is by Hidde de Fries, who's in here somewhere, and Scott O'Hara, who is not in here somewhere.
But this article is great. That's a QR code if you want to scan it. Scott in particular did a lot of the accessibility work around the standardization of popovers. This is the beer that Brahma sells me. Okay. The other thing that popovers solve is this clipping issue.
So if I wrap this popover in a container and I give that container width zero, height zero, overflow hidden, nothing changes. It doesn't impact the rendering. It's inside the container in terms of the DOM, which is great for the CSS cascade and accessibility. But it doesn't render inside the container. It renders in this different layer. It renders in the top layer.
And that renders after everything else and on top of everything else. But this wasn't a concept that was invented for popovers because things came before it. In 2022, the dialogue elements became supported across all browsers, which is similar to popover but for modal dialogues. It also uses the top layer to display on top of everything else and break out of containers.
It took a long time for this to become cross browser. The original spec and Chrome implementation had significant accessibility issues that took time to figure out. Work actually started on it ten years previously. This was back in 2012. It was specced by Ian Hickson, who also did the HTML5 spec. But even then, dialogue was not the first to need this top layer. There was another feature that needed to leave an element in place in the DOM but have it break out of all of its containers. And that came a year previously.
It's the full screen API. And that was developed by Robert O'Callaghan and Chris Pierce at Mozilla, along with Arna van Kestrin at Opera. So they were the first to need this extra layer, although they initially called it the Full Screen Element Stack. But they quickly realized, look, this is going be useful for more things, so we will give it a more generic name.
And that became the top layer. So my point is even these modern features that we get today are built on top of decades of platform development. Although, as we saw before, the problem with calling something the top layer is inevitably something else comes along, view transitions. And as Brahma said, we ended up having to add the topest layer, and there will be more.
We actually called it the view transition layer, which isn't as fun, right? But you can't have fun in the W3C. Except when it comes to logos apparently. I don't know. Okay, where are we at? Let's go forward in time again. So the popovers, they fixed a huge part of the problem.
Back to this mockup docs page, if this picker were a popover, which by the way is fun to say, if this picker were a popover, the picker would pop out. Although it wouldn't look like this. It would look like this. The top layer is position absolute starting at the top left of the document. And then the popovers themselves are position fixed with an auto margin, placing them right in the center of the viewport.
You can change that with CSS. Sure you can. But what we want here is a little bit more complicated. Popovers have solved layering, but we have a positioning problem. So back in 2021, Melanie Richards' original pop up proposal, she identified this problem and proposed a solution for it based on an anchor attribute. But this is a styling issue.
So the idea was developed in the CSS Working Group. And this feature is now known as CSS Anchor Positioning, which became baseline in this year. And I think it's the most complicated part of this story and one of the most complex yet powerful features to land in CSS for a long time. It's difficult to know who to credit for it, but I will call out Tab from Google, Elica from Apple, and Emilio from Mozilla.
They're kind of like the CSS working group trifecta. They do a lot of the CSS work. Not all of it, but a lot of it. So let's dive in. Let's go back to our simple popover example from before. If I give the button an anchor name, I can tell my popover to position itself relative to that button using the same name.
And I can say, position area top. Position yourself to the top of the button. And just like that, there we go. In fact, if a popover is opened via a button using things like the command attribute here, the button automatically becomes the implicit anchor for the popover. So this is enough. This does the job.
If the button moves visual position, either through animation or scrolling, whatever, that popover will stay anchored to the button. Now I know I said this stuff is complicated, and here we are with one CSS property solving everything. But there is a lot of implicit behavior packed into this. Watch what happens when I move the button towards the edge of the viewport. Click. There we go.
Oh. So it's not just solid absolute positioning. It shifts to stay visible in the viewport, which is kind of perfect, but it's just one example of the kind of magic that's hiding behind position area. So let's dive into that further. When you use position area, you are dealing with something very, very similar to a CSS grid.
Here's the grid. It extends from the anchor out to the containing block of the thing that you are positioning. So these are some of the values you can use to position things in particular cells. There are logical alternatives too. So you can say inline start, block start, which is the same as top left in this writing mode.
But yeah, all of these work. And as the anchored element or the containing block move around due to positioning, scrolling, animation, that kind of thing, that grid will change shape like this. Now each of these comes with default alignment within each cell. And here they are. That's the default alignments. But these are just using align self and justify self.
So you can alter that with CSS. Also like CSS Grid, you can span across cells. So if you wanted to position things here, this is top span left. There you go. You can even span across three cells. So this one's bottom. But this one has a new type of alignment as well because it's aligned to the top of the cell, but it's not centered within the cell. It's centered to the anchor.
And this is a new value, anchor center, that you can give both justify self and align self. But again, that's just the default. You can do other things with it. Alright, let's get back to our code from before. Here's where we're at. But let's make this work for something that looks a little bit more like a select dropdown menu kind of thing, a picker.
I'm gonna position it to the bottom, spanning right. There we go. But if I have less content in my popover like this, it's okay but could be better. I could do this. I could say I want the minimum width to be the anchor size width. This is a new function in CSS. When I do that, there we go.
So it will expand to be the size of the anchor. Starting to get somewhere. Alright, instead of using this popover thing, let's bring in the picker from before, the kind of we had. This looks Okay. But watch this. So as we saw before, the positioned element moves to stay within the viewport. It's good that it keeps the picker visible.
That's a good thing. But it kind of obscures the button, which isn't great. But anchor positioning has a solution for this as well. Position try. So here what I'm saying is if there isn't room for the popover, try flipping everything on the block axis. And the result? There we go. Wee. Move it back up. There we go. Excellent.
But I can enhance this further. Instead of the picker flipping as soon as it hits the bottom of the viewport, we can get a little bit more dynamic with the height of the picker. What I'm about to show is what I consider to be the very common basic set of height constraints that you would have for a dropdown menu or a select picker.
I mean basic because of how common I think it would be. That's a pattern that we'll end up using a lot. It's not basic in terms of CSS, so strap in. Alright, first up, rather than the menu being this static size, I want it to be able to shrink a bit when there's not enough space. So if I were to give my picker a height of stretch, which is a new keyword, this would happen.
So it stretches to fill the cell that it's in vertically anyway. This is Chrome only right now, but there is an old WebKit value, WebKit fill available, which works in Safari and Firefox. But I don't actually want this behavior because I want the picker to be its content height usually. So I will make this the max height.
And there we go. And with that in place now, as I get to the bottom of the viewport, it shrinks. But it's kind of hard to tell that that's actually happening. The only thing that sort of gives it away you might not have noticed it. You might have thought it was just overflowing the viewports, but you can kind of tell by the rounded corners that it isn't. But that is not really good enough.
So to fix this, I going to add a margin to the bottom. And there we go. It just lifts it up a bit. Although the popover has now got too small to use, this is a bad user experience, so the solution is probably what you would expect. I'm going to give it a minimum height. So here we go with the minimum height.
It shrinks a bit, and then it flips. There we go. But watch this. Up at the top, there we go. It works again. The surprising bit though is that the margin has worked. Flip block has this weird magic about it that it doesn't just flip the position, it flips a load of other values as well. So our margin bottom there has become a margin top.
This happens with some CSS properties and not with others. Good luck. So we have introduced a bug here. Let's say our menu only has two items. This doesn't look right. We would want it to be the height of the two items. The thing that's making it not the height of the two items is the minimum height that we've asked for.
What we really want is something like this, where we say, I want the minimum height to be whatever's smaller, the content size or this rem value that we've picked up. This doesn't work because you can't use intrinsic sizes in like this, in min functions like this. But you can use them in calc size. Calc size takes an intrinsic size as the first argument, like min content in this case.
And then in this second argument, you use that value. It's renamed itself. It's now size. But you use it in a calculation and that works. And there we go. That's the problem solved. But there's one more problem we want to solve here. If I add a bunch more items to the picker, like loads, it has got too long.
Is not a great user experience. We add more, if you are on a really tall screen You might have experienced this before with some country pickers that use just a normal select. Like, it fills the entire screen, especially if you're one of those freaks that has your monitor in portrait mode. We know you're out there walking among us.
So the solution to this is kind of as you would expect. We should apply a max height, but we already have one of those. It's the same problem we had before, exactly the same problem. We want two values for the max height. So the solution is the same. We use calc size. So again, I'm saying I want to calculate size, but I want to think about the stretch size.
And I want whichever is smaller, the stretch size or 25 rems or whatever we want to pick as our more static size. Problem solved. We now have a popover with sensible minimum and maximum sizes. So here we go. It goes to the bottom screen, shrinks a bit, flips, ta da, and then up to the top of the screen, the opposite will happen. And there we go.
Calc size is Chrome only right now, and it looks like both Firefox and Safari will ship custom select without calc size included. So you can either polyfill that with JavaScript or just put a kind of not quite as good, but reasonable fallback in your CSS. And then as calc size is adopted by more browsers, they'll pick up that better behavior.
Like I said, anchor positioning is a really deeply complicated feature, and it has had a rough landing in browsers. These slides are running in Chrome because I'm wanting to show demos of things that aren't in Firefox yet. I gave a shorter version of this talk back in March. And when I went to update my slides and add stuff for this one, two of my demos here were broken.
And this is running in stable Chrome. And it's only a couple of months. And things have changed in stable Chrome. That meant my demos became broken. They weren't super complicated things. This is a feature that landed in stable Chrome in 2024. But as a feature itself, it is not stable. Shit keeps changing, you know? When I was investigating one of the issues that I found on my slides, I found two more bugs.
In Sara's talk before, you see where the select picker was appearing in the wrong place compared to the select? Was that a custom select? Do you know we were using base select styles on that? Was it just a standalone? Oh, okay. I thought that might have been part of the same bug, because I've seen those kind of mispositionings happening as well.
But I often run into basic examples, not trying anything crazy, just doing what the thing was designed to do, where you get a different result in Firefox, different result in Safari, different result in Chrome. And the spec is really complicated too, so it's really hard to figure out which browser is right, if any of them. I'm not saying this to shit on Chrome or the standards process, although maybe a little bit.
But mostly just to say that if you try and use anchor positioning and it makes you feel stupid, frustrated, or confused, it is not you. It is going to take years for this to get fully stable, especially across browsers. But still, if you persevere with it, work around the bugs, you can do cool stuff with it. Yeah, just be aware that some of the articles you read have been carefully written to avoid those bugs.
It makes it look like, well, like I did here, just do this, just do this, just do this. But that is not how the author of that article or the author of these slides actually found it. Okay, we are getting somewhere. Popovers have solved layering, and anchor positioning has solved the positioning problem. Now we just need the rest to tie it all together, basically the markup and the CSS for a Styleable Select.
Now I'm thirty minutes into a forty minute talk on Styleable Select and I'm only just getting to the select part. But that's mostly because all of the complexity is in popovers and anchor positioning. We just need to bring it all together. The first thing to solve is how do developers switch from the unstylable select to the stylable one, because we can't just break the existing selects out there.
One suggestion was made back in 2021. It was by our friend Greg. He'd left Microsoft at this point. He was now working at Salesforce, but he was still doing little bits of web standard stuff in his spare time. This was his proposal. This was actually part of customizing checkboxes, but the mechanism was intended to be general. So in CSS, you would do something like appearance base, and that would opt you into a generic cross browser, cross platform styling that developers could then alter using the full functionality of CSS. And this was put before the CSS working group.
And representatives from Chrome, Safari, and Firefox gave the idea a resounding no. They didn't like this idea. They didn't like it because of how browser engines typically work. In browsers, you have a DOM tree and that gets styled with CSS and that gives you a render tree. But the render tree can have significant differences to the DOM.
For example, pseudo elements, they can introduce new boxes that are in the render tree only. They're not in the DOM tree. A single DOM node can be split into multiple boxes in the render tree due to things like page breaks, column breaks, like fragmentation, that kind of thing. Parts can be entirely missing from the render tree due to display none. The problem is in these days, modern browsers, form controls are built using DOM, specifically internal Shadow DOM.
In Firefox and Chrome DevTools, you can hit a mode and you can go and investigate that internal Shadow DOM. And because appearance base was going to change the structure of the select elements, CSS wasn't just styling the DOM. It was triggering a different DOM. And then that different DOM would then have to go back through CSS. Things were bouncing between these two models.
It was no longer a linear process, which didn't really work with how browser engines were designed. So instead of that, work began on the select menu element. And this would be a clean break, a new element without legacy parsing or styling issues. But then in 2023, there was pushback. And that pushback came from Ana van Kestrin at Apple and Henry Sivinan from Mozilla. Not the first time I've mentioned Arna.
Last time I mentioned him, he was at Opera. He's now at Apple. I know it's confusing when people go to work for another browser and basically do the same job. Anyway, Arnav and Henry, they pushed back on this select menu proposal. They felt it didn't really justify a new element. They said it would be better to go with a solution that would be a progressive enhancement on top of the existing select element.
So plans changed. Instead of a new element, stick with select, but an attribute or something like that as an opt in. And this would work fine for how browsers were built. The idea of an attribute triggering a different DOM, that happens. That happens in lots of places. That's kind of how you build a web component as well.
But then in 2024, Anna pushed back again, saying this attribute is the wrong solution. This is a styling problem. Styling happens in CSS. Making the switch happen in CSS is going to be harder for browser developers, but it is going to be ultimately better for web developers. And he was absolutely right. The CSS Working Group had to think about it, and thankfully they overturned their previous decision, which is great.
So let the record show that Greg was right all along. Good news for us is, as of this month, Greg is back at Microsoft working on web platform stuff, so we should listen to him a bit more, I guess. Welcome back, Greg. Alright, let's put this all together. Here is a select element, just usual as they come, wherever.
It looks like this. If you open it, it looks like this on a Mac anyway, scaled badly because of the high resolution screen, whatever. To make it a custom stylable select, we opt in with appearance based select. Now it looks like this. But if you click it, you still get the default picker.
If you want the stylable picker, you need to include this. You need to tell the picker to be base select as well. And now the picker looks like this. It looks kind of bad. But it's designed a little bit like popovers to have just basic functional defaults, and they're fully defined in the spec so it will be the same across browsers, across platforms.
And you can take it from there with CSS. It's kind of cool that you can just opt into one part of it as well. You can just opt into styling the button and leaving the picker as it is. And you can even make styling the picker conditional, like in a media query, for instance. If it doesn't look like there's going to be enough space for your custom picker, you can just customize the button and leave the device to do its normal thing for the select, like those things we saw with mobile devices before.
The picker is a popover. You can style it as you would any popover. It has the interactions of a popover. It interacts with other popovers as a popover would because it is a popover. It uses anchor positioning to position itself. You can use anchor positioning properties to adjust the positioning. If I make my picker display flex, which you should only do when it's open because otherwise you're overriding the default display none for when it's closed, now the options go sideways.
Great. I could even rotate them a bit. Look, it looks shit. My point is that you can throw whatever CSS you want at it, and it just works as if you were styling any other element. So I'm not here to teach you basic CSS. Know how to make a couple of boxes that look like that into that. It's just backgrounds and borders, right, and a starting style animation there. But let's look at a few things that are very particular to Select. We've got the picker icon here.
The picker icon is the arrow in the button. Now you can do what you want with that. I have replaced it there with a different, but almost the same arrow, but created with a CSS shape, which means I can animate it. Watch the arrow. Watch, watch, watch, watch, watch. There we go. Again. There we go. It is actually a diamond shape, but I am just moving the outer two points up and down, which is great fun, great fun. Look, there we go.
All right, enough of that. There is also the option check mark, which is kind of this dorky looking tick that you see on the selected option there. You can restyle it or you can hide it. I am going to hide it, Instead, I going to use the checked pseudo Class on the option element there. I can create a design like this, easy peasy.
But one of the major changes to select is in the parser. Previously, you were only allowed options, option groups, and HR elements inside the Select. But now you can have divs, too, just divs. But they become part of the picker, so you can use those for styling purposes, wrapping everything in them, whatever. But the bigger change is to Option Elements, which now allow pretty much any element inside them.
So yeah, I'm going to throw in some images in there. Great. So now it looks like that. And if I select CSS, click, there we go, the button now displays CSS. But it doesn't have the icon in there. So by default, what will happen is for the button, it will take on the text content of the option you selected, so not the image as well.
But you can change that. If you include a button as the first child of the select, it takes over the rendering of the selected item. So it would look like this. Great. You can put whatever you want in a button, images, all sorts, that kind of thing. It feels weird putting a button inside a select. It is not something we've done before, but this is fully supported.
It doesn't get announced differently to a screen reader. It doesn't get announced as an additional button. Screen reader will just see a combo box, which is what the ARIA for a select element is. So in terms of accessibility, it will get the announced value of the combo box. And that only supports text, which is an important detail.
So in my button, if I put an image in there and I put an emphasis tag and I put some text, whatever, it will render like this, which is great. But the screen reader will just hear, cats are amazing. It won't hear the emphasis on R because it loses that semantics. It's just given the value as text.
So keep that in mind when using a custom select. But the best thing to do is to look at accessibility DevTools. We've got them in Firefox or in Chrome as well. And it will show you how this stuff is being exposed to a screen reader. Alright, now you've got your button, which you can update with JavaScript. But this isn't JSDay, so I'm going to use a new element. I am going to use the selectedContent element.
This is kind of weird and unique. This element is populated with a clone of the selectedOptionsContent. It modifies its own DOM. It's not a shadow DOM thing or anything like that. It's light DOM. It's inner DOM. It just replaces it, which is really unusual. There's not really any other element that does that, which means you have a copy of the selected option that you can style however you want. I've styled it exactly the same here, but you could use the cascade to style it differently when it's in the button to when it's appearing in an option, just using regular CSS selectors.
If I select a different option, say SVG, click, there we go, the selected option clears itself out and adds a clone of the new selected option there. Cool. It just kind of works, even though it's weird. All right, the last thing I want to talk about is option group. They work, but they look bad. And you cannot restyle those labels.
You just can't. But there is a fix. You can add a legend element in there, and that takes over the rendering of the label. And once you do that, you can put whatever you want in there. You can put images and such in there and it will render in place of the label. But I'm happy with just text in my label.
I'm going to keep it as a label attribute, as a nice fallback. But I'm also going to be a little bit lazy. So what I'm going to do, I'm going to use the ator function in CSS to write the label attribute to a label custom property in the option group. And then in the legend element, in the before suit element, I'm gonna read that back and output it as content.
And that works. And I can now style it however I want. And this is new behavior. The Atta function's been around for a while, but the particular way this works, the kind of order of operations, is new. This is in the new part of the Interop twenty twenty six behaviors for Atta. It is only spot in Chrome right now, but it is behind a flag in Firefox and it will arrive in Safari as well. I know they are working on it.
It will definitely land this year because it is part of Interop twenty twenty six. And that is about it, really. It was a dream that started way back in 1993. Whole browser engines have come and gone in that time. Foundational concepts have been added to the platform.
We have popovers. We've got CSS anchor positioning, CSS starting styles, discrete animations on the display property. We can do show and hide animations, container queries, scroll queries, anchor queries, invoker commands, animating to height auto, calc size, all features that were part of the customizable select story in some way, but can be used independently. And then there's the new content model for the select itself. It took thirty three years to fulfill a requirement that was laid down in the very first select proposal, but we are finally there. Almost. Now we just need to do multi select.
That's a story for another day though. Thank you very much. Cheers.
People
- Adam Argyle
- Anne van Kesteren
- Dave Raggett
- Elika Etemad
- Emilio Cobos
- Greg Whitworth
- Henri Sivonen
- Hidde de Vries
- Ian Hickson
- Joey Aja
- Mason Fried
- Melanie Richards
- Robert O'Callahan
- Satya Nadella
- Scott O'Hara
- Tab Atkins
- Tim Berners-Lee
Technologies & Tools
- attr()
- calc-size()
- Chrome
- Chromium
- CSS Anchor Positioning
- Dialog element
- EdgeHTML
- Firefox
- Fullscreen API
- Invoker Commands
- Popover API
- Safari
- selectedcontent element
- Shadow DOM
- Stylable Select
- Top Layer
- View Transitions
Standards & Specs
- ARIA
- HTML5
- Interop 2026
Organisations & Products
- Android
- CSS Working Group
- iPhone
- Microsoft
- Open UI
- W3C
A styleable native















