Today I want to introduce you to the latest addition to CSS, specifically the CSS Overflow 5 specification. I don’t usually write about CSS features, but this one is particularly intriguing because it allows for pure CSS-based carousels — no JavaScript required! Why is this significant?
The last few years have seen great improvements in the web performance space. We have been democratizing performance for WordPress and beyond. This trend continues also in 2025. New web platform features make it easier than ever to build websites with great performance, accessibility, and developer experience.
For example, invoker commands enable building interactive UIs declaratively using only HTML attributes. Similarly, there are new enhancements in the works to easily build tooltips. They use a combination of popover="hint"
, CSS anchor positioning, and interest invokers. Full customization of <select>
elements is possible too. And now, CSS carousels.
According to HTTP Archive, over 54% of all WordPress sites use a JavaScript slider library (source). These libraries are often quite heavy, negatively impacting performance. For example, Swiper is a whopping 140KB (minified). With CSS carousels, there is no need for such libraries anymore!
Why CSS carousels are superior
Here’s why I am excited about this new web platform addition:
- Great performance
Less JavaScript means better performance for your website. This CSS-powered solution also won’t block the main thread like some of these JavaScript libraries. That means it will always perform better than any JavaScript solution. - Strong accessibility
Carousel best practices such as keyboard support are handled by the browser, so you don’t have to worry about it. Sure, you might be able to build an equally accessible carousel yourself. However, it would cost you a lot more time (and code). Plus, CSS carousels work even with JavaScript disabled. - Excellent developer experience
No external dependencies, strong accessibility, and performance out of the box. CSS carousels make maintenance a bliss, saving you lots of valuable time now and in the future.
Right now this feature is available in Chrome (as of version 135). Other browsers will probably follow soon. Meanwhile, they could fall back to existing JavaScript solutions.
Key ingredients for a CSS-only carousel
At the core of a basic carousel is a scrolling list of items (either horizontal or vertical). Ideally combined with some scroll snapping (where content “snaps” into place as you scroll) to ensure that the items are always nicely visible. It could look something like this:
<ul class="carousel">
<li><img ...></li>
<li><img ...></li>
<li><img ...></li>
</ul>
Code language: HTML, XML (xml)
.carousel {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 100%;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
anchor-name: --carousel;
> li {
scroll-snap-align: center;
}
}
Code language: CSS (css)
Carousels often have arrows on the side to go forward and backward (or up and down). With CSS Overflow Level 5, these arrows can now be easily added using the new ::scroll-button()
pseudo-elements. For example:
.carousel {
/* for the left/right arrows */
/* Note: they are actually siblings of .carousel */
&::scroll-button(*) {
position: fixed;
position-anchor: --carousel;
}
&::scroll-button(inline-start) {
position-area: inline-start center;
/* emoji/icon with alt text for accessibility */
content: "⬅️" / attr(data-previous);
}
&::scroll-button(inline-end) {
position-area: inline-end center;
content: "➡️" / attr(data-next);
}
}
Code language: CSS (css)
Similarly, if you want to add “dots” below the carousel, you can do so using the new ::scroll-marker
(the dot) and ::scroll-marker-group
(wraps all the dots) pseudo-elements. It could look like this:
.carousel {
scroll-marker-group: after;
/* Note: this is actually a sibling of .carousel */
&::scroll-marker-group {
display: grid;
grid-auto-columns: 20px;
grid-auto-flow: column;
gap: 20px;
}
/* This will add one dot for each <li> */
& > li::scroll-marker {
content: "⭕";
cursor: pointer;
aspect-ratio: 1;
&:target-current {
content: "🔴";
}
}
}
Code language: CSS (css)
Finally and optionally, you could use CSS inertness to hide any “hidden” carousel elements from the accessibility tree. This of course depends on your use case. The behavior is the same as when using the HTML inert attribute, but defined using CSS. Example:
.carousel {
> li {
container-type: scroll-state;
> .card {
@container not scroll-state(snapped: x) {
interactivity: inert;
opacity: .25;
}
}
}
}
Code language: CSS (css)
These are all the basic components you need to create a carousel using modern CSS. The result is pretty impressive:



If you are using Chrome 135 or later, you should see a carousel complete with arrows, dots, and keyboard support. In other browsers you simply get a swipeable set of images. Here’s a screenshot:

But CSS carousels can do a lot more. If you would like some inspiration, I highly recommend checking out the Carousel Gallery site with tons of incredible demos. Each demo covers a real use case found on the internet. They all show how to orchestrate scroll buttons and markers with scroll-driven animations, scroll-state()
queries, and much more. Here’s one of the demos of a e-commerce product image gallery using a CSS carousel:
Want to get more hands on and dive right in? Try the Carousel Configurator to help visualize the capabilities of CSS-only carousels.
Building a CSS carousel WordPress block
As I mentioned earlier, over 54% of all WordPress sites use at least one of the popular slider libraries. So it’s no wonder that there have been discussions about introducing a first-party carousel block for many years. One of the main blockers was of course the reliance on JavaScript.
In 2025, building a performant, accessible, and maintainable carousel using only CSS finally becomes a breeze. To prove it, let’s apply these new features in WordPress by building a custom carousel block!
Tip: If you want to see the final result, check out this GitHub repository.
Setting up a carousel block variation
For the sake of simplicity, I am not going to build a completely new block from the ground up. Instead, I am leveraging the Block Variations API to make a carousel a variation of the gallery block. This saves a lot of boilerplate code.
The nice thing about block variations is that I can set them up entirely using PHP. I only need some JavaScript to update the block’s HTML class attribute for properly targeting the carousel variation using CSS. Here’s an excerpt:
/**
* Adds carousel variation to gallery block.
*
* @param array $variations List of all block variations.
* @param WP_Block $block_type Block type instance.
* @return array Filtered list of block variations.
*/
function css_carousel_block_add_carousel_variation( $variations, $block_type ) {
if ( 'core/gallery' !== $block_type->name ) {
return $variations;
}
$variations[] = array(
'name' => 'carousel',
'title' => __( 'Carousel', 'css-carousel-block' ),
'description' => __( 'A carousel', 'css-carousel-block' ),
'scope' => array( 'block', 'inserter', 'transforms' ),
'isDefault' => false,
'attributes' => array(
'displayType' => 'carousel',
),
);
return $variations;
}
add_filter( 'get_block_type_variations', 'css_carousel_block_add_carousel_variation', 10, 2 );
Code language: PHP (php)
Check out the GitHub repository for the full code.
CSS carousels in the editor
The block markup on the frontend and in the editor is very similar. That means we can use the same CSS in both places, making for a true WYSIWYG experience. Additional bonus: we do not have to use any janky JavaScript library in the block editor that messes with React. The result:

While the CSS carousel already works great in the editor, there are some minor differences to the frontend.
For example, the translated, human-readable labels for the scroll markers (arrows) are added via data-*
attributes on the frontend. This way, they can be used in the content
CSS property. Unfortunately, I couldn’t find a suitable filter in the editor to achieve the same.
Second, in the editor we need to cater for the gallery block’s inner image blocks and their block toolbar. It looks odd when the toolbar stays in place when swiping through the carousel. This would be a great opportunity to use new scroll snap events such as scrollSnapChange
. We can hide the toolbar when swiping and automatically select the next block upon slide change.
Further iterating on this CSS carousel block
Turning a basic gallery block into a feature-rich carousel is surprisingly easy. But of course this is a mere proof of concept. There is a lot more that could be done.
For instance, the scroll buttons and markers in this demo are positioned using the new CSS anchor positioning API. This requires each carousel to have its unique anchor name. That’s something I haven’t implemented yet in this proof of concept.
In addition to that, cross-browser support is an important consideration when implementing such new features. Since CSS carousels are currently only supported by Chrome, there could be a fallback solution using JavaScript in other browsers. This is very easy to implement. Check out this pull request for the Twenty Fourteen theme for an example.
It’s also worth mentioning that work on CSS carousels is still ongoing. For example, work is already being done to let you add your own components for scroll buttons and markers. This makes it easier to add custom SVG icons or use Tailwind classes. Another highly requested feature is support for cyclic scrolling. Right now this still requires JavaScript.
Share your feedback
Long story short, CSS carousels are really powerful. WordPress users and contributors have been wanting a carousel block for years. This proof of concept could be the start of it. And if you are a developer who builds carousels for clients’ websites, why use this new feature in your next project? That’s why I would love for you to try it out, provide feedback, and help make it happen.
Leave a Reply