Pagination

A navigation component for moving between pages of content. Built on Ark UI’s Pagination — supports controlled/uncontrolled modes, custom page sizes, and link-based navigation.

Docs

Basic

Basic pagination (100 items, 10 per page)

import {
  Pagination,
  PaginationPageList,
  PaginationFirstTrigger,
  PaginationPrevTrigger,
  PaginationNextTrigger,
  PaginationLastTrigger,
} from "~/components/pagination";

export default function PaginationBasicDemo() {
  return (
    <div>
      <div>
        <p class="text-sm text-muted-foreground mb-3">Basic pagination (100 items, 10 per page)</p>
        <Pagination count={100} pageSize={10} class="gap-1">
          <PaginationFirstTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="11 17 6 12 11 7" />
              <polyline points="18 17 13 12 18 7" />
            </svg>
          </PaginationFirstTrigger>
          <PaginationPrevTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="15 18 9 12 15 6" />
            </svg>
          </PaginationPrevTrigger>
          <PaginationPageList />
          <PaginationNextTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="9 18 15 12 9 6" />
            </svg>
          </PaginationNextTrigger>
          <PaginationLastTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="13 17 18 12 13 7" />
              <polyline points="6 17 11 12 6 7" />
            </svg>
          </PaginationLastTrigger>
        </Pagination>
      </div>
    </div>
  );
}

Installation

CLI

Run the following command to add the component to your project:

npx @ark-preset/cli@latest add pagination

Manual

Create the recipe file at src/components/recipes/pagination.ts:

import { tv, type VariantProps } from "tailwind-variants";

export const paginationVariants = tv({
  slots: {
    root: "mx-auto flex w-full justify-center",
    trigger:
      "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition duration-300 motion-safe:hover:-translate-y-0.5 motion-safe:active:translate-y-0.5 active:duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-8 min-w-8",
    item: [
      "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition duration-300 motion-safe:hover:-translate-y-0.5 motion-safe:active:translate-y-0.5 active:duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground",
      "h-8 min-w-8",
      "data-[selected]:bg-primary data-[selected]:text-primary-foreground data-[selected]:hover:bg-primary data-[selected]:hover:text-primary-foreground",
    ],
    ellipsis: "flex h-8 min-w-8 items-center justify-center text-sm",
  },
  variants: {
    size: {
      sm: {
        item: "h-7 min-w-7",
        ellipsis: "h-7 min-w-7",
      },
      md: {
        item: "h-8 min-w-8",
        ellipsis: "h-8 min-w-8",
      },
      lg: {
        item: "h-9 min-w-9",
        ellipsis: "h-9 min-w-9",
      },
    },
  },
  defaultVariants: {
    size: "md",
  },
});

export type PaginationVariants = VariantProps<typeof paginationVariants>;

Create the component directory and files.

src/components/pagination/pagination.base.tsx:

import { Pagination as ArkPagination } from "@ark-ui/solid/pagination";
import { createContext, useContext, splitProps, type Component } from "solid-js";
import { paginationVariants, type PaginationVariants } from "../recipes/pagination";

type PaginationVariantContextValue = Pick<PaginationVariants, "size">;

const PaginationVariantContext = createContext<PaginationVariantContextValue>();

const usePaginationVariant = () => useContext(PaginationVariantContext);

const styles = paginationVariants();

const Root: Component<ArkPagination.RootProps & PaginationVariants> = (props) => {
  const [local, others] = splitProps(props, ["class", "size"]);
  return (
    <PaginationVariantContext.Provider value={{ size: local.size }}>
      <ArkPagination.Root
        class={styles.root({ class: local.class, size: local.size })}
        {...others}
      />
    </PaginationVariantContext.Provider>
  );
};

const RootProvider: Component<ArkPagination.RootProviderProps & PaginationVariants> = (props) => {
  const [local, others] = splitProps(props, ["class", "size"]);
  return (
    <PaginationVariantContext.Provider value={{ size: local.size }}>
      <ArkPagination.RootProvider
        class={styles.root({ class: local.class, size: local.size })}
        {...others}
      />
    </PaginationVariantContext.Provider>
  );
};

const Ellipsis: Component<ArkPagination.EllipsisProps> = (props) => {
  return <ArkPagination.Ellipsis {...props} />;
};

const FirstTrigger: Component<ArkPagination.FirstTriggerProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkPagination.FirstTrigger class={styles.trigger({ class: local.class })} {...others} />;
};

const Item: Component<ArkPagination.ItemProps & PaginationVariants> = (props) => {
  const ctx = usePaginationVariant();
  const [local, others] = splitProps(props, ["class", "size"]);
  return (
    <ArkPagination.Item
      class={styles.item({
        class: local.class,
        size: local.size ?? ctx?.size,
      })}
      {...others}
    />
  );
};

const LastTrigger: Component<ArkPagination.LastTriggerProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkPagination.LastTrigger class={styles.trigger({ class: local.class })} {...others} />;
};

const NextTrigger: Component<ArkPagination.NextTriggerProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkPagination.NextTrigger class={styles.trigger({ class: local.class })} {...others} />;
};

const PrevTrigger: Component<ArkPagination.PrevTriggerProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkPagination.PrevTrigger class={styles.trigger({ class: local.class })} {...others} />;
};

export const Pagination = {
  Root,
  RootProvider,
  Ellipsis,
  FirstTrigger,
  Item,
  LastTrigger,
  NextTrigger,
  PrevTrigger,
};

export { PaginationVariantContext, usePaginationVariant };

src/components/pagination/index.tsx:

import { splitProps, For, type Component } from "solid-js";
import { Pagination as PaginationBase } from "./pagination.base";
import { Pagination as ArkPagination } from "@ark-ui/solid/pagination";
import type { PaginationVariants } from "../recipes/pagination";

const Pagination: Component<ArkPagination.RootProps & PaginationVariants> = (props) => {
  const [local, others] = splitProps(props, ["class", "size", "children"]);
  return (
    <PaginationBase.Root class={local.class} size={local.size} {...others}>
      {local.children}
    </PaginationBase.Root>
  );
};

const PaginationRoot = Pagination;

const PaginationItem: Component<ArkPagination.ItemProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <PaginationBase.Item class={local.class} {...others} />;
};

const PaginationPrevTrigger = PaginationBase.PrevTrigger;
const PaginationNextTrigger = PaginationBase.NextTrigger;
const PaginationFirstTrigger = PaginationBase.FirstTrigger;
const PaginationLastTrigger = PaginationBase.LastTrigger;
const PaginationEllipsis = PaginationBase.Ellipsis;

const PaginationPageList: Component = () => {
  return (
    <ArkPagination.Context>
      {(pagination) => (
        <For each={pagination().pages}>
          {(page, index) =>
            page.type === "page" ? (
              <PaginationBase.Item type="page" value={page.value}>
                {page.value}
              </PaginationBase.Item>
            ) : (
              <PaginationBase.Ellipsis index={index()}>...</PaginationBase.Ellipsis>
            )
          }
        </For>
      )}
    </ArkPagination.Context>
  );
};

export {
  Pagination,
  PaginationRoot,
  PaginationItem,
  PaginationPrevTrigger,
  PaginationNextTrigger,
  PaginationFirstTrigger,
  PaginationLastTrigger,
  PaginationEllipsis,
  PaginationPageList,
  PaginationBase,
};

export { paginationVariants, type PaginationVariants } from "../recipes/pagination";

Note: Make sure your project has the Tailwind CSS theme variables set up (--background, --foreground, --ring, --border, etc.) or override the utility classes to match your design system.

Usage

Controlled Page

Use page and onPageChange to control the current page externally.

Controlled page: 5

import { createSignal } from "solid-js";
import {
  Pagination,
  PaginationPageList,
  PaginationFirstTrigger,
  PaginationPrevTrigger,
  PaginationNextTrigger,
  PaginationLastTrigger,
} from "~/components/pagination";

export default function PaginationControlledDemo() {
  const [page, setPage] = createSignal(5);

  return (
    <div>
      <div>
        <p class="text-sm text-muted-foreground mb-3">
          Controlled page: <span class="font-medium text-foreground">{page()}</span>
        </p>
        <Pagination
          count={100}
          pageSize={10}
          page={page()}
          onPageChange={(e) => setPage(e.page)}
          class="gap-1"
        >
          <PaginationFirstTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="11 17 6 12 11 7" />
              <polyline points="18 17 13 12 18 7" />
            </svg>
          </PaginationFirstTrigger>
          <PaginationPrevTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="15 18 9 12 15 6" />
            </svg>
          </PaginationPrevTrigger>
          <PaginationPageList />
          <PaginationNextTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="9 18 15 12 9 6" />
            </svg>
          </PaginationNextTrigger>
          <PaginationLastTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="13 17 18 12 13 7" />
              <polyline points="6 17 11 12 6 7" />
            </svg>
          </PaginationLastTrigger>
        </Pagination>
      </div>
    </div>
  );
}

Size Variants

Use the size prop to control button dimensions — sm (h-7 min-w-7), md (h-8 min-w-8), lg (h-9 min-w-9).

Size sm

Size md

Size lg

import {
  Pagination,
  PaginationPageList,
  PaginationFirstTrigger,
  PaginationPrevTrigger,
  PaginationNextTrigger,
  PaginationLastTrigger,
} from "~/components/pagination";

function PaginationSized(props: { size: "sm" | "md" | "lg"; label: string }) {
  return (
    <div>
      <p class="text-sm text-muted-foreground mb-3">
        Size <span class="font-medium text-foreground">{props.label}</span>
      </p>
      <Pagination count={100} pageSize={10} size={props.size} class="gap-1">
        <PaginationFirstTrigger>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="16"
            height="16"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <polyline points="11 17 6 12 11 7" />
            <polyline points="18 17 13 12 18 7" />
          </svg>
        </PaginationFirstTrigger>
        <PaginationPrevTrigger>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="16"
            height="16"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <polyline points="15 18 9 12 15 6" />
          </svg>
        </PaginationPrevTrigger>
        <PaginationPageList />
        <PaginationNextTrigger>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="16"
            height="16"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <polyline points="9 18 15 12 9 6" />
          </svg>
        </PaginationNextTrigger>
        <PaginationLastTrigger>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="16"
            height="16"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <polyline points="13 17 18 12 13 7" />
            <polyline points="6 17 11 12 6 7" />
          </svg>
        </PaginationLastTrigger>
      </Pagination>
    </div>
  );
}

export default function PaginationSizeDemo() {
  return (
    <div class="flex flex-col gap-6">
      <PaginationSized size="sm" label="sm" />
      <PaginationSized size="md" label="md" />
      <PaginationSized size="lg" label="lg" />
    </div>
  );
}

Advanced Usage

RootProvider Pattern

For full control over the pagination machine state, use usePagination with PaginationBase.RootProvider.

RootProvider pattern

import { For } from "solid-js";
import { usePagination } from "@ark-ui/solid/pagination";
import { PaginationBase } from "~/components/pagination";

export default function PaginationRootProviderDemo() {
  const pagination = usePagination({ count: 100, pageSize: 10 });

  return (
    <div>
      <div>
        <p class="text-sm text-muted-foreground mb-3">RootProvider pattern</p>
        <PaginationBase.RootProvider value={pagination} class="gap-1">
          <PaginationBase.FirstTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="11 17 6 12 11 7" />
              <polyline points="18 17 13 12 18 7" />
            </svg>
          </PaginationBase.FirstTrigger>
          <PaginationBase.PrevTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="15 18 9 12 15 6" />
            </svg>
          </PaginationBase.PrevTrigger>
          <For each={pagination().pages}>
            {(page, _index) =>
              page.type === "page" ? (
                <PaginationBase.Item type="page" value={page.value}>
                  {page.value}
                </PaginationBase.Item>
              ) : (
                <PaginationBase.Ellipsis index={_index()}>...</PaginationBase.Ellipsis>
              )
            }
          </For>
          <PaginationBase.NextTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="9 18 15 12 9 6" />
            </svg>
          </PaginationBase.NextTrigger>
          <PaginationBase.LastTrigger>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <polyline points="13 17 18 12 13 7" />
              <polyline points="6 17 11 12 6 7" />
            </svg>
          </PaginationBase.LastTrigger>
        </PaginationBase.RootProvider>
      </div>
    </div>
  );
}

API Reference

See the Ark UI Pagination documentation.