Carousel
A carousel displays a sequence of slides in a scrollable track. It is commonly used for featured content, hero sections, and media galleries.
Ready to use
The Carousel Progress Indicator is a high-impact navigation element for hero sections and featured content. It gives visual feedback on how long the current slide stays on screen and lets users jump between the key content pillars. In the CDS Figma library it is specified on its own as the row of timed control bars; the styleguide wraps that indicator inside a full carousel of slides.
Anatomy
Section titled “Anatomy”- Carousel
- Track
- Slide
- Progress indicator
- Control
The CDS Figma source documents only the progress indicator — the row of controls pinned above. Each control is a timed bar with an optional label, and the active one fills over time. The styleguide expands that indicator into a full carousel: a track of slides with the progress indicator overlaid at the bottom, so the pins cover both the broader carousel and the indicator Figma specifies.
When to use it
Section titled “When to use it”Use the progress indicator when:
- Content is high-impact and browse-first — hero sections, featured galleries, and campaign pillars where a timed, self-advancing rhythm suits the content.
- Users benefit from seeing how long a slide stays on screen and being able to jump straight to another pillar.
Avoid it when:
- The task is functional or utility-driven (checkout, a settings menu, a complex form). Users should never feel rushed by a disappearing interface.
- A slide carries more than a couple of sentences or dense data — people read at different speeds, and an auto-playing timer frustrates slower readers.
- The set is large (roughly more than seven to ten items). High item counts make each bar too narrow to read or tap; reach for standard pagination or a “view all” gallery instead.
- The imagery behind it is low-resolution or low-contrast. The indicator draws the eye to the background and will highlight weak assets rather than hide them.
Properties
Section titled “Properties”State
Each control moves through four states. Rested shows a thin, muted bar. Hover thickens the bar and shifts it to the hover colour on pointer devices for any non-active item. Focus draws the CDS focus ring around the item for keyboard users. In progress / Active thickens the bar and fills it left-to-right to mark the current slide and how much of its time remains.
Scheme
Neutral or Contrast. Because the indicator usually overlays imagery, Contrast flips the bar and label to the light foreground tokens so they stay legible on dark media; Neutral keeps the dark foreground for light surfaces. It follows the CDS scheme set on the surrounding wrapper.
Label
Each control can show a short text label beneath its bar (on by default) that names the slide it jumps to. Hide the label to fall back to bare progress bars when horizontal space is tight.
Progress & timing
The active control’s bar animates from empty to full over a set duration (5 seconds by default) to signal the slide’s remaining time before it advances. Under reduced-motion preferences the active bar renders full with no animation, so the current slide is still marked.
Platform considerations
Section titled “Platform considerations”Desktop
The controls sit in a horizontal row, evenly spaced, overlaid at the bottom of the hero. Hover thickens each bar and a visible focus ring supports keyboard navigation between pillars.
Tablet
Touch and pointer both work. Keep labels short so neighbouring bars don’t overlap, and make sure the indicator stays clear of any content sitting over the same imagery.
Mobile
Touch-first, and the tightest constraint. Thin horizontal bars can be hard to tap accurately — keep the tap target at least 44 × 44 px, trim the item count, or switch to a count indicator when the bars would get too small.
Best practices
Section titled “Best practices”The indicator earns its high-impact styling only when the content behind it can carry the attention it draws.
Do
Reserve it for high-impact hero and featured content, keep labels short (roughly 10–12 characters) so they don’t wrap or overlap, use the Contrast scheme when it overlays dark imagery so the bar and label stay legible, and keep the item count low so each bar stays wide enough to read and tap.
Don't
Don’t use a timed carousel for functional tasks like checkout, settings, or forms, don’t auto-advance slides that are dense with text or data, don’t exceed roughly ten items (the bars become too small to be useful or accessible), and don’t overlay the indicator on low-quality or low-contrast imagery.
Content guidelines
Section titled “Content guidelines”Labels are pillar names, not sentences — keep each to a short word or two (about 10–12 characters), like “Overview”, “Models”, or “Offers”. Aim for a consistent length across the set so the bars stay evenly weighted, and localise the labels through the same strings used elsewhere in the product.
Styles
Section titled “Styles”<div class="tng-carousel"> <div class="tng-carousel-track"> <div class="tng-carousel-slide"> <div class="tng-slot tng-text-body" style="block-size: 300px"> Slide 1 </div> </div> <div class="tng-carousel-slide"> <div class="tng-slot tng-text-body" style="block-size: 300px"> Slide 2 </div> </div> <div class="tng-carousel-slide"> <div class="tng-slot tng-text-body" style="block-size: 300px"> Slide 3 </div> </div> </div></div>Elements
Section titled “Elements”Carousel controls
Section titled “Carousel controls”Carousel controls render a list of item buttons used to navigate between slides. The active item displays a top progress bar that fills over time.
Use position utilities to overlay them on the carousel: add p-relative to .tng-carousel and p-absolute at-bottom inset-lg to .tng-carousel-controls.
<div class="tng-carousel p-relative"> <div class="tng-carousel-track"> <div class="tng-carousel-slide"> <div class="tng-slot tng-text-body" style="block-size: 300px"> Slide 1 </div> </div> <div class="tng-carousel-slide"> <div class="tng-slot tng-text-body" style="block-size: 300px"> Slide 2 </div> </div> <div class="tng-carousel-slide"> <div class="tng-slot tng-text-body" style="block-size: 300px"> Slide 3 </div> </div> </div> <div class="tng-carousel-controls p-absolute at-bottom inset-lg"> <button class="tng-carousel-control">Item 1</button> <button class="tng-carousel-control is-active" disabled> Item 2 </button> <button class="tng-carousel-control">Item 3</button> </div></div>Progress duration
Section titled “Progress duration”The active progress animation duration is controlled by the custom property --tng-carousel-control-progress-duration.
- Apply it on
.tng-carousel-controlsto set one duration for all items. The default is 5 seconds. - Apply it on
.tng-carousel-controlto set duration per asset.
<div class="tng-carousel-controls" style="--tng-carousel-control-progress-duration: 5s;"> <button class="tng-carousel-control">Asset 1</button> <button class="tng-carousel-control" style="--tng-carousel-control-progress-duration: 8s;" > Asset 2 </button> <button class="tng-carousel-control">Asset 3</button></div>Behavior notes
Section titled “Behavior notes”- The progress fill runs only on
.tng-carousel-control.is-active. - The bar uses rested color for remaining track and active color for completed progress.
- Hover changes indicator thickness and hover track color for non-disabled items.
- Under reduced motion, active items render at full progress without animation.
Modules
Section titled “Modules”With count indicator
Section titled “With count indicator”The count indicator can be overlaid on the carousel using position utilities. Position the wrapper p-absolute at-bottom inset-lg and add .on-image to the label so it picks up the image-friendly backdrop.
<div class="tng-carousel p-relative"> <div class="tng-carousel-track"> <div class="tng-carousel-slide"> <div class="tng-slot tng-text-body" style="block-size: 300px"> Slide 1 </div> </div> <div class="tng-carousel-slide"> <div class="tng-slot tng-text-body" style="block-size: 300px"> Slide 2 </div> </div> <div class="tng-carousel-slide"> <div class="tng-slot tng-text-body" style="block-size: 300px"> Slide 3 </div> </div> </div> <div class="tng-count-indicator p-absolute at-bottom inset-lg"> <span class="tng-count-indicator-label">1 of 3</span> <button class="tng-icon-button is-md" aria-label="Previous"> <i class="tng-icon icon-chevron-left" aria-hidden="true"></i> </button> <button class="tng-icon-button is-md" aria-label="Next"> <i class="tng-icon icon-chevron-right" aria-hidden="true"></i> </button> </div></div>Recipes
Section titled “Recipes”The carousel follows the WAI-ARIA Carousel pattern — a set of slides presented one at a time with controls to move between them — and meets WCAG 2.1 AA. An accessible carousel stays operable from the keyboard, tells assistive technology which slide is showing and how many there are, gives every control an accessible name, and — because it advances on a timer — lets people stop that motion so no one is rushed by a disappearing interface.
For designers
Section titled “For designers”- The progress bars, their labels, and the fill that marks progress must all keep accessible contrast on every supported surface, including over imagery — this is why the Contrast scheme flips them to light foreground tokens on dark media. Source: WCAG 1.4.3 Contrast (Minimum).
- Which slide is active must never rest on colour alone — pair it with the thickened, filling bar and the control’s label, so the current position reads without relying on hue. Source: WCAG 1.4.1 Use of Color.
- Each control needs a visible focus ring that stays legible on every surface and is clearly distinct from the hover treatment. Source: WCAG 2.4.7 Focus Visible.
- Because the carousel auto-advances on a timer, give users a visible way to pause or stop the rotation (and pause it on hover and focus) — a self-advancing interface is only accessible when its motion can be halted. Source: WCAG 2.2.2 Pause, Stop, Hide.
- Thin progress bars are easy to miss and hard to tap — keep each control’s target at least 24 × 24 px (aim for 44 px for comfort on touch), and trim the item count before the bars get too small. Source: WCAG 2.5.8 Target Size (Minimum).
- Use semantic colour tokens (the Neutral / Contrast scheme tokens) rather than fixed values, so the indicator adapts correctly across themes and over different backgrounds.
For developers
Section titled “For developers”Wrap the rotating slides in a labelled landmark region announced as a carousel, and present each slide as a discrete group so assistive technology can report position within the set. Build the pagination from native <button> elements, so keyboard and focus behaviour come for free. Because the carousel advances on a timer, provide a keyboard-operable mechanism to pause and stop the rotation, pause it while a control is hovered or focused, and honour prefers-reduced-motion by rendering the active bar full with no animation and not auto-advancing. The per-element roles, names, and states are in the Labelling elements section below.
Keyboard interaction
Section titled “Keyboard interaction”| Key | Action |
|---|---|
| Tab / Shift + Tab | Move focus to the next / previous slide control |
| Enter / Space | Activate the focused control to jump to its slide |
Labelling elements
Section titled “Labelling elements”Give every element the role, name, and state assistive technology needs.
Carousel region
role="region" with aria-roledescription="carousel" so it announces as a carousel rather than a bare region. Give it an accessible name with aria-label (e.g. “Featured assets”) or aria-labelledby, so listeners know what the rotating set is for.
Slides
Present each slide container as role="group" with aria-roledescription="slide" and a positional name such as aria-label="1 of 3", so people can tell where they are in the set. Document only the slide container — the content slotted inside carries its own component’s semantics.
Slide controls
Each control is a native <button> named with aria-label (e.g. “Go to slide 1”) since its bar carries no text on its own. Mark the current slide’s control with aria-current="true" and disable it so it can’t re-trigger — together they expose the active state.
Source: WCAG 4.1.2 Name, Role, Value.