Corex. Carousel
(Corex v0.1.0)
View Source
Phoenix implementation of Zag.js Carousel.
Anatomy
| Goal | API |
|---|---|
| Image gallery | items={[Corex.Image.new("/images/a.jpg", alt: "A"), ...]} — renders <img> per slide |
| Custom slides | items={@posts} + <:item :let={post}> — your markup per slide |
| Full structure | compound + carousel_* subcomponents |
Items
Pass items for simple-mode slides. Image galleries use Corex.Image — see
Corex.Image.new/2. Custom slides use any list plus the <:item> slot.
items | <:item> slot | Result |
|---|---|---|
[%Corex.Image{}, ...] | omitted | <img src alt class> per slide |
| any list | <:item :let={item}> | your markup via render_slot/2 |
| — | — | compound mode: set item_count and use carousel_item |
Without <:item>, every entry must be %Corex.Image{}; other values raise at render time.
Slides may include links and other controls; off-view slides are marked inert on the client so they stay out of the tab order.
Images
<.carousel
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall"),
Corex.Image.new("/images/sand.jpg", alt: "Sand"),
Corex.Image.new("/images/star.jpg", alt: "Star"),
Corex.Image.new("/images/winter.jpg", alt: "Winter")
]}
class="carousel"
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>Custom content
<.carousel items={@posts} class="carousel">
<:item :let={post}>
<article>
<h3>{post.title}</h3>
<p>{post.description}</p>
<.navigate to="#" class="link">Read more</.navigate>
</article>
</:item>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>Compound
<.carousel compound :let={ctx} item_count={2} class="carousel">
<.carousel_root ctx={ctx}>
<.carousel_item_group ctx={ctx}>
<.carousel_item ctx={ctx} index={0}>First slide</.carousel_item>
<.carousel_item ctx={ctx} index={1}>Second slide</.carousel_item>
</.carousel_item_group>
<.carousel_control ctx={ctx}>
<.carousel_prev_trigger ctx={ctx}><.heroicon name="hero-arrow-left" /></.carousel_prev_trigger>
<.carousel_indicator_group ctx={ctx}>
<.carousel_indicator ctx={ctx} index={0} />
<.carousel_indicator ctx={ctx} index={1} />
</.carousel_indicator_group>
<.carousel_next_trigger ctx={ctx}><.heroicon name="hero-arrow-right" /></.carousel_next_trigger>
</.carousel_control>
</.carousel_root>
</.carousel>API
Requires a stable id on <.carousel>.
| Function | Action | Returns |
|---|---|---|
play/1 | Start or resume autoplay (client) | %Phoenix.LiveView.JS{} |
play/2 | Start or resume autoplay (server) | socket |
pause/1 | Pause autoplay (client) | %Phoenix.LiveView.JS{} |
pause/2 | Pause autoplay (server) | socket |
scroll_next/1 | Next page (client) | %Phoenix.LiveView.JS{} |
scroll_next/2 | Next page with instant (client) | %Phoenix.LiveView.JS{} |
scroll_next/3 | Next page (server) | socket |
scroll_prev/1 | Previous page (client) | %Phoenix.LiveView.JS{} |
scroll_prev/2 | Previous page with instant (client) | %Phoenix.LiveView.JS{} |
scroll_prev/3 | Previous page (server) | socket |
Events
Pick an event name and pass it to on_* on <.carousel>.
Server events
| Event | When | Payload |
|---|---|---|
on_page_change="carousel_page_changed" | Active page changes | %{"id" => id, "page" => page, "pageSnapPoint" => snap} |
on_page_change
<.carousel
class="carousel"
on_page_change="carousel_page_changed"
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall"),
Corex.Image.new("/images/sand.jpg", alt: "Sand"),
Corex.Image.new("/images/star.jpg", alt: "Star"),
Corex.Image.new("/images/winter.jpg", alt: "Winter")
]}
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>def handle_event("carousel_page_changed", %{"id" => _id, "page" => page}, socket) do
{:noreply, assign(socket, :carousel_page, page)}
endClient events
| Event | When | event.detail |
|---|---|---|
on_page_change_client="carousel-page-changed" | Active page changes | id, page, pageSnapPoint |
on_page_change_client
<.carousel
id="carousel-events-client"
class="carousel"
on_page_change_client="carousel-page-changed"
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall"),
Corex.Image.new("/images/sand.jpg", alt: "Sand"),
Corex.Image.new("/images/star.jpg", alt: "Star"),
Corex.Image.new("/images/winter.jpg", alt: "Winter")
]}
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>const el = document.getElementById("carousel-events-client");
el?.addEventListener("carousel-page-changed", (e) => console.log(e.detail));Style
Target parts with data-scope and data-part, or use Corex Design: import tokens and carousel.css, then set class="carousel" on <.carousel>.
[data-scope="carousel"][data-part="root"] {}
[data-scope="carousel"][data-part="control"] {}
[data-scope="carousel"][data-part="item-group"] {}
[data-scope="carousel"][data-part="item"] {}
[data-scope="carousel"][data-part="prev-trigger"] {}
[data-scope="carousel"][data-part="next-trigger"] {}
[data-scope="carousel"][data-part="indicator-group"] {}
[data-scope="carousel"][data-part="indicator"] {}@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/carousel.css";Stack modifiers on the host (class on <.carousel>).
Color
| Modifier | Classes |
|---|---|
| Default | carousel |
| Accent | carousel carousel--accent |
| Brand | carousel carousel--brand |
| Alert | carousel carousel--alert |
| Info | carousel carousel--info |
| Success | carousel carousel--success |
<.carousel
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall"),
Corex.Image.new("/images/sand.jpg", alt: "Sand"),
Corex.Image.new("/images/star.jpg", alt: "Star"),
Corex.Image.new("/images/winter.jpg", alt: "Winter")
]}
class="carousel"
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
<.carousel
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall"),
Corex.Image.new("/images/sand.jpg", alt: "Sand"),
Corex.Image.new("/images/star.jpg", alt: "Star"),
Corex.Image.new("/images/winter.jpg", alt: "Winter")
]}
class="carousel carousel--accent"
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>Size
Layout density for the root, control bar, prev/next triggers, autoplay control, and indicators.
| Modifier | Classes |
|---|---|
| SM | carousel carousel--sm |
| MD | carousel carousel--md |
| LG | carousel carousel--lg |
| XL | carousel carousel--xl |
<.carousel
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall"),
Corex.Image.new("/images/sand.jpg", alt: "Sand"),
Corex.Image.new("/images/star.jpg", alt: "Star"),
Corex.Image.new("/images/winter.jpg", alt: "Winter")
]}
class="carousel carousel--sm"
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
<.carousel
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall"),
Corex.Image.new("/images/sand.jpg", alt: "Sand"),
Corex.Image.new("/images/star.jpg", alt: "Star"),
Corex.Image.new("/images/winter.jpg", alt: "Winter")
]}
class="carousel carousel--lg"
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>Radius
Corner radius for the slide area and prev/next triggers.
| Modifier | Classes |
|---|---|
| None | carousel carousel--rounded-none |
| SM | carousel carousel--rounded-sm |
| MD | carousel carousel--rounded-md |
| LG | carousel carousel--rounded-lg |
| XL | carousel carousel--rounded-xl |
| Full | carousel carousel--rounded-full |
<.carousel
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall"),
Corex.Image.new("/images/sand.jpg", alt: "Sand"),
Corex.Image.new("/images/star.jpg", alt: "Star"),
Corex.Image.new("/images/winter.jpg", alt: "Winter")
]}
class="carousel carousel--rounded-md"
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>
Summary
API
Pause autoplay from the client. Dispatches corex:carousel:pause on the carousel root.
Pause autoplay from the server. push_event("carousel_pause", %{"id" => carousel_id}).
Start or resume autoplay from the client. Dispatches corex:carousel:play on the carousel root.
Start or resume autoplay from the server. push_event("carousel_play", %{"id" => carousel_id}).
Same as scroll_next/2 with instant: false.
Scroll to the next page from the client. Dispatches corex:carousel:scroll-next.
Pass true as the second argument for an instant jump (no animation).
Scroll to the next page from the server. push_event("carousel_scroll_next", %{"id" => id, "instant" => boolean}).
Same as scroll_prev/2 with instant: false.
Scroll to the previous page from the client. Dispatches corex:carousel:scroll-prev.
Pass true as the second argument for an instant jump (no animation).
Scroll to the previous page from the server. push_event("carousel_scroll_prev", %{"id" => id, "instant" => boolean}).
Components
Attributes
id(:string)aria_label(:string) - Defaults tonil.items(:list) - List of%Corex.Image{}for image slides, or arbitrary data when<:item>is set. Omit in compound mode; useitem_countwhen children do not pass throughitems. Defaults tonil.item_count(:integer) - When set, overrides the slide count used for the hook and compound context (use in compound mode withoutitems). Defaults tonil.page(:integer) - Active page (1-based, same as pagination; first page is 1). Defaults to1.dir(:string) - Defaults tonil.Must be one ofnil,"ltr", or"rtl".orientation(:string) - Defaults to"horizontal". Must be one of"horizontal", or"vertical".slides_per_page(:integer) - Defaults to1.slides_per_move(:any) - Number of slides to move per step, or "auto". Defaults tonil.loop(:boolean) - Defaults tofalse.autoplay(:boolean) - Defaults tofalse.autoplay_delay(:integer) - Defaults to4000.allow_mouse_drag(:boolean) - Defaults tofalse.spacing(:string) - Defaults to"0px".padding(:string) - Defaults tonil.in_view_threshold(:float) - Defaults to0.6.snap_type(:string) - Defaults to"mandatory". Must be one of"proximity", or"mandatory".auto_size(:boolean) - Defaults tofalse.on_page_change(:string) - Defaults tonil.on_page_change_client(:string) - Defaults tonil.compound(:boolean) - Enable compound mode with :let={ctx} and carousel_root, carousel_item_group, carousel_item, carousel_control, carousel_prev_trigger, carousel_next_trigger, carousel_indicator_group, carousel_indicator. Defaults tofalse.- Global attributes are accepted.
Slots
inner_block- Compound mode. Use compound and :let={ctx}.prev_trigger- Accepts attributes:class(:string)
next_trigger- Accepts attributes:class(:string)
item- Custom markup for each slide. Use :let={item} to receive each entry fromitems. Required whenitemsare not%Corex.Image{}structs. Accepts attributes:class(:string)
Compounds
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
inner_block(required)
Attributes
ctx(:map) (required)index(:integer) (required)- Global attributes are accepted.
Slots
inner_block
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
inner_block(required)
Attributes
ctx(:map) (required)index(:integer) (required)- Global attributes are accepted.
Slots
inner_block(required)
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
inner_block(required)
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
inner_block(required)
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
inner_block(required)
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
inner_block(required)
API
Pause autoplay from the client. Dispatches corex:carousel:pause on the carousel root.
<.action phx-click={Corex.Carousel.pause("my-carousel")}>Pause</.action>
<.carousel
id="my-carousel"
autoplay
loop
class="carousel"
items={[Corex.Image.new("/images/beach.jpg", alt: "Beach")]}
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>document.getElementById("my-carousel")?.dispatchEvent(
new CustomEvent("corex:carousel:pause", { bubbles: false, detail: {} })
);
Pause autoplay from the server. push_event("carousel_pause", %{"id" => carousel_id}).
<.action phx-click="carousel_pause">Pause</.action>
<.carousel
id="my-carousel"
autoplay
loop
class="carousel"
items={[Corex.Image.new("/images/beach.jpg", alt: "Beach")]}
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>def handle_event("carousel_pause", _params, socket) do
{:noreply, Corex.Carousel.pause(socket, "my-carousel")}
end
Start or resume autoplay from the client. Dispatches corex:carousel:play on the carousel root.
<.action phx-click={Corex.Carousel.play("my-carousel")}>Play</.action>
<.action phx-click={Corex.Carousel.pause("my-carousel")}>Pause</.action>
<.carousel
id="my-carousel"
autoplay
loop
class="carousel"
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall")
]}
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>document.getElementById("my-carousel")?.dispatchEvent(
new CustomEvent("corex:carousel:play", { bubbles: false, detail: {} })
);
Start or resume autoplay from the server. push_event("carousel_play", %{"id" => carousel_id}).
<.action phx-click="carousel_play">Play</.action>
<.carousel
id="my-carousel"
autoplay
loop
class="carousel"
items={[Corex.Image.new("/images/beach.jpg", alt: "Beach")]}
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>def handle_event("carousel_play", _params, socket) do
{:noreply, Corex.Carousel.play(socket, "my-carousel")}
end
Same as scroll_next/2 with instant: false.
Scroll to the next page from the client. Dispatches corex:carousel:scroll-next.
Pass true as the second argument for an instant jump (no animation).
<.action phx-click={Corex.Carousel.scroll_next("my-carousel")}>Next</.action>
<.action phx-click={Corex.Carousel.scroll_next("my-carousel", true)}>Next (instant)</.action>
<.carousel
id="my-carousel"
loop
class="carousel"
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall")
]}
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>document.getElementById("my-carousel")?.dispatchEvent(
new CustomEvent("corex:carousel:scroll-next", { bubbles: false, detail: { instant: true } })
);
Scroll to the next page from the server. push_event("carousel_scroll_next", %{"id" => id, "instant" => boolean}).
<.action phx-click="carousel_next">Next</.action>
<.carousel
id="my-carousel"
loop
class="carousel"
items={[Corex.Image.new("/images/beach.jpg", alt: "Beach")]}
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>def handle_event("carousel_next", _params, socket) do
{:noreply, Corex.Carousel.scroll_next(socket, "my-carousel")}
end
Same as scroll_prev/2 with instant: false.
Scroll to the previous page from the client. Dispatches corex:carousel:scroll-prev.
Pass true as the second argument for an instant jump (no animation).
<.action phx-click={Corex.Carousel.scroll_prev("my-carousel")}>Prev</.action>
<.action phx-click={Corex.Carousel.scroll_prev("my-carousel", true)}>Prev (instant)</.action>
<.carousel
id="my-carousel"
loop
class="carousel"
items={[
Corex.Image.new("/images/beach.jpg", alt: "Beach"),
Corex.Image.new("/images/fall.jpg", alt: "Fall")
]}
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>document.getElementById("my-carousel")?.dispatchEvent(
new CustomEvent("corex:carousel:scroll-prev", { bubbles: false, detail: {} })
);
Scroll to the previous page from the server. push_event("carousel_scroll_prev", %{"id" => id, "instant" => boolean}).
<.action phx-click="carousel_prev">Prev</.action>
<.carousel
id="my-carousel"
loop
class="carousel"
items={[Corex.Image.new("/images/beach.jpg", alt: "Beach")]}
>
<:prev_trigger><.heroicon name="hero-arrow-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-arrow-right" /></:next_trigger>
</.carousel>def handle_event("carousel_prev", _params, socket) do
{:noreply, Corex.Carousel.scroll_prev(socket, "my-carousel")}
end