Skip to content

Carousel

hc-carousel is a carousel whose source of truth is the native scroll position. Slides live in a CSS scroll-snap rail; installCarousel() tracks the in-view slide, syncs prev/next and dot controls, adds keyboard support, and offers opt-in autoplay. There is no animation library and no JS-driven transform — native smooth scrolling does the motion.

import { installCarousel } from '@hypermedia-components/core';
installCarousel();
  • Tracks the most-visible slide with an IntersectionObserver and marks it data-active (style the active slide off that attribute).
  • Generates one dot per slide into a [data-hc-carousel-dots] container (or reuses author-provided [data-hc-carousel-dot] buttons) and keeps aria-current in sync.
  • Disables the prev / next buttons at the two ends.
  • Scrolls on prev / next / dot click and on ←/→ while the rail is focused — always via native smooth scrolling.
  • Emits a bubbling hc:carouselchange with detail.index on every change.

Each slide fills the viewport by default (--hc-carousel-slide-size: 100%). For a multi-item rail, set the slide basis to show several at once:

<div class="hc-carousel" style="--hc-carousel-slide-size: 50%"></div>

Add data-autoplay="<ms>" to advance automatically. It is opt-in only and:

  • pauses on hover and focus (so it never moves content out from under a pointer or the keyboard), and
  • is disabled entirely under prefers-reduced-motion: reduce — no automatic motion for users who ask to avoid it.
<div class="hc-carousel" data-autoplay="4000" aria-label="Promos"></div>

Slides are plain HTML, so they can be lazy-loaded as partials — e.g. render the rail server-side, or swap more slides into the viewport with htmx. The behavior re-scans added .hc-carousel roots via a MutationObserver; for slides added to an existing carousel, re-run installCarousel().

  • Give the carousel an aria-label; the rail is exposed as a focusable region (aria-roledescription="carousel") so ←/→ work and the scroll container is keyboard reachable.
  • Each slide is a group with aria-roledescription="slide" and an aria-label (“1 of 3” by default — override per slide if you have a better title).
  • Prev / next / dot controls are real <button>s with labels; dots expose the active slide with aria-current.
  • Autoplay’s hover/focus pause and reduced-motion opt-out are the core accessibility guarantees — keep them.
Token pathPurpose
carousel.gapGap between slides.
carousel.slide-sizeSlide flex-basis (default 100%).
carousel.control-sizePrev / next button diameter.
carousel.control-bg / -fg / -borderPrev / next button colors.
carousel.control-offsetInset of the prev / next buttons.
carousel.dot-size / -gapDot size and spacing.
carousel.dot-color / -active-colorDot and active-dot colors.
carousel.dots-marginGap above the dot row.
Show the generated CSS variables
--hc-carousel-gap | -slide-size
--hc-carousel-control-size | -control-bg | -control-fg | -control-border | -control-offset
--hc-carousel-dot-size | -dot-gap | -dot-color | -dot-active-color | -dots-margin
  • Scroll area — a plain scrollable region with edge shadows (no snapping or controls).
  • Tabs — switch between panels rather than scroll through a rail.