Carousel
v0.0.10
mix mishka.ui.gen.headless carousel
Overview of the Phoenix and LiveView Headless Carousel Component
The Mishka Chelekom Headless Carousel is a Phoenix function component that turns a set of slides into a scroll-snap carousel for LiveView. The scrolling is the browser's own: the viewport is a scroll container and the slides are its snap points, so dragging, trackpads, touch and momentum scrolling behave the way people expect, and the slides still scroll if the script is missing. A small HeadlessCarousel hook adds what CSS cannot report: which slide is current, previous and next buttons, indicator buttons and autoplay.
The root is a region with aria-roledescription="carousel", and each slide is a group labelled "1 of 4" unless you give it a label. The current slide is found by measuring the scroll position once scrolling settles, so a swipe that lands two slides along is still reported correctly. State is on the markup as data-index, data-current, data-orientation and data-snap. Style the parts with class, viewport_class, slide_class, controls_class, control_class, indicators_class and indicator_class.
Base UI Carousel example - slides with indicators (Phoenix headless)
Base UI Carousel example - slides with indicators (Phoenix headless)
<.carousel
id="baseui-carousel-hero"
label="Panels"
show_indicators
class="flex w-full max-w-80 flex-col gap-2"
viewport_class="flex snap-x snap-mandatory overflow-x-auto focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white"
slide_class="w-full shrink-0 snap-start"
controls_class="flex items-center justify-center gap-2"
indicators_class="flex items-center gap-2"
indicator_class="size-2.5 rounded-full bg-neutral-300 data-[current]:bg-neutral-950 dark:bg-neutral-600 dark:data-[current]:bg-white"
>
<:slide :for={n <- 1..4}>
<div class="grid h-28 place-items-center rounded-md border border-neutral-200 bg-neutral-100 text-2xl font-medium text-neutral-950 dark:border-neutral-800 dark:bg-neutral-900 dark:text-white">
{n}
</div>
</:slide>
</.carousel>Base UI Carousel example - previous and next controls (Phoenix headless)
Base UI Carousel example - previous and next controls (Phoenix headless)
<.carousel
id="baseui-carousel-controls"
label="Panels"
show_controls
show_indicators
class="flex w-full max-w-80 flex-col gap-2"
viewport_class="flex snap-x snap-mandatory overflow-x-auto focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white"
slide_class="w-full shrink-0 snap-start"
controls_class="flex items-center justify-center gap-3"
control_class="flex size-8 items-center justify-center rounded-full border border-neutral-300 text-lg leading-none text-neutral-950 hover:bg-neutral-100 disabled:cursor-not-allowed disabled:opacity-40 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-800 dark:focus-visible:outline-white"
indicators_class="flex items-center gap-2"
indicator_class="size-2.5 rounded-full bg-neutral-300 data-[current]:bg-neutral-950 dark:bg-neutral-600 dark:data-[current]:bg-white"
>
<:slide :for={n <- 1..4}>
<div class="grid h-28 place-items-center rounded-md border border-neutral-200 bg-neutral-100 text-2xl font-medium text-neutral-950 dark:border-neutral-800 dark:bg-neutral-900 dark:text-white">
{n}
</div>
</:slide>
</.carousel>Attributes, slots and events
The component needs a unique id, which carries the hook. label is required: it names the carousel for assistive technology, and the indicator group is named after it ("Panels slides" in the examples).
snap (start, center, end or none) and orientation (horizontal or vertical) are written to data-snap and data-orientation on the root, so your CSS can react to them. orientation also decides which arrow keys move the carousel (Up and Down when vertical) and which scroll axis the hook measures. The snapping itself comes from your classes.
index sets which slide is marked current when the carousel renders, counting from 0. loop makes the previous and next buttons wrap around at the ends instead of being disabled there. autoplay takes a number of milliseconds and moves to the next slide on that interval, going back to the first slide after the last. It pauses while the pointer is over the carousel or focus is inside it, and it does not run for visitors who prefer reduced motion.
show_controls renders the previous and next buttons, named by previous_label and next_label ("Previous slide" and "Next slide" by default). show_indicators renders one button per slide, named "Go to slide 2" and so on.
on_change is the name of a LiveView event. The hook pushes it with %{"index" => 2} when it starts and each time the current slide changes.
Each :slide entry takes a label to replace the default "1 of 4" description, and a class. The optional :controls slot is drawn at the start of the controls row, before the built-in buttons, for custom markup.
Parts & data attributes
Each part carries a stable data-part and a structural chelekom-carousel__* class you can target.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Root | - | role="region", aria-roledescription="carousel", aria-label, data-orientation, data-snap, data-index, data-loop, data-autoplay | Carries the hook and the carousel state. |
| Viewport | viewport | tabindex="0", aria-live="off" when autoplay is set | The scroll-snap container. It is focusable so the arrow keys work. |
| Slide | slide | role="group", aria-roledescription="slide", aria-label, aria-hidden, data-index, data-current | One slide. Every slide except the current one is aria-hidden. |
| Controls | controls | - | The row that holds the buttons, the indicators and the controls slot. |
| Previous / Next | previous / next | aria-label, aria-controls, disabled at the ends unless loop is set | Step one slide back or forward. |
| Indicators | indicators | role="group", aria-label | The wrapper for the indicator buttons. |
| Indicator | indicator | aria-current, aria-label, data-index, data-current | One button per slide. |
Accessibility
Follows the WAI-ARIA Carousel pattern. Tab reaches the viewport and the buttons. With the viewport focused, Left and Right move one slide (Up and Down in a vertical carousel), and Home and End jump to the first and last slide. Only the current slide is exposed to assistive technology. Autoplay pauses on hover and focus, and the viewport gets aria-live="off" while it runs so screen readers are not interrupted. For visitors who prefer reduced motion, autoplay stays off and the buttons jump to a slide instead of scrolling smoothly.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | Unique id (carries the HeadlessCarousel hook) |
label
|
string
| — | — | Yes | What this carousel is a carousel of |
snap
|
string
|
start
|
startcenterendnone
| No | Where a slide comes to rest |
orientation
|
string
|
horizontal
|
horizontalvertical
| No | Which way it scrolls |
index
|
integer
|
0
| — | No | The slide to start on |
autoplay
|
integer
| — | — | No | Advance every N milliseconds; pauses on hover and focus, and never runs under reduced motion |
loop
|
boolean
|
false
| — | No | Wrap around at the ends instead of stopping |
show_controls
|
boolean
|
false
| — | No | Render previous/next buttons |
show_indicators
|
boolean
|
false
| — | No | Render one button per slide |
previous_label
|
string
|
Previous slide
| — | No | Accessible name for previous |
next_label
|
string
|
Next slide
| — | No | Accessible name for next |
on_change
|
string
| — | — | No | LiveView event pushed as `%{index}` |
class
|
any
| — | — | No | Extra classes for the root |
viewport_class
|
any
| — | — | No | — |
slide_class
|
any
| — | — | No | — |
control_class
|
any
| — | — | No | — |
controls_class
|
any
| — | — | No | — |
indicator_class
|
any
| — | — | No | — |
indicators_class
|
any
| — | — | No | — |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
slide
| Yes | One slide |
controls
| No | Custom markup for the controls row, instead of the default buttons |
Attributes of the slide slot
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
label
|
string
| — | — | No | Overrides the default "N of M" description |
class
|
any
| — | — | No | — |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
carousel
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless carousel