Corex. Pagination
(Corex v0.2.0)
View Source
Pagination for Phoenix LiveView. Behavior follows Zag.js Pagination.
Pagination and Carousel both use 1-based page (first page is 1). Page numbers and ellipses are rendered on the server so the control is complete before the hook runs.
Anatomy
Minimal
<.pagination class="pagination" count={95} page_size={10}>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>Controlled page
<.pagination
class="pagination"
count={18}
page={@page}
page_size={4}
controlled
on_page_change="pagination_controlled_changed"
>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>def mount(_params, _session, socket) do
{:ok, assign(socket, :page, 1)}
end
def handle_event("pagination_controlled_changed", %{"page" => page}, socket) do
{:noreply, assign(socket, :page, page)}
endLink mode
Set type={:link}, to, and optional page_param / page_size_param. The hook builds href on each control (SSR and client).
redirect | Behavior |
|---|---|
:href (default) | Full page load |
:patch | data-phx-link="patch" (same as <.navigate type="patch">) |
:navigate | data-phx-link="redirect" (same as <.navigate type="navigate">) |
For assigns-driven state, prefer type={:button}, controlled, and on_page_change instead of link mode.
Translation
<.pagination
class="pagination"
count={95}
page_size={10}
translation={%Corex.Pagination.Translation{
prev_trigger_label: Corex.Gettext.gettext("Previous page"),
next_trigger_label: Corex.Gettext.gettext("Next page"),
item_label:
Corex.Gettext.gettext("Page %{page} of %{total_pages}",
page: "%{page}",
total_pages: "%{total_pages}"
)
}}
>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>API
Requires a stable id on <.pagination>.
| Function | Action | Returns |
|---|---|---|
set_page/2 | Set active page (client) | %Phoenix.LiveView.JS{} |
set_page/3 | Set active page (server) | socket |
set_page_size/2 | Set page size (client) | %Phoenix.LiveView.JS{} |
set_page_size/3 | Set page size (server) | socket |
go_to_next_page/1 | Next page (client) | %Phoenix.LiveView.JS{} |
go_to_next_page/2 | Next page (server) | socket |
go_to_prev_page/1 | Previous page (client) | %Phoenix.LiveView.JS{} |
go_to_prev_page/2 | Previous page (server) | socket |
go_to_first_page/1 | First page (client) | %Phoenix.LiveView.JS{} |
go_to_first_page/2 | First page (server) | socket |
go_to_last_page/1 | Last page (client) | %Phoenix.LiveView.JS{} |
go_to_last_page/2 | Last page (server) | socket |
set_page
<.action phx-click={Corex.Pagination.set_page("pagination-api-bind", 5)} class="button ui-size-sm">5</.action>
<.pagination id="pagination-api-bind" class="pagination" count={95} page={5} page_size={10}>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>def handle_event("pagination_api_page_3", _, socket) do
{:noreply, Corex.Pagination.set_page(socket, "pagination-api-srv", 3)}
endEvents
Pick an event name and pass it to on_* on <.pagination>.
Server events
| Event | When | Payload |
|---|---|---|
on_page_change="pagination_page_changed" | Active page changes | %{"id" => id, "page" => page} |
on_page_size_change="pagination_page_size_changed" | Page size changes | %{"id" => id, "page_size" => page_size} |
on_page_change
<.pagination class="pagination" count={95} page_size={10} on_page_change="pagination_page_changed">
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>def handle_event("pagination_page_changed", %{"id" => id, "page" => page}, socket) do
{:noreply, assign(socket, :page, page)}
endClient events
| Event | When | event.detail |
|---|---|---|
on_page_change_client="pagination-page-changed" | Active page changes | id, page, page_size |
on_page_size_change_client="pagination-page-size-changed" | Page size changes | id, page_size |
on_page_change_client
<.pagination
id="pagination-events-client"
class="pagination"
count={95}
page_size={10}
on_page_change_client="pagination-page-changed"
>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>document.getElementById("pagination-events-client")?.addEventListener("pagination-page-changed", (e) => {
console.log(e.detail);
});Patterns
Controlled
Bind page (and optionally page_size) with controlled (true, :all, :page, or :page_size) and handle on_page_change so assigns stay the source of truth.
<.pagination
class="pagination"
count={18}
page={@page}
page_size={4}
controlled
on_page_change="pagination_controlled_changed"
>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>Slice list data on the server (Zag api.slice is client-only):
offset = (page - 1) * page_size
Enum.slice(items, offset, page_size)Patch (URL)
Use type={:link}, controlled, to, and redirect={:patch} so page and page size sync from query params via handle_params/3.
<.pagination
class="pagination"
count={18}
page={@page}
page_size={4}
controlled={:all}
type={:link}
to="/posts"
redirect={:patch}
>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>Style
Target parts with data-scope and data-part, or import pagination.css and stack modifiers on the host.
[data-scope="pagination"][data-part="root"] {}
[data-scope="pagination"][data-part="item"] {}
[data-scope="pagination"][data-part="ellipsis"] {}
[data-scope="pagination"][data-part="prev-trigger"][data-disabled] {}
[data-scope="pagination"][data-part="next-trigger"][data-disabled] {}@import "../corex/corex.css";Stack modifiers on <.pagination class="pagination ...">. Combine axes, for example pagination ui-accent ui-size-lg or pagination ui-info.
Axes: Semantic (ui-accent, ui-brand, ui-alert, ui-info, ui-success), Size (ui-size-sm … ui-size-xl), Radius (ui-rounded-*). No variant axis. See the modifier guide.
Semantic modifiers set palette variables on item triggers. Unselected items stay neutral; the active page always fills (--ctl-fill, or ink when no semantic role is set).
Semantic
Palette variables for pagination ink and the filled active page.
| Modifier | Classes |
|---|---|
| Default | pagination |
| Accent | pagination ui-accent |
| Brand | pagination ui-brand |
| Alert | pagination ui-alert |
| Success | pagination ui-success |
| Info | pagination ui-info |
Size
| Modifier | Classes |
|---|---|
| Default | pagination |
| SM | pagination ui-size-sm |
| MD | pagination ui-size-md |
| LG | pagination ui-size-lg |
| XL | pagination ui-size-xl |
Rounded
| Modifier | Classes |
|---|---|
| Default | pagination |
| None | pagination ui-rounded-none |
| SM | pagination ui-rounded-sm |
| MD | pagination ui-rounded-md |
| LG | pagination ui-rounded-lg |
| XL | pagination ui-rounded-xl |
| Full | pagination ui-rounded-full |
Max width
| Modifier | Classes |
|---|---|
| Default | pagination |
| SM | pagination max-w-sm |
| MD | pagination max-w-md |
| LG | pagination max-w-lg |
| XL | pagination max-w-xl |
Summary
API
Go to page 1 from phx-click. Dispatches corex:pagination:go-to-first-page.
Go to first page from handle_event (pagination_go_to_first_page).
Go to the last page from phx-click. Dispatches corex:pagination:go-to-last-page.
Go to last page from handle_event (pagination_go_to_last_page).
Go to the next page from phx-click. Dispatches corex:pagination:go-to-next-page.
Go to the next page from handle_event (pagination_go_to_next_page).
Go to the previous page from phx-click. Dispatches corex:pagination:go-to-prev-page.
Go to the previous page from handle_event (pagination_go_to_prev_page).
Set the current 1-based page from phx-click. Dispatches corex:pagination:set-page with detail.page.
Set the current page from handle_event (pagination_set_page).
Change page size from phx-click. Dispatches corex:pagination:set-page-size with detail.page_size.
Change page size from handle_event (pagination_set_page_size).
Components
Attributes
id(:string)count(:integer) (required) - Total number of data items.page(:integer) - Active page (1-based). Defaults to1.page_size(:integer) - Items per page. Defaults to10.sibling_count(:integer) - Pages beside the active page. Defaults to1.boundary_count(:integer) - Pages at the start and end. Defaults to1.controlled(:any) - false — uncontrolled (default). true or :all — server drives both page and page_size. :page — server drives page only; use withon_page_change. :page_size — server drives page_size only; use withon_page_size_change.Defaults to
false.type(:atom) - Trigger element type passed to Zag (:buttonor:link). Defaults to:button. Must be one of:button, or:link.to(:string) - Base path for link mode; hook builds query URLs frompage_paramandpage_size_param. Defaults tonil.page_param(:string) - Query param name for page in link mode. Defaults to"page".page_size_param(:string) - Query param name for page size in link mode. Defaults to"page_size".redirect(:atom) - Navigation kind whentypeis:linkand LiveView is connected. Defaults to:href. Must be one of:href,:patch, or:navigate.dir(:string) - Defaults tonil.Must be one ofnil,"ltr", or"rtl".on_page_change(:string) - Defaults tonil.on_page_change_client(:string) - Defaults tonil.on_page_size_change(:string) - Defaults tonil.on_page_size_change_client(:string) - Defaults tonil.translation(Corex.Pagination.Translation) - Override translatable strings. Defaults tonil.Global attributes are accepted.
Slots
prev_trigger(required) - Accepts attributes:class(:string)
next_trigger(required) - Accepts attributes:class(:string)
ellipsis(required) - Accepts attributes:class(:string)
API
@spec go_to_first_page(String.t()) :: Phoenix.LiveView.JS.t()
Go to page 1 from phx-click. Dispatches corex:pagination:go-to-first-page.
<.action phx-click={Corex.Pagination.go_to_first_page("my-pagination")}>First</.action>
<.pagination id="my-pagination" class="pagination" count={95} page={1} page_size={10}>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>
@spec go_to_first_page(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Go to first page from handle_event (pagination_go_to_first_page).
def handle_event("first", _params, socket) do
{:noreply, Corex.Pagination.go_to_first_page(socket, "my-pagination")}
end
@spec go_to_last_page(String.t()) :: Phoenix.LiveView.JS.t()
Go to the last page from phx-click. Dispatches corex:pagination:go-to-last-page.
<.action phx-click={Corex.Pagination.go_to_last_page("my-pagination")}>Last</.action>
<.pagination id="my-pagination" class="pagination" count={95} page={1} page_size={10}>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>
@spec go_to_last_page(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Go to last page from handle_event (pagination_go_to_last_page).
def handle_event("last", _params, socket) do
{:noreply, Corex.Pagination.go_to_last_page(socket, "my-pagination")}
end
@spec go_to_next_page(String.t()) :: Phoenix.LiveView.JS.t()
Go to the next page from phx-click. Dispatches corex:pagination:go-to-next-page.
<.action phx-click={Corex.Pagination.go_to_next_page("my-pagination")}>Next</.action>
<.pagination id="my-pagination" class="pagination" count={95} page={1} page_size={10}>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>
@spec go_to_next_page(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Go to the next page from handle_event (pagination_go_to_next_page).
def handle_event("next", _params, socket) do
{:noreply, Corex.Pagination.go_to_next_page(socket, "my-pagination")}
end
@spec go_to_prev_page(String.t()) :: Phoenix.LiveView.JS.t()
Go to the previous page from phx-click. Dispatches corex:pagination:go-to-prev-page.
<.action phx-click={Corex.Pagination.go_to_prev_page("my-pagination")}>Previous</.action>
<.pagination id="my-pagination" class="pagination" count={95} page={1} page_size={10}>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>
@spec go_to_prev_page(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Go to the previous page from handle_event (pagination_go_to_prev_page).
def handle_event("prev", _params, socket) do
{:noreply, Corex.Pagination.go_to_prev_page(socket, "my-pagination")}
end
@spec set_page(String.t(), integer()) :: Phoenix.LiveView.JS.t()
Set the current 1-based page from phx-click. Dispatches corex:pagination:set-page with detail.page.
<.action phx-click={Corex.Pagination.set_page("my-pagination", 2)}>Page 2</.action>
<.pagination id="my-pagination" class="pagination" count={95} page={1} page_size={10}>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>document.getElementById("my-pagination")?.dispatchEvent(
new CustomEvent("corex:pagination:set-page", {
bubbles: false,
detail: { page: 2 },
})
);
@spec set_page(Phoenix.LiveView.Socket.t(), String.t(), integer()) :: Phoenix.LiveView.Socket.t()
Set the current page from handle_event (pagination_set_page).
def handle_event("set_page", %{"page" => page}, socket) do
{:noreply, Corex.Pagination.set_page(socket, "my-pagination", String.to_integer(page))}
end
@spec set_page_size(String.t(), integer()) :: Phoenix.LiveView.JS.t()
Change page size from phx-click. Dispatches corex:pagination:set-page-size with detail.page_size.
<.action phx-click={Corex.Pagination.set_page_size("my-pagination", 20)}>20 per page</.action>
<.pagination id="my-pagination" class="pagination" count={95} page={1} page_size={10}>
<:prev_trigger><.heroicon name="hero-chevron-left" /></:prev_trigger>
<:next_trigger><.heroicon name="hero-chevron-right" /></:next_trigger>
<:ellipsis><.heroicon name="hero-ellipsis-horizontal" /></:ellipsis>
</.pagination>
@spec set_page_size(Phoenix.LiveView.Socket.t(), String.t(), integer()) :: Phoenix.LiveView.Socket.t()
Change page size from handle_event (pagination_set_page_size).
def handle_event("set_size", _params, socket) do
{:noreply, Corex.Pagination.set_page_size(socket, "my-pagination", 25)}
end