0.1.0

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.

import { RuiCarousel, RuiCarouselSlide } from '@ecopages/radiant-ui/carousel';
 
<RuiCarousel index={0} transition="slide" showIndicators label="Feature highlights">
  <RuiCarouselSlide id="first">First panel</RuiCarouselSlide>
  <RuiCarouselSlide id="second">Second panel</RuiCarouselSlide>
</RuiCarousel>

Custom markup

<rui-carousel> coordinates any light-DOM tree that matches its query contract. The Rui* helpers stamp these targets; they are not required.

import '@ecopages/radiant-ui/carousel';
 
<rui-carousel index={0} transition="slide" slides-per-view={3} show-indicators label="Feature highlights">
  <section data-ref="root" class="rui-carousel rui-carousel--slide" data-carousel-track="window" data-carousel-surface="cards" aria-roledescription="carousel" aria-label="Feature highlights">
    <div class="rui-carousel__stage">
      <div data-ref="viewport" class="rui-carousel__viewport">
        <div data-ref="track" class="rui-carousel__track">
          <div data-slide="first" class="rui-carousel__slide">First panel</div>
          <div data-slide="second" class="rui-carousel__slide">Second panel</div>
          <div data-slide="third" class="rui-carousel__slide">Third panel</div>
        </div>
      </div>
    </div>
    <div class="rui-carousel__footer">
      <button type="button" data-carousel-action="prev">Previous</button>
      <div data-ref="indicators" role="group" aria-label="Choose slide window"></div>
      <button type="button" data-carousel-action="next">Next</button>
    </div>
  </section>
</rui-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):

rui-carousel {
  --rui-carousel-gap: 1.5rem;
}
<RuiCarousel transition="slide" slidesPerView={3} slidesPerGroup={3} label="Featured products">
  <RuiCarouselSlide id="one">One</RuiCarouselSlide>
  <RuiCarouselSlide id="two">Two</RuiCarouselSlide>
  <RuiCarouselSlide id="three">Three</RuiCarouselSlide>
  <RuiCarouselSlide id="four">Four</RuiCarouselSlide>
  <RuiCarouselSlide id="five">Five</RuiCarouselSlide>
  <RuiCarouselSlide id="six">Six</RuiCarouselSlide>
</RuiCarousel>

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:

PartCSS roles
Viewport / card (rui-carousel__viewport or rui-carousel__slide)surface, border, radius-container
Card gap--rui-carousel-gap → --space-inline
Indicatorsbackground, border
Active indicatorprimary
Overlay chromesurface, border, shadow-sm (blurred pills)
Focusfocus-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 (<rui-carousel>) that owns slide state, autoplay, transitions, and control chrome. The RuiCarouselSlide and control helpers compose the surface. index is the first visible slide.

Attributes

AttributeTypeDefaultDescription
labelstringCarouselAccessible name for the carousel region.
indexnumber0First visible slide index.
autoplaybooleanfalseAdvance automatically.
intervalnumber4000Autoplay interval in ms.
transitionnone · slide · fadenoneSlide swap animation.
controls-varianttoolbar · overlaytoolbarChrome layout.
show-indicatorsbooleanfalseRender slide tabs or multi-slide window buttons.
show-rotation-controlbooleanfalseRender play/pause control.
loopbooleantrueAllow looping past the last slide.
wrapbooleantrueKeep controls enabled at the ends.
slides-per-viewnumber1Slides 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-groupnumber1Slides moved per prev/next/autoplay/swipe.
slide-countnumber0Authored slide count for render-time control state.

CSS properties

Set these on rui-carousel. Each defaults to a theme token.

PropertyDefaultUsed for
--rui-carousel-gap--space-inlineSpace between cards (slides-per-view > 1)
--rui-carousel-radius--radius-containerShell viewport or each card
--rui-carousel-border-color--borderShell viewport or each card
--rui-carousel-surface--surfaceShell viewport or each card
--rui-carousel-padding--space-insetShell viewport (toolbar) or each card

Light-DOM contract

TargetRequiredHost writesAuthor owns
[data-ref="root"]yesdata-carousel-track (swap · stack · window), data-carousel-surface (shell · cards)carousel region; view seeds aria-roledescription, aria-label
[data-ref="viewport"]yesaria-live, aria-atomicslide window
[data-ref="track"]yes--rui-carousel-index, --rui-carousel-slides-per-view, --rui-carousel-gap-count, --rui-carousel-slide-countslide track
[data-slide]yesrole, aria-*, aria-hidden, data-active, hidden (swap), idslide id value and content
[data-carousel-action="prev"] / nextnodisabled at endsnavigation buttons
[data-carousel-action="rotation"]noaria-pressed, aria-labelplay/pause control
[data-ref="indicators"]when show-indicatorsrole, 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

ComponentTarget stampedNotes
RuiCarouselshell targets via CarouselShellslides 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):

ClassDescription
.rui-carouselRoot region (aria-roledescription="carousel").
.rui-carousel--noneNo animation between slides.
.rui-carousel--slideHorizontal slide transition.
.rui-carousel--fadeCross-fade transition.
.rui-carousel--controls-toolbarControls below the viewport.
.rui-carousel--controls-overlayControls overlaid on the viewport.
.rui-carousel__stageViewport wrapper.
.rui-carousel__viewportOverflow-hidden slide window.
.rui-carousel__trackSlide track.
.rui-carousel__slideSlide surface (RuiCarouselSlide).
.rui-carousel__footerControls row below the viewport.
.rui-carousel__toolbarToolbar grid.
.rui-carousel__toolbar-rotationRotation control cell.
.rui-carousel__toolbar-centerIndicators cell.
.rui-carousel__toolbar-side--startPrev control cell.
.rui-carousel__toolbar-side--endNext control cell.
.rui-carousel__navPrev/next button (RuiCarouselPrev / RuiCarouselNext).
.rui-carousel__nav--overlayCircular on-slide chrome.
.rui-carousel__nav--toolbarToolbar chrome (default).
.rui-carousel__nav-labelIcon + label row (toolbar variant).
.rui-carousel__nav-iconDecorative chevron glyph.
.rui-carousel__rotationRotation toggle button (RuiCarouselRotation).
.rui-carousel__rotation--overlayOverlay pill chrome.
.rui-carousel__overlay-chromeAbsolute overlay layer over the viewport.
.rui-carousel__overlay-rotationRotation control overlay position.
.rui-carousel__controls--overlayPrev/next overlay row.
.rui-carousel__indicatorsIndicator tablist.
.rui-carousel__indicators--overlayOverlay pill indicator tablist.
.rui-carousel__indicatorIndicator button (role="tab").

Theme roles

PartCSS 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.