Pagination
Page navigation with previous/next controls, page links, status text, disabled edges, outline/icon-only styles, a page-size slot, and a go-to-page form (GET, or Alpine state with x-page).
Preview
<x-ui.pagination>
<x-ui.pagination.content>
<x-ui.pagination.item>
<x-ui.pagination.previous href="#" />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">1</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#" :active="true">2</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">3</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.ellipsis />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">10</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.next href="#" />
</x-ui.pagination.item>
</x-ui.pagination.content>
</x-ui.pagination>
Installation
php artisan ui:add pagination
Registry contract
php artisan ui:add pagination
writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.
-
resources/views/components/ui/pagination.blade.php -
resources/views/components/ui/pagination/content.blade.php -
resources/views/components/ui/pagination/item.blade.php -
resources/views/components/ui/pagination/status.blade.php -
resources/views/components/ui/pagination/link.blade.php -
resources/views/components/ui/pagination/previous.blade.php -
resources/views/components/ui/pagination/next.blade.php -
resources/views/components/ui/pagination/ellipsis.blade.php -
resources/views/components/ui/pagination/jump.blade.php
- Registry dependencies
- None — installs on its own.
- Packages
-
composer: jml/brok:^0.2
Use with AI
A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.
# Brok UI: Pagination (`pagination`)
Page navigation with previous/next controls, page links, status text, disabled edges, outline/icon-only styles, a page-size slot, and a go-to-page form (GET, or Alpine state with x-page).
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add pagination
```
## Usage
```blade
<x-ui.pagination>
<x-ui.pagination.content>
<x-ui.pagination.item>
<x-ui.pagination.previous href="#" />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">1</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#" :active="true">2</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">3</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.ellipsis />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">10</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.next href="#" />
</x-ui.pagination.item>
</x-ui.pagination.content>
</x-ui.pagination>
```
## Props
- `align` (string, default `center`) — Declared by @props in the shipped Blade source.
- `disabled` (false|true, default `false`) — Documented catalog control used by the preview workbench.
- `href` (string, default `#`) — Declared by @props in the registry Blade source.
- `active` (bool, default `false`) — Declared by @props in the registry Blade source.
- `variant` (string, default `ghost`) — Declared by @props in the registry Blade source.
- `showLabel` (bool, default `true`) — Declared by @props in the registry Blade source.
- `label` (mixed|null, default `null`) — Declared by @props in the registry Blade source.
- `action` (mixed|null, default `null`) — Declared by @props in the registry Blade source.
- `name` (string, default `page`) — Declared by @props in the registry Blade source.
- `current` (int, default `1`) — Declared by @props in the registry Blade source.
- `total` (mixed|null, default `null`) — Declared by @props in the registry Blade source.
- `params` (array, default `[]`) — Declared by @props in the registry Blade source.
- `xPage` (mixed|null, default `null`) — Declared by @props in the registry Blade source.
- `xTotal` (mixed|null, default `null`) — Declared by @props in the registry Blade source.
- `buttonLabel` (mixed|null, default `null`) — Declared by @props in the registry Blade source.
## Use when
- Use to orient users and help them move across pages, sections, or commands.
- Navigating a long, server- or client-paginated list or table by page number.
- Needing a page-jump form, either a plain GET request or bound to Alpine/Livewire client state.
## Avoid when
- Do not hide primary wayfinding in novelty interactions or deep nested structures if straightforward navigation would be clearer.
- The list is short enough to show in full, or infinite scroll fits the content better than discrete pages.
- Only a simple prev/next flow is needed with no page numbers; a plain button pair is lighter weight.
## Anti-patterns
- Hiding primary wayfinding in novelty interactions
## Rules
- Use the `<brok:pagination>` tag (or `<x-ui.pagination>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.
## Links
- Docs: https://brokui.dev/docs/components/pagination
- Registry JSON (files, props, contract): https://brokui.dev/r/open/pagination.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Examples
Client Jump
{{-- Client mode: `x-page` names the Alpine state; Go (or Enter) clamps the number,
writes it back and dispatches `pagination:jump`. Nothing is submitted. --}}
<div x-data="{ page: 4, total: 24 }" class="flex w-full flex-col gap-4">
<x-ui.pagination align="between">
<x-ui.pagination.status class="px-0" x-text="`Page ${page} of ${total}`"></x-ui.pagination.status>
<x-ui.pagination.content>
<x-ui.pagination.item>
<x-ui.pagination.previous href="#" :show-label="false" @click.prevent="page = Math.max(1, page - 1)" />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.next href="#" :show-label="false" @click.prevent="page = Math.min(total, page + 1)" />
</x-ui.pagination.item>
</x-ui.pagination.content>
<x-ui.pagination.jump x-page="page" x-total="total" />
</x-ui.pagination>
</div>
<div class="flex flex-col gap-10">
{{-- Previous / Next only --}}
<div class="flex flex-col gap-3">
<p class="text-sm font-medium text-muted-foreground">{{ __('Previous / next only') }}</p>
<x-ui.pagination>
<x-ui.pagination.content>
<x-ui.pagination.item>
<x-ui.pagination.previous href="#" />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.next href="#" />
</x-ui.pagination.item>
</x-ui.pagination.content>
</x-ui.pagination>
</div>
{{-- Compact "Page X of Y" --}}
<div class="flex flex-col gap-3">
<p class="text-sm font-medium text-muted-foreground">{{ __('Compact') }}</p>
<x-ui.pagination>
<x-ui.pagination.content>
<x-ui.pagination.item>
<x-ui.pagination.previous href="#" :show-label="false" />
</x-ui.pagination.item>
<x-ui.pagination.status>{{ __('Page :current of :total', ['current' => 2, 'total' => 12]) }}</x-ui.pagination.status>
<x-ui.pagination.item>
<x-ui.pagination.next href="#" :show-label="false" />
</x-ui.pagination.item>
</x-ui.pagination.content>
</x-ui.pagination>
</div>
{{-- Outline buttons --}}
<div class="flex flex-col gap-3">
<p class="text-sm font-medium text-muted-foreground">{{ __('Outline buttons') }}</p>
<x-ui.pagination>
<x-ui.pagination.content>
<x-ui.pagination.item>
<x-ui.pagination.link href="#" variant="outline">1</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#" variant="outline" :active="true">2</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#" variant="outline">3</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#" variant="outline">4</x-ui.pagination.link>
</x-ui.pagination.item>
</x-ui.pagination.content>
</x-ui.pagination>
</div>
{{-- Disabled edges (first page) --}}
<div class="flex flex-col gap-3">
<p class="text-sm font-medium text-muted-foreground">{{ __('Disabled edges') }}</p>
<x-ui.pagination>
<x-ui.pagination.content>
<x-ui.pagination.item>
<x-ui.pagination.previous :disabled="true" />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#" :active="true">1</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">2</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">3</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.next href="#" />
</x-ui.pagination.item>
</x-ui.pagination.content>
</x-ui.pagination>
</div>
{{-- Result count + pager (spread to edges) --}}
<div class="flex flex-col gap-3">
<p class="text-sm font-medium text-muted-foreground">{{ __('Results summary') }}</p>
<x-ui.pagination align="between">
<p class="text-sm text-muted-foreground">{{ __('Showing :from–:to of :total results', ['from' => 1, 'to' => 10, 'total' => 97]) }}</p>
<x-ui.pagination.content>
<x-ui.pagination.item>
<x-ui.pagination.previous href="#" />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#" :active="true">1</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">2</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">3</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.next href="#" />
</x-ui.pagination.item>
</x-ui.pagination.content>
</x-ui.pagination>
</div>
{{-- Page-size select + status (interactive window) --}}
<div class="flex flex-col gap-3">
<p class="text-sm font-medium text-muted-foreground">{{ __('Rows per page') }}</p>
<x-ui.pagination align="between">
<div class="flex items-center gap-2">
<span class="text-sm text-muted-foreground">{{ __('Rows per page') }}</span>
<x-ui.select size="sm" class="w-20" aria-label="{{ __('Rows per page') }}">
<option>10</option>
<option>20</option>
<option>50</option>
</x-ui.select>
</div>
<x-ui.pagination.content>
<x-ui.pagination.status>{{ __('Page :current of :total', ['current' => 1, 'total' => 9]) }}</x-ui.pagination.status>
<x-ui.pagination.item>
<x-ui.pagination.previous href="#" :show-label="false" :disabled="true" />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.next href="#" :show-label="false" />
</x-ui.pagination.item>
</x-ui.pagination.content>
</x-ui.pagination>
</div>
</div>
With Jump
{{-- Status on the start edge, the pager in the middle, a go-to-page form on the end edge. --}}
<x-ui.pagination align="between">
<x-ui.pagination.status class="px-0">{{ __('Page :current of :total', ['current' => 4, 'total' => 24]) }}</x-ui.pagination.status>
<x-ui.pagination.content>
<x-ui.pagination.item>
<x-ui.pagination.previous href="#" :show-label="false" />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">3</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#" :active="true">4</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">5</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.next href="#" :show-label="false" />
</x-ui.pagination.item>
</x-ui.pagination.content>
<x-ui.pagination.jump :current="4" :total="24" :params="['sort' => 'newest']" />
</x-ui.pagination>
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| align | string | center | Declared by @props in the shipped Blade source. |
| disabled | false | true | false | Documented catalog control used by the preview workbench. |
| href | string | # | Declared by @props in the registry Blade source. |
| active | bool | false | Declared by @props in the registry Blade source. |
| variant | string | ghost | Declared by @props in the registry Blade source. |
| showLabel | bool | true | Declared by @props in the registry Blade source. |
| label | mixed | null | null | Declared by @props in the registry Blade source. |
| action | mixed | null | null | Declared by @props in the registry Blade source. |
| name | string | page | Declared by @props in the registry Blade source. |
| current | int | 1 | Declared by @props in the registry Blade source. |
| total | mixed | null | null | Declared by @props in the registry Blade source. |
| params | array | [] | Declared by @props in the registry Blade source. |
| xPage | mixed | null | null | Declared by @props in the registry Blade source. |
| xTotal | mixed | null | null | Declared by @props in the registry Blade source. |
| buttonLabel | mixed | null | null | Declared by @props in the registry Blade source. |
Slots
default— Compositional parts: content, links, previous/next, ellipsis, status, and jump.x-ui.pagination.content— Installed subcomponent from the registry item.x-ui.pagination.item— Installed subcomponent from the registry item.x-ui.pagination.status— Installed subcomponent from the registry item.x-ui.pagination.link— Installed subcomponent from the registry item.x-ui.pagination.previous— Installed subcomponent from the registry item.x-ui.pagination.next— Installed subcomponent from the registry item.x-ui.pagination.ellipsis— Installed subcomponent from the registry item.x-ui.pagination.jump— Installed subcomponent from the registry item.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- In server mode, pagination.jump renders a plain GET form so it works without JavaScript and with any server-side paginator.
- In client mode (xPage set), the same form never submits; typing a number and pressing Go or Enter clamps it to the valid range, writes it back to the Alpine expression, and dispatches a pagination:jump event.
- Previous/next and page links become disabled or drop out at the edges of the page range rather than linking to an invalid page.
- Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.
Guidance
Orient users and move between destinations.
Use when
- Use to orient users and help them move across pages, sections, or commands.
- Navigating a long, server- or client-paginated list or table by page number.
- Needing a page-jump form, either a plain GET request or bound to Alpine/Livewire client state.
Avoid when
- Do not hide primary wayfinding in novelty interactions or deep nested structures if straightforward navigation would be clearer.
- The list is short enough to show in full, or infinite scroll fits the content better than discrete pages.
- Only a simple prev/next flow is needed with no page numbers; a plain button pair is lighter weight.
Use instead
- Visible links and local navigation
Anti-patterns
- Hiding primary wayfinding in novelty interactions
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- native
- Focus
native
- The current page link is marked with aria-current="page" so assistive tech identifies it without relying on styling alone.
- The jump input carries a real associated label, so its purpose is announced even though it is a single number field.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, so keyboard focus is always visible. - Disabled and invalid states are conveyed to assistive tech, not by color alone.
- Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
- Labels go through
__()and layout uses logical properties (ms-*,text-start), so it mirrors underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
Add a stable wire:key when Livewire can reorder this interactive component.
<div wire:key="pagination-{{ $record->id }}">
<x-ui.pagination>
<x-ui.pagination.content>
<x-ui.pagination.item>
<x-ui.pagination.previous href="#" />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">1</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#" :active="true">2</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">3</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.ellipsis />
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.link href="#">10</x-ui.pagination.link>
</x-ui.pagination.item>
<x-ui.pagination.item>
<x-ui.pagination.next href="#" />
</x-ui.pagination.item>
</x-ui.pagination.content>
</x-ui.pagination>
</div>
Validation
Validation support: native. Keep the error message connected with aria-describedby.
<form wire:submit="save" class="space-y-2">
<brok:pagination
wire:model="value"
:aria-invalid="$errors->has('value') ? 'true' : 'false'"
aria-describedby="value-error"
/>
@error('value')
<p id="value-error" role="alert">{{ $message }}</p>
@enderror
<brok:button type="submit" wire:loading.attr="disabled">
<span wire:loading.remove>Save</span>
<span wire:loading>Saving…</span>
</brok:button>
</form>
Source
The exact, editable files ui:add writes
into your app. Previews render this same code; there are no preview-only components.
@props([
// `center` (default) keeps the classic centered pager; `between` spreads
// children to the edges for status-text / page-size-select layouts.
'align' => 'center',
])
@php
$justify = $align === 'between' ? 'justify-between' : 'justify-center';
@endphp
<nav role="navigation" aria-label="{{ __('Pagination navigation') }}" data-slot="pagination" data-align="{{ $align }}" {{ $attributes->merge(['class' => 'mx-auto flex w-full flex-wrap items-center gap-4 '.$justify]) }}>{{ $slot }}</nav>
<ul data-slot="pagination-content" {{ $attributes->merge(['class' => 'flex flex-row items-center gap-1']) }}>{{ $slot }}</ul>
<li data-slot="pagination-item" {{ $attributes }}>{{ $slot }}</li>
<li role="presentation" data-slot="pagination-status" {{ $attributes->merge(['class' => 'flex h-9 items-center px-3 text-sm text-muted-foreground']) }}>{{ $slot }}</li>
@props([
'href' => '#',
'active' => false,
'disabled' => false,
'variant' => 'ghost',
])
@php
$styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
$variant = $styles['normalizeVariant']($variant);
// `ghost` (default) keeps the original look; `outline` gives every page a
// visible border (Flexnative "outline buttons"). The active page always
// reads as selected regardless of variant.
$base = 'inline-flex h-9 min-w-9 items-center justify-center gap-1 rounded-md border px-3 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none';
if ($active) {
$state = 'border-border bg-accent text-accent-foreground';
} elseif ($variant === 'outline') {
$state = 'border-input bg-background text-foreground hover:bg-muted';
} else {
$state = 'border-transparent text-foreground hover:bg-muted';
}
$disabledClasses = $disabled ? ' pointer-events-none opacity-50' : '';
$classes = trim($base.' '.$state.$disabledClasses);
@endphp
@if ($disabled)
{{-- A disabled page is non-navigable: render a span, not an anchor, and
expose the state to assistive tech. --}}
<span role="link" data-slot="pagination-link" data-variant="{{ $variant }}" data-active="false" aria-disabled="true" {{ $attributes->merge(['class' => $classes]) }}>{{ $slot }}</span>
@else
<a href="{{ $href }}" data-slot="pagination-link" data-variant="{{ $variant }}" data-active="{{ $active ? 'true' : 'false' }}" @if($active) aria-current="page" @endif {{ $attributes->merge(['class' => $classes]) }}>{{ $slot }}</a>
@endif
@props([
'href' => '#',
'disabled' => false,
'showLabel' => true,
'label' => null,
])
@php
$text = $label ?? __('Previous');
// Icon-only mode keeps a square tap target and an accessible name; the
// visible word collapses to an sr-only label.
$padding = $showLabel ? 'ps-2 pe-3' : 'size-9 justify-center';
$base = 'inline-flex h-9 items-center justify-center gap-1 rounded-md text-sm font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none';
$classes = trim($base.' '.$padding.($disabled ? ' pointer-events-none opacity-50' : ''));
@endphp
@if ($disabled)
<span role="link" data-slot="pagination-previous" aria-disabled="true" aria-label="{{ $text }}" {{ $attributes->merge(['class' => $classes]) }}>
<svg class="size-4 rtl:-scale-x-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="m15 18-6-6 6-6"></path>
</svg>
@if ($showLabel)<span>{{ $text }}</span>@else<span class="sr-only">{{ $text }}</span>@endif
</span>
@else
<a href="{{ $href }}" data-slot="pagination-previous" aria-label="{{ $text }}" {{ $attributes->merge(['class' => $classes]) }}>
<svg class="size-4 rtl:-scale-x-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="m15 18-6-6 6-6"></path>
</svg>
@if ($showLabel)<span>{{ $text }}</span>@else<span class="sr-only">{{ $text }}</span>@endif
</a>
@endif
@props([
'href' => '#',
'disabled' => false,
'showLabel' => true,
'label' => null,
])
@php
$text = $label ?? __('Next');
// Icon-only mode keeps a square tap target and an accessible name; the
// visible word collapses to an sr-only label.
$padding = $showLabel ? 'ps-3 pe-2' : 'size-9 justify-center';
$base = 'inline-flex h-9 items-center justify-center gap-1 rounded-md text-sm font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none';
$classes = trim($base.' '.$padding.($disabled ? ' pointer-events-none opacity-50' : ''));
@endphp
@if ($disabled)
<span role="link" data-slot="pagination-next" aria-disabled="true" aria-label="{{ $text }}" {{ $attributes->merge(['class' => $classes]) }}>
@if ($showLabel)<span>{{ $text }}</span>@else<span class="sr-only">{{ $text }}</span>@endif
<svg class="size-4 rtl:-scale-x-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="m9 18 6-6-6-6"></path>
</svg>
</span>
@else
<a href="{{ $href }}" data-slot="pagination-next" aria-label="{{ $text }}" {{ $attributes->merge(['class' => $classes]) }}>
@if ($showLabel)<span>{{ $text }}</span>@else<span class="sr-only">{{ $text }}</span>@endif
<svg class="size-4 rtl:-scale-x-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="m9 18 6-6-6-6"></path>
</svg>
</a>
@endif
<span data-slot="pagination-ellipsis" aria-hidden="true" {{ $attributes->merge(['class' => 'flex size-9 items-center justify-center']) }}>
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="19" cy="12" r="1"></circle>
<circle cx="5" cy="12" r="1"></circle>
</svg>
<span class="sr-only">{{ __('More pages') }}</span>
</span>
@props([
// Where the form submits (GET). Defaults to the current URL, so `?page=N`
// lands on Laravel's paginator without extra wiring.
'action' => null,
// Query key for the page number.
'name' => 'page',
'current' => 1,
// Last page; leave null when unknown (the input then has no max).
'total' => null,
// Extra query params carried along (filters, sort) as hidden inputs.
'params' => [],
// Client mode: an Alpine expression holding the page number (`page`,
// `$wire.page`, `table.page`). The form then never submits: Go (or Enter)
// clamps the typed number, writes it back and dispatches
// `pagination:jump` { page }. `xTotal` is the matching last-page expression.
'xPage' => null,
'xTotal' => null,
'label' => null,
'buttonLabel' => null,
])
@php
$text = $label ?? __('Go to page');
$button = $buttonLabel ?? __('Go');
$id = 'pagination-jump-'.str()->random(6);
$client = $xPage !== null;
$maxExpr = $client ? ($xTotal ?? ($total !== null ? (int) $total : 'Infinity')) : null;
$field = 'inline-flex h-9 w-16 rounded-md border border-input bg-background px-3 text-center text-sm text-foreground shadow-xs transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring focus-visible:ring-offset-[length:var(--ring-offset-width)] focus-visible:ring-offset-background motion-reduce:transition-none';
$submit = 'inline-flex h-9 items-center justify-center rounded-md border border-input bg-background px-3 text-sm font-medium text-foreground shadow-xs transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring focus-visible:ring-offset-[length:var(--ring-offset-width)] focus-visible:ring-offset-background motion-reduce:transition-none';
@endphp
{{-- Type a page number and go. Server mode is a plain GET form, so it works
without JS and with any server-side paginator; client mode (`x-page`)
keeps the same markup and writes the Alpine state instead. Composes with
`align="between"`. --}}
<form
method="get"
@if ($action !== null) action="{{ $action }}" @endif
data-slot="pagination-jump"
data-mode="{{ $client ? 'client' : 'server' }}"
@if ($client)
novalidate
x-data="{ jumpDraft: '' }"
x-effect="jumpDraft = String({{ $xPage }})"
@submit.prevent="const max = Number({{ $maxExpr }}) || Infinity; const n = Math.min(Math.max(1, parseInt(jumpDraft, 10) || 1), max); {{ $xPage }} = n; jumpDraft = String(n); $dispatch('pagination:jump', { page: n })"
@endif
{{ $attributes->merge(['class' => 'flex items-center gap-2']) }}
>
@unless ($client)
@foreach ($params as $key => $value)
@if ($key !== $name)
<input type="hidden" name="{{ $key }}" value="{{ $value }}" />
@endif
@endforeach
@endunless
<label for="{{ $id }}" class="text-sm text-muted-foreground">{{ $text }}</label>
<input
id="{{ $id }}"
type="number"
name="{{ $name }}"
inputmode="numeric"
min="1"
@if ($client)
x-model="jumpDraft"
:max="{{ $maxExpr }} === Infinity ? null : {{ $maxExpr }}"
@else
@if ($total !== null) max="{{ (int) $total }}" @endif
value="{{ (int) $current }}"
@endif
required
class="{{ $field }}"
/>
@if ($client && $xTotal !== null)
<span class="text-sm text-muted-foreground" x-text="`{{ __('of :total', ['total' => '${'.$xTotal.'}']) }}`"></span>
@elseif ($total !== null)
<span class="text-sm text-muted-foreground">{{ __('of :total', ['total' => (int) $total]) }}</span>
@endif
<button type="submit" class="{{ $submit }}">{{ $button }}</button>
</form>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.4.2 - Status
- Stable
- License
-
open - Accessibility reviewed
- No review date recorded
- Last breaking change
- No date recorded
- Deprecation
- Not deprecated
- Contract
-
v6 - Foundation
-
≥ 1.0.0