---
title: Carousel
description: Carousels cycle through a set of panels (hero images, feature highlights, or onboarding steps) while keeping a window of slides in focus.
category: Data display
---
import { meta as CarouselMeta, Default, OverlayControls, WithIndicators, WithSlidesPerView } from '@/content/stories/carousel';
import Canvas from '@/components/component-docs/canvas';
import Demo from '@/components/component-docs/demo';
export const config = {
dependencies: {
components: [Canvas, Demo],
scripts: [
'../../components/component-docs/demo.script.tsx',
'../../components/component-docs/canvas.script.tsx',
'../../components/component-docs/controls.script.tsx',
],
},
};
# Carousel
Carousels cycle through a set of panels (hero images, feature highlights, or onboarding steps). `index` is the first visible slide; `slidesPerView` controls how many fill the viewport.
## Try it
## Usage
Place `RuiCarouselSlide` children inside `RuiCarousel`. Each slide needs an `id`. Enable `autoplay` only when motion is not distracting and can be paused.
```tsx
import { RuiCarousel, RuiCarouselSlide } from '@ecopages/radiant-ui/carousel';
First panel
Second panel
```
## Custom markup
`` coordinates any light-DOM tree that matches its query contract. The
`Rui*` helpers stamp these targets; they are not required.
```tsx
import '@ecopages/radiant-ui/carousel';
First panel
Second panel
Third panel
```
When `show-indicators` is set, the host creates `[data-carousel-indicator]` buttons inside `[data-ref="indicators"]`. One slide per view uses a tab for each slide. Multiple slides per view use one button per valid window start, with `aria-current` identifying the current window. Five slides with `slidesPerView={3}` have three window buttons. Arrow keys, Home, and End navigate those windows.
## Show several slides
Set `slidesPerView` to how many slides fill the viewport (`1.2` peeks the next slide at the start, and the previous slide at the end so the last full card sits on the right). `slidesPerGroup` is how far prev/next/autoplay move. `index` stays the first visible slide. One pane keeps chrome on the viewport; more than one slide paints each slide as a card. Space between cards is `--rui-carousel-gap` (default `--space-inline`):
```css
rui-carousel {
--rui-carousel-gap: 1.5rem;
}
```
```tsx
One
Two
Three
Four
Five
Six
```
## Use autoplay sparingly
Autoplaying carousels can disorient users and violate reduced-motion preferences. Prefer manual controls and expose `showRotationControl` when autoplay is on.
## Pick a transition
`slide` moves content horizontally. `fade` cross-fades panels. `none` swaps instantly and is best when motion would be gratuitous.
Set `controlsVariant="overlay"` to pin prev/next on the slide edges.
## Theming
Carousel maps to **semantic surface and border roles**, never Tailwind palette steps:
| Part | CSS roles |
| --- | --- |
| Viewport / card (`rui-carousel__viewport` or `rui-carousel__slide`) | `surface`, `border`, `radius-container` |
| Card gap | `--rui-carousel-gap` → `--space-inline` |
| Indicators | `background`, `border` |
| Active indicator | `primary` |
| Overlay chrome | `surface`, `border`, `shadow-sm` (blurred pills) |
| Focus | `focus-ring` |
| Geometry | `--space-inline`, `--space-stack`, `--space-inset` |
Override the `--rui-carousel-*` custom properties on `rui-carousel`. Theme roles stay the defaults behind those properties.
## API
`RuiCarousel` is a custom element (``) that owns slide state, autoplay, transitions, and control chrome. The `RuiCarouselSlide` and control helpers compose the surface. `index` is the first visible slide.
### Attributes
| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | `Carousel` | Accessible name for the carousel region. |
| `index` | `number` | `0` | First visible slide index. |
| `autoplay` | `boolean` | `false` | Advance automatically. |
| `interval` | `number` | `4000` | Autoplay interval in ms. |
| `transition` | `none` · `slide` · `fade` | `none` | Slide swap animation. |
| `controls-variant` | `toolbar` · `overlay` | `toolbar` | Chrome layout. |
| `show-indicators` | `boolean` | `false` | Render slide tabs or multi-slide window buttons. |
| `show-rotation-control` | `boolean` | `false` | Render play/pause control. |
| `loop` | `boolean` | `true` | Allow looping past the last slide. |
| `wrap` | `boolean` | `true` | Keep controls enabled at the ends. |
| `slides-per-view` | `number` | `1` | Slides that fill the viewport. Values `>= 1`; fractional peek is allowed. At the end the last card sits on the right and the previous card peeks. |
| `slides-per-group` | `number` | `1` | Slides moved per prev/next/autoplay/swipe. |
| `slide-count` | `number` | `0` | Authored slide count for render-time control state. |
### CSS properties
Set these on `rui-carousel`. Each defaults to a theme token.
| Property | Default | Used for |
| --- | --- | --- |
| `--rui-carousel-gap` | `--space-inline` | Space between cards (`slides-per-view` > 1) |
| `--rui-carousel-radius` | `--radius-container` | Shell viewport or each card |
| `--rui-carousel-border-color` | `--border` | Shell viewport or each card |
| `--rui-carousel-surface` | `--surface` | Shell viewport or each card |
| `--rui-carousel-padding` | `--space-inset` | Shell viewport (toolbar) or each card |
### Light-DOM contract
| Target | Required | Host writes | Author owns |
| --- | --- | --- | --- |
| `[data-ref="root"]` | yes | `data-carousel-track` (`swap` · `stack` · `window`), `data-carousel-surface` (`shell` · `cards`) | carousel region; view seeds `aria-roledescription`, `aria-label` |
| `[data-ref="viewport"]` | yes | `aria-live`, `aria-atomic` | slide window |
| `[data-ref="track"]` | yes | `--rui-carousel-index`, `--rui-carousel-slides-per-view`, `--rui-carousel-gap-count`, `--rui-carousel-slide-count` | slide track |
| `[data-slide]` | yes | `role`, `aria-*`, `aria-hidden`, `data-active`, `hidden` (swap), `id` | slide `id` value and content |
| `[data-carousel-action="prev"]` / `next` | no | `disabled` at ends | navigation buttons |
| `[data-carousel-action="rotation"]` | no | `aria-pressed`, `aria-label` | play/pause control |
| `[data-ref="indicators"]` | when `show-indicators` | `role`, `aria-label`; creates `[data-carousel-indicator]` | indicator container |
Do not set `aria-hidden`, `aria-selected`, or `tabIndex` on slides or indicators. Nested hosts: none.
### View helpers
| Component | Target stamped | Notes |
| --- | --- | --- |
| `RuiCarousel` | shell targets via `CarouselShell` | `slides` convenience prop or `RuiCarouselSlide` children; default prev/next/rotation chrome. Stamps `data-carousel-track` and `data-carousel-surface` on `[data-ref="root"]`. |
| `RuiCarouselSlide` | `[data-slide]` | Requires `id` prop. |
| `RuiCarouselPrev` | `[data-carousel-action="prev"]` | Override with `prev` prop on `RuiCarousel`. |
| `RuiCarouselNext` | `[data-carousel-action="next"]` | Override with `next` prop on `RuiCarousel`. |
| `RuiCarouselRotation` | `[data-carousel-action="rotation"]` | Shown when `autoplay` or `showRotationControl`. |
### CSS classes
Public BEM classes (documented via `@cssclass` on the element and view helpers):
| Class | Description |
| --- | --- |
| `.rui-carousel` | Root region (`aria-roledescription="carousel"`). |
| `.rui-carousel--none` | No animation between slides. |
| `.rui-carousel--slide` | Horizontal slide transition. |
| `.rui-carousel--fade` | Cross-fade transition. |
| `.rui-carousel--controls-toolbar` | Controls below the viewport. |
| `.rui-carousel--controls-overlay` | Controls overlaid on the viewport. |
| `.rui-carousel__stage` | Viewport wrapper. |
| `.rui-carousel__viewport` | Overflow-hidden slide window. |
| `.rui-carousel__track` | Slide track. |
| `.rui-carousel__slide` | Slide surface (`RuiCarouselSlide`). |
| `.rui-carousel__footer` | Controls row below the viewport. |
| `.rui-carousel__toolbar` | Toolbar grid. |
| `.rui-carousel__toolbar-rotation` | Rotation control cell. |
| `.rui-carousel__toolbar-center` | Indicators cell. |
| `.rui-carousel__toolbar-side--start` | Prev control cell. |
| `.rui-carousel__toolbar-side--end` | Next control cell. |
| `.rui-carousel__nav` | Prev/next button (`RuiCarouselPrev` / `RuiCarouselNext`). |
| `.rui-carousel__nav--overlay` | Circular on-slide chrome. |
| `.rui-carousel__nav--toolbar` | Toolbar chrome (default). |
| `.rui-carousel__nav-label` | Icon + label row (toolbar variant). |
| `.rui-carousel__nav-icon` | Decorative chevron glyph. |
| `.rui-carousel__rotation` | Rotation toggle button (`RuiCarouselRotation`). |
| `.rui-carousel__rotation--overlay` | Overlay pill chrome. |
| `.rui-carousel__overlay-chrome` | Absolute overlay layer over the viewport. |
| `.rui-carousel__overlay-rotation` | Rotation control overlay position. |
| `.rui-carousel__controls--overlay` | Prev/next overlay row. |
| `.rui-carousel__indicators` | Indicator tablist. |
| `.rui-carousel__indicators--overlay` | Overlay pill indicator tablist. |
| `.rui-carousel__indicator` | Indicator button (`role="tab"`). |
### Theme roles
| Part | CSS variables consumed |
| --- | --- |
| Viewport / card | `--rui-carousel-surface`, `--rui-carousel-border-color`, `--rui-carousel-radius`, `--rui-carousel-padding` |
| Card gap | `--rui-carousel-gap` |
| Indicators | `--background`, `--border` |
| Active indicator | `--primary` |
| Overlay chrome | `--surface`, `--border`, `--shadow-control` |
| Focus | `--focus-ring` |
| Geometry | `--space-inline`, `--space-stack`, `--space-inset` |
## Accessibility
- Provide a descriptive `label` so screen readers identify the carousel region.
- Prev/next controls must be keyboard reachable and expose their purpose in the accessible name.
- Slides outside the visible window are `aria-hidden`. `data-active` marks the first visible slide.
- Respect `prefers-reduced-motion` and avoid autoplay when users request reduced motion.