A horizontal carousel built on native CSS scroll-snap and a small LiveView hook.
Unlike the React original (which wraps Embla),
this v1 is a pure CSS scroll-snap carousel: the content track is an
overflow-x-auto flex row with snap-x snap-mandatory, and each item is a
full-width snap-start child. Scrolling, momentum, and snapping are entirely
native to the browser, so there is no JS layout/animation loop.
The ShadixCarousel hook (assets/ts/carousel.ts) only wires the
previous/next buttons: clicking them scrolls the content element by one item
(its clientWidth) left or right with smooth behaviour, and the hook toggles
the buttons' disabled state at the start/end of the track (and on scroll /
resize).
Simplifications vs. the original
- Horizontal only. No
orientation="vertical". - No drag/pointer physics. You can still drag/flick on touch devices via native scrolling, but there is no Embla-style mouse-drag inertia.
- No autoplay, no loop, no programmatic
setApi/opts/plugins. - One-item paging. Prev/next scroll by exactly one viewport width
(one full-basis item) rather than honouring multi-item
slidesToScroll. - Keyboard arrows are not bound (the React version listens for Left/Right); rely on the focusable scroll container instead.
The content track and buttons are linked by data-carousel-prev /
data-carousel-next markers and a data-carousel-content marker so the hook
can find them from the root wrapper without ids.
Summary
Functions
Renders the carousel root: a relative wrapper carrying the ShadixCarousel
hook.
The scrollable track. A horizontal flex row with mandatory x scroll-snap and a hidden scrollbar.
A single slide. Full-basis, non-growing/shrinking, snapping to its start edge.
The "next" control: a round, absolutely-positioned icon button.
The "previous" control: a round, absolutely-positioned icon button.
Functions
Renders the carousel root: a relative wrapper carrying the ShadixCarousel
hook.
Compose carousel_content/1 (with carousel_item/1 children) and the
carousel_previous/1 / carousel_next/1 buttons inside it. The hook finds
the content track and buttons by their data-carousel-* markers, so they may
appear anywhere within this wrapper.
Attributes
id(:string) (required)class(:string) - Defaults tonil.- Global attributes are accepted.
Slots
inner_block(required)
The scrollable track. A horizontal flex row with mandatory x scroll-snap and a hidden scrollbar.
Carries data-carousel-content so the ShadixCarousel hook can scroll it by
one item when the prev/next buttons are clicked.
Attributes
class(:string) - Defaults tonil.- Global attributes are accepted.
Slots
inner_block(required)
A single slide. Full-basis, non-growing/shrinking, snapping to its start edge.
Attributes
class(:string) - Defaults tonil.- Global attributes are accepted.
Slots
inner_block(required)
The "next" control: a round, absolutely-positioned icon button.
Carries data-carousel-next; the hook wires its click to scroll the content
forward by one item and toggles its disabled state at the end of the track.
Attributes
class(:string) - Defaults tonil.- Global attributes are accepted.
The "previous" control: a round, absolutely-positioned icon button.
Carries data-carousel-prev; the hook wires its click to scroll the content
back by one item and toggles its disabled state at the start of the track.
Attributes
class(:string) - Defaults tonil.- Global attributes are accepted.