Skip to main content
Copy for LLM

Pagination

Navigate through paginated result sets. The component is a thin UI layer over Laravel's LengthAwarePaginator — pass the paginator instance and the component renders the appropriate navigation controls.

Basic Usage

In your Livewire component (or controller):

public function render()
{
    return view('livewire.users', [
        'users' => User::paginate(15),
    ]);
}

In the Blade view:

<x-wirekit::pagination :paginator="$users" />

The component renders nothing if the paginator only has a single page.

Variants

Full (default)

Shows numbered page links, prev/next arrows, and a "Showing X to Y of Z results" summary. Use for long lists where jumping to a specific page matters.

Full variant (default)

Simple

Shows prev/next buttons and a "Page X of Y" label. Ideal for infinite scrolls or when jumping to arbitrary pages is not needed.

Simple variant

Mini

Just prev/next buttons — the most compact form, useful in tight layouts like sidebar widgets.

Mini variant

Alignment

The justify prop controls how the summary text and page buttons are spread within the container:

justify=between (default: summary left, pages right)
justify=center (everything centered)
justify=end (everything right-aligned)
justify=start (everything left-aligned)

The full variant with justify="between" (default) spreads the "Showing X to Y" summary to the left and page numbers to the right — the most common table layout pattern.

Width & Layout

The pagination stretches to fill its parent width (w-full), which allows justify="between" to spread its children apart. Wrap it in a constrained container if you need a narrower pagination area.

In a Livewire Component

Every page control is a link, so in a Livewire component a click loads the whole page again, and whatever the component holds outside the URL is gone afterwards: a filter the reader set is empty on the next page. Add livewire, and a click turns the page inside the component instead:

<x-wirekit::pagination :paginator="$orders" livewire />

Each control stays a link to its page, so it still opens in a new tab and still works before Livewire has loaded. A click calls the component's gotoPage() for the paginator's own page name, which WithPagination provides, so two paginated lists in one component turn their own pages. After the turn the page scrolls back to the top, the way Livewire's own pager does. Give scroll-to a selector to bring another element into view, or pass :scroll-to="false" to keep the reader where they are. The scroll moves as your page's scroll-behavior says, and jumps where the reader asked for reduced motion.

$paginator->links() renders the view a component names in paginationView(). Name WireKit's, and every links() call in the component renders this pager:

use Livewire\Component;
use Livewire\WithPagination;

class Orders extends Component
{
    use WithPagination;

    public function paginationView(): string
    {
        return 'wirekit::pagination.livewire';
    }

    public function paginationSimpleView(): string
    {
        return 'wirekit::pagination.livewire-simple';
    }
}

paginationSimpleView() serves simplePaginate() and cursorPaginate(), which render previous and next only. A scroll target passed to links() reaches these views as well, false included.

Choosing the Page Size

per-page-options puts a choice of page size beside the summary. Bind it to a property of your Livewire component with wire:model, and read that property where you paginate:

{{-- 1. The sizes on offer, and the property the choice sets --}}
<x-wirekit::pagination
    :paginator="$orders"
    livewire
    :per-page-options="[10, 25, 50, 100]"
    wire:model.live="perPage"
/>
// 2. The component paginates with the reader's choice
public int $perPage = 25;

public function render(): View
{
    return view('livewire.orders', [
        'orders' => Order::query()->paginate($this->perPage),
    ]);
}

With livewire the change also goes back to the first page, because the page the reader was on may not exist at the new size. Without livewire, reset it yourself in updatedPerPage() with $this->resetPage(). Check the value before you paginate with it: wire:model sets whatever the browser sends.

The size the list is using is always one of the choices, even when it is not in the list you passed, so the control never shows a size the list is not using. And the pager stays when everything fits one page, as the summary and the choice without page links: otherwise picking the largest size would remove the control that picks a smaller one again. An empty list still renders no pager.

Outside Livewire, leave wire:model off. The choice is then a small form that sends it as per_page (rename it with per-page-name), keeps the query string your page links carry, and drops the page number, so the list starts at its first page. The form has its own Apply button, and picking a size does not send it: on Windows and Linux the arrow keys change a closed select's value, so a select that loaded a new page on every change would reload the list at each key and put the reader back at the top of the page:

Choosing the page size

A List That Fits One Page

A pager with nothing to page through renders nothing, so a list of seven results does not say it has seven while a list of thirty does. summary-on-single-page keeps the summary there:

{{-- 1. The count stays when every result fits one page --}}
<x-wirekit::pagination :paginator="$orders" summary-on-single-page />

The summary then stands on its own, outside a navigation landmark, because there is nothing to navigate. An empty list still renders nothing, since its own empty state speaks. A paginator that does not know its total, such as the one simplePaginate() returns or a cursor paginator, has no summary to show. With per-page-options the summary is there on a single page anyway, beside the choice.

Translation

Every visible string runs through Laravel's __(), so the component follows your app locale with no props to set. WireKit ships catalogs for several languages, so these strings may already be translated for you — see Localization. To change the wording, add the keys to your app's own JSON language files (lang/de.json, lang/fr.json, …); yours win per key.

Each key carries the wirekit:: prefix, which is part of the key rather than a separate namespace — write it exactly as shown or the entry will not be found.

Key Where it appears
wirekit::Pagination the nav landmark's aria-label
wirekit::Showing :first to :last of :total results the summary line (full variant), one form per number, chosen by the total
wirekit::Page :current of :last the centered label (simple variant)
wirekit::Previous / wirekit::Next the prev/next controls and their aria-labels
wirekit::Go to page :page each numbered link's aria-label
wirekit::Per page the words that name the page-size choice
wirekit::Apply the button that sends the page-size choice when scripts are off
{
    "wirekit::Pagination": "Seitennummerierung",
    "wirekit::Showing :first to :last of :total results": "Zeige :first bis :last von :total Ergebnis|Zeige :first bis :last von :total Ergebnissen",
    "wirekit::Page :current of :last": "Seite :current von :last",
    "wirekit::Previous": "Zurück",
    "wirekit::Next": "Weiter",
    "wirekit::Go to page :page": "Gehe zu Seite :page"
}

The two sentences are single keys with placeholders rather than assembled from separate words, so you can reorder them freely — a locale that needs the total before the range, or a different particle between the numbers, writes exactly that. The numbers keep their emphasis wherever you move the placeholders.

Props

Prop Type Default Description
paginator LengthAwarePaginator null The Laravel paginator instance (required)
variant string 'full' 'full', 'simple', 'mini'
justify string 'between' Flex layout: 'between' (summary left, pages right), 'center', 'start', 'end'
previous-label string null Word for the backward control. Previous is correct on a page-ordered list and misleading on a reverse-chronological one, where moving "next" goes backward in time — on a changelog or an activity feed, previous-label="Newer" says what the button does. Leave it unset to keep the translated default.
next-label string null Word for the forward control — the companion to previous-label.
livewire boolean false Turn pages inside a Livewire component. Each control keeps its link, and a click calls the component's gotoPage() for the paginator's page name. See In a Livewire Component.
scroll-to string|false 'body' With livewire: the selector brought into view after a page turn, looked up among the pager's ancestors first and then in the page. false keeps the scroll position.
per-page-options array|string|null null Sizes to choose from beside the summary, e.g. [10, 25, 50, 100]. Bind the choice with wire:model on the pager. See Choosing the Page Size.
per-page-name string 'per_page' The query parameter that carries the choice when the pager is not bound with wire:model.
summary-on-single-page boolean false Show the summary when every result fits one page. See A List That Fits One Page.
scope string|null null Scoped personalization name

Paginator Object Reference

The component reads the following methods/properties from the paginator; all are provided automatically by Laravel's Model::paginate() and DB::table()->paginate():

Accessor Used for
hasPages() Early-return when the collection fits on a single page (component renders nothing)
currentPage() Highlight the current page with aria-current="page"
lastPage() Render the "Page X of Y" summary in simple variant
firstItem() / lastItem() Render the "Showing X to Y" range in full variant
total() Render the "of Z results" total in full variant
previousPageUrl() / nextPageUrl() Prev/next arrow hrefs
linkCollection() Numbered page links (trimmed to exclude prev/next)
getPageName() / getCursorName() The page name a Livewire page turn is sent for (livewire only)

A Paginator from simplePaginate() and a CursorPaginator from cursorPaginate() know whether another page exists, but not how many there are. The component renders mini for both, whatever variant asks for, because previous and next are all they can drive. Pass variant="mini" to say so in your markup.

Accessibility

  • The outer element is <nav role="navigation" aria-label="Pagination"> — screen readers announce this as a navigation landmark
  • The current page uses aria-current="page" per WAI-ARIA Authoring Practices
  • In the full variant the prev/next controls are glyphs, so they carry aria-label="Previous" / aria-label="Next"; the simple and mini variants render the words themselves and are named by their own text
  • A prev/next control at a boundary is rendered as <span role="link" aria-disabled="true"> instead of <a> — it cannot be focused or followed, and a screen reader announces it as an unavailable link rather than skipping it, so a reader on the first or last page learns they are at the edge just as a sighted reader does from the dimmed control
  • Ellipses (…) are rendered with aria-hidden="true" (decorative)

Keyboard Interaction

Key Action
Tab Move focus through page links and prev/next buttons
Enter Follow the focused link

Pagination renders as a list of native links and buttons — keyboard interaction is delegated to the browser.

Pitfalls

  • Outside Livewire, keep a pager with per-page-options out of your own <form>. Its page-size choice is a form of its own, and a browser does not nest forms: inside yours, the pager's form is dropped, Apply sends your form, and the controls after the pager are no longer part of your form. Place the pager after your form closes, or bind the choice with wire:model.
  • Don't drive pagination via wire:click events on each link. The component renders native <a> elements pointing at real URLs (Laravel's linkCollection()); wire:click would intercept browser navigation and break the back button.
  • Don't show all 100 pages. The component already truncates with ellipses (…) — overriding the truncation defeats accessibility (every link must fit in the keyboard tab order).
  • A cursor paginator always renders as mini, whatever you ask for. simple and full state a page number or a total, and a CursorPaginator has neither by design — that is the point of cursor pagination, which exists for endless append-only lists that cannot afford a COUNT(*). The component detects the shape and renders previous/next rather than failing, and says so in the log when APP_DEBUG is on. Pass variant="mini" to make the intent explicit and silence the note.

Design Tokens

Element Token
Button background --color-wk-bg-elevated
Button border --color-wk-border
Button hover background --color-wk-bg-muted
Button hover border --color-wk-border-hover
Active page background --color-wk-accent
Active page text --color-wk-accent-fg
Disabled state --color-wk-bg-subtle, --color-wk-text-subtle and --color-wk-border-subtle, without an opacity
Text color --color-wk-text
Muted text (summary) --color-wk-text-muted
Button radius --radius-wk-md
Button height --size-wk-sm

Customization

Override defaults without publishing views via config/wirekit.php:

'components' => [
    'pagination' => [
        'variant' => 'simple',
    ],
],

Usage & Conventions

Prop conventions — this component uses one or more of the shared semantic prop names (intent / variant / tone / surface). See Prop naming conventions for the canonical vocabulary, alias matrix, and decision tree.

Further Reading

Updated in WireKit v2.66.0 (2026-10-13)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store