--- 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'; ``` 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.