Carousel component with touch swipe, button navigation, and keyboard support.
Follows the shadcn/ui Carousel anatomy adapted for Phoenix LiveView.
The PhiaCarousel vanilla-JS hook manages CSS transform-based sliding,
touch/swipe detection, keyboard navigation (arrow keys), and optional loop
behaviour. Zero npm dependencies.
When to use
- Hero banner with rotating promotional images
- Product image gallery (multiple photos of the same item)
- Onboarding walkthrough (step-by-step screens)
- Testimonial rotator
- Vertical announcement ticker
Anatomy
| Component | Element | Purpose |
|---|---|---|
carousel/1 | div | Root container — mounts the PhiaCarousel hook |
carousel_content/1 | div | Flex track that holds all slides side-by-side |
carousel_item/1 | div | Individual slide (min-w-full shrink-0) |
carousel_previous/1 | button | Navigate to the previous slide |
carousel_next/1 | button | Navigate to the next slide |
Basic horizontal carousel
<.carousel id="hero-banner">
<.carousel_content>
<.carousel_item>
<img src="/images/promo-1.jpg" alt="Summer sale" class="w-full object-cover" />
</.carousel_item>
<.carousel_item>
<img src="/images/promo-2.jpg" alt="New arrivals" class="w-full object-cover" />
</.carousel_item>
<.carousel_item>
<img src="/images/promo-3.jpg" alt="Free shipping" class="w-full object-cover" />
</.carousel_item>
</.carousel_content>
<.carousel_previous />
<.carousel_next />
</.carousel>Looping vertical carousel
<.carousel id="announcements" orientation="vertical" loop={true}>
<.carousel_content>
<.carousel_item class="h-12 flex items-center">
Maintenance window scheduled for Sunday 2 AM UTC
</.carousel_item>
<.carousel_item class="h-12 flex items-center">
v2.4.0 released — see the changelog
</.carousel_item>
</.carousel_content>
<.carousel_previous />
<.carousel_next />
</.carousel>Carousel with dot indicator slot
<.carousel id="product-gallery">
<.carousel_content>
<.carousel_item :for={img <- @product_images}>
<img src={img.url} alt={img.alt} class="w-full rounded-lg object-cover" />
</.carousel_item>
</.carousel_content>
<.carousel_previous />
<.carousel_next />
<:indicators>
<button
:for={{_img, i} <- Enum.with_index(@product_images)}
data-index={i}
class="h-2 w-2 rounded-full bg-muted data-[active]:bg-primary"
/>
</:indicators>
</.carousel>Accessibility
- The root element has
role="region"andaria-label="carousel" - Each slide has
role="group"andaria-roledescription="slide" - Navigation buttons have
aria-label="Previous slide"/"Next slide" - The root is focusable (
tabindex="0") so arrow-key navigation works without a mouse
Hook setup
mix phia.add carousel # copies hooks/carousel.js to your project
# app.js
import PhiaCarousel from "./hooks/carousel"
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { PhiaCarousel }
})
Summary
Functions
Renders the carousel root container.
Renders the carousel track container.
Renders an individual carousel slide.
Renders the next-slide navigation button.
Renders the previous-slide navigation button.
Functions
Renders the carousel root container.
Mounts the PhiaCarousel hook and communicates configuration via data-*
attributes. The root is focusable (tabindex="0") so keyboard users can
navigate slides with arrow keys without any additional setup.
role="region" + aria-label="carousel" ensures the landmark is announced
correctly by screen readers.
Attributes
id(:string) - Unique carousel ID. ThePhiaCarouselhook uses this to identify the instance and store its current slide index in hook state.Defaults to
"carousel".orientation(:string) - Carousel slide direction:"horizontal"— slides left/right (default, most common)"vertical"— slides up/down (e.g. announcement ticker) The hook readsdata-orientationto set the correct transform axis.
Defaults to
"horizontal". Must be one of"horizontal", or"vertical".loop(:boolean) - Whentrue, navigating past the last slide wraps to the first (and navigating before the first wraps to the last). The hook readsdata-loop.Defaults to
false.class(:string) - Additional CSS classes for the root div. Defaults tonil.Global attributes are accepted. HTML attributes forwarded to the root div.
Slots
inner_block(required) -carousel_content/1and navigation sub-components.indicators- Optional dot indicator buttons rendered at the bottom of the carousel. ThePhiaCarouselhook can toggle adata-activeattribute on the active indicator. Usedata-[active]:bg-primaryTailwind variant to style it.
Renders the carousel track container.
The data-carousel-track attribute is used by the PhiaCarousel hook to
locate and animate this element. The hook updates style.transform on this
element directly to translate between slides.
transition-transform duration-300 ease-in-out provides the slide animation.
Do not remove these classes unless you want an instant snap transition.
Attributes
class(:string) - Additional CSS classes for the track div. Defaults tonil.- Global attributes are accepted. HTML attributes forwarded to the track div (the element that receives
transform).
Slots
inner_block(required) -carousel_item/1slide children.
Renders an individual carousel slide.
min-w-full shrink-0 ensures each slide occupies exactly 100% of the track
width (or height for vertical carousels), preventing any bleeding between
slides.
role="group" and aria-roledescription="slide" allow screen readers to
announce "Slide 1 of 3" when the user navigates via keyboard.
Attributes
class(:string) - Additional CSS classes for the slide div. Defaults tonil.- Global attributes are accepted. HTML attributes forwarded to the slide div.
Slots
inner_block(required) - Slide content — images, cards, text, etc.
Renders the next-slide navigation button.
The data-carousel-next attribute is used by the PhiaCarousel hook to
attach a click listener and manage the disabled attribute. When loop
is false and the carousel is at the last slide, the hook sets disabled
on this button to prevent forward navigation.
Override the default › icon by placing content in the :inner_block slot.
Attributes
class(:string) - Additional CSS classes for the button element. Defaults tonil.- Global attributes are accepted. HTML attributes forwarded to the
<button>element.
Slots
inner_block- Button content — defaults to the right-pointing single-guillemet character›.
Renders the previous-slide navigation button.
The data-carousel-prev attribute is used by the PhiaCarousel hook to
attach a click listener and manage the disabled attribute. When loop
is false and the carousel is at the first slide, the hook sets
disabled on this button to prevent backward navigation.
Override the default ‹ icon by placing content in the :inner_block slot.
Attributes
class(:string) - Additional CSS classes for the button element. Defaults tonil.- Global attributes are accepted. HTML attributes forwarded to the
<button>element.
Slots
inner_block- Button content — defaults to the left-pointing single-guillemet character‹.