6 Modern CSS Features That Can Reduce Your JavaScript in 2026
From customizable dropdowns to scroll-driven animations, modern CSS can do much more than you might think.
CSS has come a long way.
Things that once required JavaScript can now be done with just a few lines of CSS. And honestly, that’s one of the most exciting things happening in frontend development right now.
We used to rely on CSS for styling and JavaScript for interactions. But that line is becoming less clear. Today, CSS can handle more complex tasks, including scroll animations, popovers, element positioning, and even transitions between pages.
This doesn’t mean JavaScript is going away. It means we have more tools to choose from, and sometimes, the simplest solution is already built into the browser.
Let’s look at six modern CSS features that can help you write cleaner code and reduce unnecessary JavaScript.
1. Customizable <select> Dropdowns
Styling a normal HTML dropdown has always been a little frustrating.
You might want rounded corners, custom colors, or a dropdown menu that matches the rest of your design. But native dropdowns can look different across browsers, making it difficult to get the exact appearance you want.
That’s why developers often build custom dropdowns using JavaScript. The downside? More code to maintain and more work to keep everything accessible.
Modern CSS gives us another option.
With appearance: base-select, supported browsers let you customize more parts of a native <select> element while keeping its built-in functionality.
Example: A custom assignee dropdown
HTML
<label for="assignee">Choose assignee</label>
<select id="assignee">
<option value="alex">Alex Morgan</option>
<option value="sarah">Sarah Lee</option>
<option value="john">John Carter</option>
</select>CSS
select,
select::picker(select) {
appearance: base-select;
}
select {
padding: 12px 16px;
border: 1px solid #ccc;
border-radius: 10px;
background: white;
font: inherit;
cursor: pointer;
}
select::picker(select) {
padding: 6px;
border: 1px solid #ddd;
border-radius: 10px;
}
option {
padding: 10px;
border-radius: 6px;
}What’s happening here?
appearance: base-selectenables the customizable appearance.select::picker(select)lets you style the dropdown menu.optionlets you customize individual choices.
The result is a dropdown you can style more freely without building the entire component from scratch.
When to use it: When you need a simple dropdown that matches your design without the overhead of a custom JavaScript component.
Note: Customizable select features vary by browser. Test your design in your target browsers before using it in production.
2. Scroll-State Queries
Have you ever wanted your navbar to change its appearance as soon as it sticks to the top of the screen?
Traditionally, this meant using JavaScript scroll listeners or an IntersectionObserver to detect the navbar's position and update its styles.
Now, CSS has another option: scroll-state queries.
They let you apply styles based on an element’s scroll state, such as whether a sticky element has become stuck to the top of its container.
Example: A navbar that changes when you scroll
Imagine a navbar that starts with rounded corners and a limited width. Once it sticks to the top, it expands across the screen and gains a shadow.
CSS
.nav-wrapper {
position: sticky;
top: 0;
container-type: scroll-state;
}
.nav-wrapper nav {
width: 800px;
max-width: 100%;
margin: auto;
border-radius: 12px;
transition:
width 0.3s,
border-radius 0.3s;
}
@container scroll-state(stuck: top) {
.nav-wrapper nav {
width: 100%;
border-radius: 0;
box-shadow: 0 2px 10px rgb(0 0 0 / 10%);
}
}How does it work?
position: stickykeeps the wrapper at the top while scrolling.container-type: scroll-stateallows CSS to check its scroll state.stuck: topdetects when the wrapper becomes stuck at the top.- The navbar changes its width, corners, and shadow when that happens.
No JavaScript scroll listener is needed for this effect.
When to use it: When you want sticky elements to respond to their position without writing extra JavaScript.
Note: Scroll-state queries have more limited browser support than many established CSS features, so check compatibility before using them.
3. Scroll-Driven Animations
Here’s a small detail that makes a website feel much better: a reading progress bar that fills up as you scroll through an article.
It sounds simple, but traditionally, developers used JavaScript to track the scroll position and update the bar as the user moved down the page.
With scroll-driven animations, CSS can handle this directly.
Instead of running an animation according to time, you can link its progress to how far the user has scrolled.
Example: An article reading progress bar
HTML
<div class="progress-bar"></div>CSS
.progress-bar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: #2563eb;
transform-origin: left;
scale: 0 1;
animation: progress linear;
animation-timeline: scroll(root);
}
@keyframes progress {
to {
scale: 1 1;
}
}What’s happening here?
position: fixedkeeps the bar at the top of the screen.animation-timeline: scroll(root)links the animation to the page's scroll progress.scale: 0 1starts the bar at zero width.- As you scroll down, the bar grows until it reaches full width.
The animation follows your scrolling rather than running on a fixed timer.
You can use the same idea for other effects, such as revealing content, animating images, or creating parallax-style movement.
When to use it: When an animation should follow the user’s scrolling instead of playing automatically.
Note: Scroll-driven animations work in modern browsers, but support can vary. Check compatibility for your target audience.
4. The Native Popover API
Have you ever built a notification popup, a small menu, or a user profile card?
Get kavinesh_velmurugan’s stories in your inbox
Join Medium for free to get updates from this writer.
You probably know the usual process: write JavaScript to open and close it, manage its visibility, and make sure it appears above other elements.
For simple popups, you might not need all that code.
The HTML Popover API lets you create a popup using built-in browser functionality. You can then use CSS to control its appearance and add animations.
Example: A notification popup
HTML
<button popovertarget="notify-box">
View Alerts
</button>
<div id="notify-box" popover>
<p>You have 3 new messages.</p>
</div>That’s all the HTML you need for a basic working popup.
CSS
#notify-box {
padding: 1rem;
border: 1px solid #ddd;
border-radius: 10px;
background: white;
box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
opacity: 0;
transform: translateY(-8px);
transition:
opacity 0.3s,
transform 0.3s,
display 0.3s allow-discrete,
overlay 0.3s allow-discrete;
}
#notify-box:popover-open {
opacity: 1;
transform: translateY(0);
}
@starting-style {
#notify-box:popover-open {
opacity: 0;
transform: translateY(-8px);
}
}How does it work?
popovertargetconnects the button to the popup.popoverenables the browser's built-in popup behavior.:popover-opentargets the popup while it's open.@starting-styledefines the initial state for the opening animation.allow-discreteallows transitions involving properties such asdisplaywhen supported.
The browser also handles useful behavior. An automatic popover can close when you click outside it or press Escape, and it appears on the browser’s top layer.
When to use it: For notification panels, simple menus, help popups, and other lightweight overlays.
You can build the basic behavior without writing JavaScript event handlers. More complex interactions may still need JavaScript.
5. CSS Anchor Positioning
Positioning a tooltip or dropdown next to a button sounds easy until the layout changes.
Maybe the button moves, the screen gets smaller, or the content around it changes. Keeping the floating element in the right place can take extra work.
That’s why developers often use JavaScript libraries such as Popper or Floating UI to calculate positions.
CSS Anchor Positioning offers a native alternative.
It lets you position one element relative to another using CSS, without having to calculate the coordinates yourself.
Example: A tooltip above a button
HTML
<button class="anchor-button">
Hover over me
</button>
<div class="tooltip-popup">
Here's some helpful information!
</div>CSS
.anchor-button {
anchor-name: --my-anchor;
}
.tooltip-popup {
position: absolute;
position-anchor: --my-anchor;
bottom: anchor(top);
left: anchor(center);
transform: translateX(-50%);
padding: 10px 14px;
border-radius: 8px;
background: #222;
color: white;
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.anchor-button:hover + .tooltip-popup,
.anchor-button:focus-visible + .tooltip-popup {
opacity: 1;
visibility: visible;
}How does it work?
anchor-namegives the button a name that another element can reference.position-anchorconnects the tooltip to that button.bottom: anchor(top)positions the tooltip above the button.left: anchor(center)aligns the tooltip with the button's center.transform: translateX(-50%)centers the tooltip horizontally.
The tooltip can follow the button’s position without JavaScript calculations.
When to use it: For tooltips, floating labels, contextual menus, and other elements that need to stay positioned relative to a target.
Note: This example demonstrates positioning and visibility, not a complete accessible tooltip component. Production tooltips may need additional keyboard, touch, and accessibility handling. Anchor positioning support also varies by browser.
6. View Transitions for Multi-Page Websites
Imagine clicking a product thumbnail and watching it smoothly expand into the large image on the product details page.
That kind of transition can make a website feel polished and responsive. In the past, creating it often involved JavaScript or a framework with custom transition logic.
The View Transitions API makes these animations possible with built-in browser support, including for traditional multi-page websites.
Example: Transitioning a product image between pages
On the product listing page:
.product-card-thumb {
view-transition-name: hero-image;
}On the product details page:
.product-detail-hero {
view-transition-name: hero-image;
}To enable transitions for supported full-page navigations, add this to the CSS on both pages:
@view-transition {
navigation: auto;
}How does it work?
view-transition-namegives the image a name that connects it across the two pages.@view-transitionenables automatic view transitions for supported same-origin navigations.- The browser captures the old and new views and animates matching elements between them.
Because both images share the same transition name, the browser can animate between their positions and sizes.
The result? Moving from a product listing to a product details page can feel like one continuous experience instead of an abrupt page change.
When to use it: For product galleries, image expansions, page navigation, and other transitions that make moving between views feel smoother.
Note: The relevant elements must have matching transition names on their respective pages. Browser support and navigation requirements apply.
Final Thoughts
CSS is becoming much more powerful than it used to be.
Features like scroll-state queries, scroll-driven animations, the Popover API, anchor positioning, and view transitions give frontend developers new ways to build interactive experiences with less code.
That doesn’t mean JavaScript is no longer necessary. Complex application logic, dynamic data, and interactions that go beyond built-in browser behavior still need JavaScript.
But before reaching for a library or writing another event listener, it’s worth asking a simple question:
Can modern CSS handle this for me?
Sometimes, the cleanest solution is already built into the browser.
If you’re a frontend developer, try experimenting with these features in a small project. You might be surprised by how much you can build without adding another JavaScript dependency.








