If you’ve been building websites for a while, you probably have some established opinions about CSS. It’s the obvious choice for design and layout, but once things get more interactive than a basic animation, most developers instinctively reach for JavaScript. However, thanks to an ever-evolving platform, “CSS for design and JavaScript for interactivity” no longer has to be the default when building features for the web.
Modern CSS is now powerful enough to handle complex animations and user interactions that used to require custom scripts. In this article, we’ll explore some of the latest features landing in CSS and how they can simplify your development workflow while still giving you the level of interactivity you’re used to with JavaScript.
Imagine a world where you can build fully customizable <select> elements without writing any JavaScript, or create a scrollable carousel with interactive markers without calculating scrollLeft or wiring up multiple event listeners. That’s exactly what’s becoming possible with these new CSS features, and it’s definitely something to look forward to.
What’s new in CSS in 2026?
Let’s take a look at some of these latest features and how we can implement them in our codebase.
Note: Most of these features are very new and still rolling out across browsers, so they aren’t suited for production use just yet. For a deeper dive into how these features work, check out the CSS Wrapped 2025 article from the Chrome DevRel team:
| Feature | What it does |
|---|---|
appearance: base-select |
Opts a <select> into the new customizable mode so you can style it and its picker without losing native behavior. |
select::picker(select) |
A pseudo-element representing the dropdown surface of a customizable <select> that you can style (shadows, borders, spacing). |
selectedcontent |
Styles the selected option shown in a select field |
::scroll-button() |
A generated button for scrollable containers that scrolls left or right when clicked. |
::scroll-marker |
A generated element paired with each scroll item that a browser can use as a pagination dot or visual indicator. |
::scroll-marker-group |
A container for the generated scroll markers so they can be styled/placed (e.g., bottom center). |
scroll-target-group |
An attribute you can apply to a container of links so CSS can match the link whose target is currently in view (:target-current). |
:target-current |
A selector that matches a link (or other targetable element) whose target (ID anchor) is currently the scroll-active element. |
container-type: scroll-state |
Enables scroll state queries on an element, letting CSS react to whether a scroll container is in a specific snapped or stuck state. |
@container scroll-state(snapped: x) |
A container query that applies styles when the container is in a “snapped” scroll position on the x-axis. |
sibling-index() |
Returns the 1-based position of an element among its siblings, useful for dynamic delays and layout rules. |
sibling-count() |
Returns the total number of sibling elements, useful for count-based layouts or proportional styling. |
attr() |
A typed attr() function call that reads an attribute as a color for use in properties like background-color. |
@starting-style |
A container query-like block used when defining styles at the start of a transition or animation sequence (experimental syntax). |
Now let’s take a look at a real-world use case with some of these new features.
Demo: Customizing the native HTML select
This is a feature I’ve honestly been anticipating for years, so let’s see how it works in practice. The <select> element is the browser’s built-in solution for an accessible dropdown, but styling it has always been limited. If you wanted to do anything more advanced than adjusting padding or changing colors, you’d typically end up building a fully custom dropdown with extra markup, JavaScript handlers, and all the complexity that comes with trying to mimic native behavior.
Customizable selects let you combine the best of both worlds: the native accessibility and semantics of a real <select> with the kind of styling flexibility we previously only got from JavaScript-powered components.
In the demo below, we’ll use three newer CSS features to build a Pokémon selector that:
- Uses the native
<select>for semantics and accessibility - Shows an icon and background color for each option
- Animates options sliding into view in a staggered way:

Note: these features require Chrome 135+:
See the Pen
Customizable HTML select by Jemima (@Jemimaabu)
on CodePen.
All of this is done with a single <select> element and a few data-* attributes. The interactivity comes from these features: appearance: base-select (plus the select picker), tree counting functions, and typed attr().
appearance: base-select and the select picker
The first step is switching the control into its customizable mode:
select,
select::picker(select) {
appearance: base-select;
}
appearance: base-select opts your <select> into the new customizable rendering model, which also makes it a solid progressive enhancement approach. Browsers that don’t support it will simply ignore the property and render the select normally.
Once you’ve opted in, the ::picker(select) pseudo-element represents the dropdown surface itself, so you can style it like any other UI panel:
select::picker(select) {
margin-block-end: 1em;
border-radius: 12px;
border: 1px solid #e0e0e0;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
}
With customizable selects, the browser handles a lot of the complexity of dropdowns for you, including:
- Automatic overflow handling: If the dropdown is taller than the available space, the browser handles the overflow for you. That includes constraining the picker height and making it scrollable when needed, rather than letting it spill off-screen or forcing you to calculate heights manually.
- Anchor positioning fallback: The picker is anchored to the trigger element using the new anchor positioning syntax, so the dropdown is able to automatically choose the best placement based on available viewport space:
-


- Built-in focus management: Focus behavior is handled natively, so when the select opens, focus moves into the picker in a predictable way, and when it closes, focus returns appropriately. You do not need to implement your own focus trapping, or “return focus to trigger” logic.
- Full keyboard navigation (Arrow keys, Enter, Escape): Users can navigate options with the Arrow keys, confirm a selection with Enter, and dismiss the picker with Escape, just like a normal
<select>. - Support for richer option content: You can include more than plain text inside each
<option>, like icons, additional labels, or structured content.
These are things you would normally have to script manually when building a custom dropdown, but here they come straight from the platform.
Another major advantage is the built-in progressive enhancement model. If a browser doesn’t support customizable selects yet, the user simply gets the regular native <select> element. Nothing breaks. There’s no polyfill required and no need to maintain two versions of the component.
Staggering animations with sibling-index()
Next, we add animation. When the dropdown opens, each option slides in from the side with a small delay. Instead of manually assigning an index to every option, we can use a tree counting function:
option {
transition:
opacity 0.25s ease,
translate 0.5s ease;
transition-delay: calc(0.2s * (sibling-index() - 1));
@starting-style {
opacity: 0;
translate: 30px 0;
}
}
sibling-index() gives you the 1-based position of an element among its siblings. That means the first visible option gets a delay of 0.2s * (1 - 1),which is 0s. The next is 0.2s, then 0.4s, and so on.
If you add or remove options later, the animation still looks correct because the timing is calculated dynamically instead of being hard-coded in the markup.
Before tree-counting functions, achieving the same staggered effect in CSS was usually much clunkier. You either had to hard code delays with a long list of :nth-child() selectors, or manually add an --index custom property to every item in your HTML. Both approaches worked, but they got noisy fast, and they were easy to forget to update when the list changed.
Advanced attr() for data driven styling
Finally, the demo uses the typed attr() function to keep visual details in data-* attributes.
The attr() function has been baseline available for a while. But until recently, it was only reliably usable for the content property.
With the newer typed version of attr(), we can start using attribute values in more places in CSS, as long as we tell the browser what type to expect.
In this demo, each option includes a data-bg-color attribute that defines its hover background color, and we read that value directly in CSS:
//HTML
<option data-bg-color="#F8C9A0" value="charmander">
//CSS
option {
background-color: attr(data-bg-color color, transparent);
}
Because we’re explicitly treating the attribute as a color, the browser parses it correctly, and we can safely provide a fallback value if the attribute is missing. The result is a more data-driven styling approach: you can tweak theme colors in your HTML without touching the CSS.
Together, appearance: base-select, the select picker, tree counting functions, and typed attr() make it possible to build a rich, animated dropdown that is still fundamentally a real <select> element. So you can have the customizations you want while still keeping native behavior and built-in accessibility features.
In comparison, here’s what a similar dropdown built using JavaScript would look like (spoiler: it’s roughly 150+ lines of JavaScript to achieve a close-enough example of what we’ve built with CSS
):
See the Pen
Custom Select with JavaScript by Jemima (@Jemimaabu)
on CodePen.
CSS in 2026: What the landscape looks like moving forward
For me, this demo shows the most exciting part about where CSS is heading. Replacing 150+ lines of JavaScript with just a few CSS features is genuinely wild. We’re able to achieve the same amount of complexity that we’ve always had, but now it’s a lot less work to do so.
When the platform provides defaults like keyboard navigation, focus handling, and sensible positioning behavior, we can spend more time on improving existing components (instead of rebuilding the same interaction patterns in every codebase or installing a new library every time).
This also feels especially relevant in the age of AI. The simpler and more declarative these features are, the less likely an agent is to over-engineer a solution or invent behavior you didn’t ask for.
If you want to start taking advantage of these features as they land, here’s what I’d recommend:
- Revise one JavaScript-heavy UI component: The best way to start working with these new features is to see where you can use them in already implemented features in your codebase. Carousels, tooltips, and dropdowns are great candidates because they’re often a lot of code for a relatively small piece of UI, and most of the native features to build these are already baseline supported.
- Keep accessibility in the loop: Native support usually gives you a better starting point, but it doesn’t replace testing. Try your demos with a keyboard and use screen readers where you can to ensure that these features are able to be used by everyone.
- Stay curious: With a lot of these new features, they’re never fully baseline supported until they are. The easiest way to keep up to date is to keep an eye out for the latest changes in these features (I recommend using the web.dev blog), so you know when you can finally implement them in production. Watch browser support, experiment in internal tools, and keep the production rollout conservative until support stabilizes.
And personally, if it means I get to write less code while CSS does all the heavy lifting, I’m very on board with that.
The post CSS in 2026: The new features reshaping frontend development appeared first on LogRocket Blog.






