Skip to main content

Command Palette

Search for a command to run...

Should We Really Build Carousels Without JS?

Trade-offs, accessibility concerns, and what CSS alone can’t solve

Updated
6 min read
Should We Really Build Carousels Without JS?
M

Design Engineer | WTM Ambassador

CSS-only carousels truly feel exciting and highlight so much about the power of modern CSS. It simplifies the creation of carousels from something which used to be about >200 line of codes to a few lines. This is because modern CSS handles interactivity with the scroll-snap-* properties. Some other new properties including the ::scroll-marker(), and the proposed interactivity: inert now go a step further and give this interactivity some kind of accessibility.

While modern CSS provides the visual and structural foundation for carousels, a semantic JavaScript "patch" might still be necessary today to fulfill critical Web Content Accessibility Guidelines (WCAG) requirements. It would ensure that cross-browser compatibility, focus management, and other kinds of accessibility-related issues are handled for all users. We'll be looking some more into these issues in this article.

The Power and Limitations of Pure CSS: How Do CSS-Only Carousels Even Work?

The way CSS carousel works hinges on these features: the scroll-snap-type, scroll-snap-align and the overflow-x. As you would likely know, the overflow property is used to control how content is handled when it exceeds the boundaries of its containing element. In this case, it is used to enable scrolling when set to scroll, so that hidden carousel slides can be exposed by horizantally scrolling. This also means that slides which are not currently in view are "off" the screen, but still exist. This article gives a detailed overview of how CSS-only carousels work: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_overflow/CSS_carousels

The CSS Scroll Snap Module , allows the creation of smooth carousels that “snap” perfectly into place as users scroll. This is done accurately and purely without JavaScript. This is possible because scroll-snap effectively assigns the complex behavior of the carousel to the browser's own scrolling engine, which is faster and more efficient.

The scroll-snap-type goes on the parent element (the carousel wrapper) that holds all the individual slides. It tells the browser that this element is a scroll container and it should utilize the snap behavior. On the other hand, the child elements (the individual slides) are tagged with the scroll-snap-align property. It specifies where on the slide the snap should align relative to the container/wrapper. So, when the user lifts their finger or releases the mouse, the browser automatically calculates the nearest center alignment point. The browser then uses a smooth, hardware-accelerated scroll to accurately land on that point, ensuring only one slide is fully visible and perfectly centered, copying the behavior of a traditional JavaScript carousel.

But here's the thing:

Building a CSS-only carousel like this can create a very frustrating user experience to users who use Assistive Technologies (AT), even though everything appears smooth on the surface. This brings us to the next part of this article.

The Semantic Gaps: Where CSS Falls Silent

  1. The first we'll look at is Focus Traps: The ATs and the browser itself which controls the snapping still typically see and recognize off-screen content as part of the page's document order. Screen readers and keyboard users would still be able to tab to an interactive element (like a button or link) that is visually off-screen, creating a frustrating experience known as a "focus trap" issue. This is against the WCAG 2.4.3 (Focus Order) accessibilty guideline. Further going a bit deeper, we also see that this is where the HTML inert property comes in. It puts elements in an 'inert' state. The inert property tries to manage focus and interaction for off-screen content. It does this by:

    • Interaction Blocking: It prevents hidden elements from being unintentionally activated using mouse clicks or other input methods.

    • Accessibility Tree Noise: It removes off-screen content from the accessibility tree, preventing screen readers from announcing content the user can't see.

How JS fixes this: By dynamically applying inert to off-screen slides, keyboard users cannot tab into invisible elements, this helps to maintain a logical focus order for visible elements. One common method used is the combination of IntersectionObserver + inert.

  1. Inability to Update ARIA Live Regions: CSS cannot dynamically update the DOM or trigger programmatic announcements. This is a significant technical limitation. It cannot alter the HTML structure, modify ARIA attributes, or intercept and control user input (like keyboard presses) to provide the necessary semantic information and interaction required by WCAG (Web Content Accessibility Guidelines). This is against the WCAG 4.1.2 (Name, Role, Value) and the lack of programmatic context for screen reader users. This is fundamental because it requires that for all user interface components (including hidden states, like a slide changing), the name and role can be programmatically determined, and that changes in these items are announced to the user. For example:

    A change happens when the carousel slides from one image slide to the next. This is a crucial change in "value" (the content currently being displayed). There needs to be an announcement like "Image Slide X of Y" when this change occurs, but it fails because the state change is managed solely by visual scrolling in CSS, so the browser does not recognize a programmatic change to announce.

    The JS Solution: This solution uses an IntersectionObserver to detect the exact moment a slide comes into view, this would then trigger the script to manually update a visually-hidden, aria-live="polite" element with the current position (e.g., "Slide 3 of 5"). This forces the screen reader to announce the state change, fulfilling the communication requirement of WCAG 4.1.2.

  2. The Missing Pause/Stop Functionality: This is a non-visual requirement. If the carousel automatically rotates or moves, CSS cannot provide a control to stop the animation. This goes against the WCAG 2.2.2: (Pause, Stop, Hide) accessibility requirement.

    How JS Fixes this: A single line of JS to toggle the CSS animation-play-state property and also update the button's aria-label (e.g., from "Pause Slideshow" to "Play Slideshow").

  3. Incorrect ARIA Role (Semantic Mismatch) : Another major issue as pointed out by Sara Soueidan is incorrect ARIA Role which happens because the Browser exposes Carousel UI elements as ARIA "Tabs" and "Tablist". The scroll markers become role="tab".

    What this means is that the browser sees interactive scroll markers and makes a faulty assumption based on existing ARIA patterns (tabs, buttons, links). It defaults to the tablist pattern, which is wrong for a simple image carousel or gallery.

    The JS Solution: JS is required to override the browser's guess by adding clear ARIA roles (e.g., role="group", aria-roledescription="carousel").

Conclusion

So far, we see how CSS only carousels are not fully usable and might need the semantic JS fixes to properly cater for every user. It fails some important usability and accessibility checks.

The reality is that CSS is a styling engine, not a semantic language. When a carousel is created using CSS features like overflow-x and scroll-snap-type, we achieve a powerful visual component. However, the component is still semantically and functionally incomplete for assistive technology users. The browser simply cannot be relied on to correctly handle complex interactive requirements like focus management across hidden slides, programmatic announcement of state changes, etc

The use of JavaScript is not just a fix neither is it a failure of CSS; it is a good semantic patch that closes the gap between usability, performance and compliance.

This hybrid model shows how CSS dictates the motion and performance, while a few lines of JavaScript would help with the critical accessibility requirements for a carousel.

References:

  • https://www.w3.org/TR/css-scroll-snap-1/#overview
  • https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_scroll_snap/Basic_concepts
  • https://www.w3.org/TR/css-scroll-snap-1/#overview
  • https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert
  • https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
  • The proposed CSS inert property
  • https://developer.mozilla.org/en-US/docs/Web/CSS/::scroll-marker