View as Markdown

Pagination

Pagination with page navigation, next and previous links.

100%
Loading...
<twig:Pagination>
    <twig:Pagination:Content>
        <twig:Pagination:Item>
            <twig:Pagination:Previous href="#" />
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#">1</twig:Pagination:Link>
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#" active>2</twig:Pagination:Link>
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#">3</twig:Pagination:Link>
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Ellipsis />
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Next href="#" />
        </twig:Pagination:Item>
    </twig:Pagination:Content>
</twig:Pagination>

Installation

php bin/console ux:install pagination --kit shadcn

Install the following Composer dependencies:

composer require symfony/ux-icons twig/html-extra:^3.24.0 symfony/ux-twig-component:^3.5 tales-from-a-dev/twig-tailwind-extra:^1.3.0 twig/extra-bundle

Copy the following file(s) into your app:

templates/components/Pagination.html.twig
{# @block content The pagination structure, typically a `Pagination:Content`. #}
<nav
    data-slot="pagination"
    aria-label="pagination"
    role="navigation"
    {{ attributes.defaults({class: 'mx-auto flex w-full justify-center'|tailwind_classes}) }}
>
    {%- block content %}{% endblock -%}
</nav>
templates/components/Pagination/Content.html.twig
{# @block content The pagination items, typically multiple `Pagination:Item` components. #}
<ul
    data-slot="pagination-content"
    {{ attributes.defaults({class: 'flex items-center gap-0.5'|tailwind_classes}) }}
>
    {%- block content %}{% endblock -%}
</ul>
templates/components/Pagination/Ellipsis.html.twig
<span
    aria-hidden="true"
    data-slot="pagination-ellipsis"
    {{ attributes.defaults({class: ('flex size-8 items-center justify-center [&_svg:not([class*=\'size-\'])]:size-4 ')|tailwind_classes}) }}
>
    <twig:ux:icon name="lucide:more-horizontal" />
    <span class="sr-only">More pages</span>
</span>
templates/components/Pagination/Item.html.twig
{# @block content The pagination item content, typically a `Pagination:Link`. #}
<li data-slot="pagination-item" {{ attributes }}>
    {%- block content %}{% endblock -%}
</li>
templates/components/Pagination/Link.html.twig
{# @prop active boolean Whether this is the current page. #}
{# @block content The page number or navigation icon. #}
{%- props active = false -%}
<twig:Button
    as="a"
    variant="{{ active ? 'outline' : 'ghost' }}"
    size="icon"
    aria-current="{{ active ? 'page' : 'false' }}"
    data-slot="pagination-link"
    data-active="{{ active ? 'true' : 'false' }}"
    {{ ...attributes }}
>
    {{- block(outerBlocks.content) -}}
</twig:Button>
templates/components/Pagination/Next.html.twig
{# @prop text string The label text. #}
{%- props text = 'Next' -%}
<twig:Pagination:Link
    aria-label="Go to next page"
    size="default"
    {{ ...attributes.defaults({class: 'ltr:pr-1.5! rtl:pe-1.5!'|tailwind_classes}) }}
>
    <span class="hidden sm:block">{{ text }}</span>
    <twig:ux:icon name="lucide:chevron-right" data-icon="inline-end" class="rtl:rotate-180" />
</twig:Pagination:Link>
templates/components/Pagination/Previous.html.twig
{# @prop text string The label text. #}
{%- props text = 'Previous' -%}
<twig:Pagination:Link
    aria-label="Go to previous page"
    size="default"
    {{ ...attributes.defaults({class: 'ltr:pl-1.5! rtl:ps-1.5!'|tailwind_classes}) }}
>
    <twig:ux:icon name="lucide:chevron-left" data-icon="inline-start" class="rtl:rotate-180" />
    <span class="hidden sm:block">{{ text }}</span>
</twig:Pagination:Link>
templates/components/Button.html.twig
{# @prop variant 'default'|'secondary'|'destructive'|'outline'|'ghost'|'link' The visual style variant. #}
{# @prop size 'default'|'xs'|'sm'|'lg'|'icon'|'icon-xs'|'icon-sm'|'icon-lg' The button size. #}
{# @prop as 'button' The HTML tag to render. #}
{# @block content The button label and/or icon. #}
{%- props variant = 'default', size = 'default', as = 'button' -%}
{%- set style = html_cva(
    base: "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
    variants: {
        variant: {
            default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
            outline: 'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
            secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
            ghost: 'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',
            destructive: 'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40',
            link: 'text-primary underline-offset-4 hover:underline',
        },
        size: {
            default: 'h-8 gap-1.5 px-2.5 ltr:has-data-[icon=inline-end]:pr-2 rtl:has-data-[icon=inline-end]:pe-2 ltr:has-data-[icon=inline-start]:pl-2 rtl:has-data-[icon=inline-start]:ps-2',
            xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg ltr:has-data-[icon=inline-end]:pr-1.5 rtl:has-data-[icon=inline-end]:pe-1.5 ltr:has-data-[icon=inline-start]:pl-1.5 rtl:has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3",
            sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg ltr:has-data-[icon=inline-end]:pr-1.5 rtl:has-data-[icon=inline-end]:pe-1.5 ltr:has-data-[icon=inline-start]:pl-1.5 rtl:has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3.5",
            lg: 'h-9 gap-1.5 px-2.5 ltr:has-data-[icon=inline-end]:pr-2 rtl:has-data-[icon=inline-end]:pe-2 ltr:has-data-[icon=inline-start]:pl-2 rtl:has-data-[icon=inline-start]:ps-2',
            icon: 'size-8',
            'icon-xs': "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
            'icon-sm': 'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg',
            'icon-lg': 'size-9',
        },
    },
) -%}
<{{ as }}
    data-slot="button"
    data-size="{{ size }}"
    data-variant="{{ variant }}"
    {{ attributes.defaults({
        class: style.apply({variant: variant, size: size})|tailwind_classes,
        type: 'button',
    }) }}
>
    {%- block content %}{% endblock -%}
</{{ as }}>

Usage

<twig:Pagination>
    <twig:Pagination:Content>
        <twig:Pagination:Item>
            <twig:Pagination:Previous href="#" />
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#">1</twig:Pagination:Link>
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#" active>2</twig:Pagination:Link>
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#">3</twig:Pagination:Link>
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Ellipsis />
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Next href="#" />
        </twig:Pagination:Item>
    </twig:Pagination:Content>
</twig:Pagination>

Examples

Simple

A simple pagination with only page numbers.

100%
Loading...
<twig:Pagination>
    <twig:Pagination:Content>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#">1</twig:Pagination:Link>
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#" active>2</twig:Pagination:Link>
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#">3</twig:Pagination:Link>
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#">4</twig:Pagination:Link>
        </twig:Pagination:Item>
        <twig:Pagination:Item>
            <twig:Pagination:Link href="#">5</twig:Pagination:Link>
        </twig:Pagination:Item>
    </twig:Pagination:Content>
</twig:Pagination>

Icons Only

Use just the previous and next buttons without page numbers. This is useful for data tables with a rows per page selector.

100%
Loading...
<div class="flex items-center justify-between gap-4">
    <twig:Field orientation="horizontal" class="w-fit">
        <twig:Field:Label for="select-rows-per-page">Rows per page</twig:Field:Label>
        <twig:Select id="select-rows-per-page" class="w-20">
            <option value="10">10</option>
            <option value="25" selected>25</option>
            <option value="50">50</option>
            <option value="100">100</option>
        </twig:Select>
    </twig:Field>
    <twig:Pagination class="mx-0 w-auto">
        <twig:Pagination:Content>
            <twig:Pagination:Item>
                <twig:Pagination:Previous href="#" />
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Next href="#" />
            </twig:Pagination:Item>
        </twig:Pagination:Content>
    </twig:Pagination>
</div>

RTL

To enable RTL support, set the dir="rtl" attribute on the root element.

100%
Loading...
<div class="flex flex-col gap-8">
    {# Arabic #}
    <twig:Pagination dir="rtl">
        <twig:Pagination:Content>
            <twig:Pagination:Item>
                <twig:Pagination:Previous href="#" text="السابق" />
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Link href="#">١</twig:Pagination:Link>
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Link href="#" active>٢</twig:Pagination:Link>
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Link href="#">٣</twig:Pagination:Link>
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Ellipsis />
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Next href="#" text="التالي" />
            </twig:Pagination:Item>
        </twig:Pagination:Content>
    </twig:Pagination>

    {# Hebrew #}
    <twig:Pagination dir="rtl">
        <twig:Pagination:Content>
            <twig:Pagination:Item>
                <twig:Pagination:Previous href="#" text="הקודם" />
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Link href="#">1</twig:Pagination:Link>
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Link href="#" active>2</twig:Pagination:Link>
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Link href="#">3</twig:Pagination:Link>
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Ellipsis />
            </twig:Pagination:Item>
            <twig:Pagination:Item>
                <twig:Pagination:Next href="#" text="הבא" />
            </twig:Pagination:Item>
        </twig:Pagination:Content>
    </twig:Pagination>
</div>

API Reference

<twig:Pagination>

Block Description
content The pagination structure, typically a Pagination:Content.

<twig:Pagination:Content>

Block Description
content The pagination items, typically multiple Pagination:Item components.

<twig:Pagination:Item>

Block Description
content The pagination item content, typically a Pagination:Link.
Prop Type Default
active 
boolean false
Block Description
content The page number or navigation icon.

<twig:Pagination:Next>

Prop Type Default
text 
string 'Next'

<twig:Pagination:Previous>

Prop Type Default
text 
string 'Previous'