Kosmesis
Components

Pagination

Page navigation links, composing Button's variants. Purely presentational.

Installation

npx kosmesis add pagination
pnpm dlx kosmesis add pagination
yarn dlx kosmesis add pagination
bunx kosmesis add pagination

This component builds on other Kosmesis components — install these first (via kosmesis add or this same manual process on their own pages): button.

Copy and paste the following code into your project.

pagination.tsx
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { buttonVariants } from "./button";import { cn } from "@/lib/utils";export interface PaginationProps {  class?: string;  children?: Children;}/** Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. Composes `Button` for its links. */@Component()export class Pagination extends StatelessComponent<PaginationProps> {  render() {    const { class: cls, children } = this.props;    return (      <nav role="navigation" aria-label="pagination" class={cn("mx-auto flex w-full justify-center", cls)}>        {children}      </nav>    );  }}export interface PaginationContentProps {  class?: string;  children?: Children;}@Component()export class PaginationContent extends StatelessComponent<PaginationContentProps> {  render() {    const { class: cls, children } = this.props;    return <ul class={cn("flex flex-row items-center gap-1", cls)}>{children}</ul>;  }}@Component()export class PaginationItem extends StatelessComponent<PaginationContentProps> {  render() {    const { class: cls, children } = this.props;    return <li class={cls}>{children}</li>;  }}export interface PaginationLinkProps {  href?: string;  isActive?: boolean;  size?: "icon" | "default";  onClick?: (event: MouseEvent) => void;  class?: string;  "aria-label"?: string;  children?: Children;}@Component()export class PaginationLink extends StatelessComponent<PaginationLinkProps> {  render() {    const { href, isActive, size = "icon", onClick, class: cls, "aria-label": ariaLabel, children } = this.props;    return (      <a        href={href}        aria-current={isActive ? "page" : undefined}        aria-label={ariaLabel}        onClick={onClick}        class={cn(buttonVariants({ variant: isActive ? "outline" : "ghost", size }), cls)}      >        {children}      </a>    );  }}@Component()export class PaginationPrevious extends StatelessComponent<Omit<PaginationLinkProps, "size" | "isActive">> {  render() {    const { href, onClick, class: cls, children } = this.props;    return (      <PaginationLink href={href} onClick={onClick} aria-label="Go to previous page" size="default" class={cn("gap-1 pl-2.5", cls)}>        ‹ {children ?? "Previous"}      </PaginationLink>    );  }}@Component()export class PaginationNext extends StatelessComponent<Omit<PaginationLinkProps, "size" | "isActive">> {  render() {    const { href, onClick, class: cls, children } = this.props;    return (      <PaginationLink href={href} onClick={onClick} aria-label="Go to next page" size="default" class={cn("gap-1 pr-2.5", cls)}>        {children ?? "Next"} ›      </PaginationLink>    );  }}@Component()export class PaginationEllipsis extends StatelessComponent<{ class?: string }> {  render() {    const { class: cls } = this.props;    return (      <span aria-hidden={"true" as const} class={cn("flex size-9 items-center justify-center", cls)}>      </span>    );  }}

Install the following dependencies:

npm install @praxisjs/css
pnpm add @praxisjs/css
yarn add @praxisjs/css
bun add @praxisjs/css

This component builds on other Kosmesis components — install these first (via kosmesis add or this same manual process on their own pages): button.

Copy and paste the following code into your project.

pagination.tsx
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { ButtonStyles } from "./button";class PaginationStyles extends Stylesheet {  $root = this.css({ marginLeft: "auto", marginRight: "auto", display: "flex", width: "100%", justifyContent: "center" });  $content = this.css({ display: "flex", flexDirection: "row", alignItems: "center", gap: "0.25rem" });  $previous = this.css({ gap: "0.25rem", paddingLeft: "0.625rem" });  $next = this.css({ gap: "0.25rem", paddingRight: "0.625rem" });  $ellipsis = this.css({ display: "flex", width: "2.25rem", height: "2.25rem", alignItems: "center", justifyContent: "center" });}export interface PaginationProps {  class?: string;  children?: Children;}/** Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. Composes `Button` for its links. */@Component()export class Pagination extends StatelessComponent<PaginationProps> {  @Styled(PaginationStyles) $s!: PaginationStyles;  render() {    const { class: cls, children } = this.props;    return (      <nav role="navigation" aria-label="pagination" class={cx(this.$s.$root, cls)}>        {children}      </nav>    );  }}export interface PaginationContentProps {  class?: string;  children?: Children;}@Component()export class PaginationContent extends StatelessComponent<PaginationContentProps> {  @Styled(PaginationStyles) $s!: PaginationStyles;  render() {    const { class: cls, children } = this.props;    return <ul class={cx(this.$s.$content, cls)}>{children}</ul>;  }}@Component()export class PaginationItem extends StatelessComponent<PaginationContentProps> {  render() {    const { class: cls, children } = this.props;    return <li class={cls}>{children}</li>;  }}export interface PaginationLinkProps {  href?: string;  isActive?: boolean;  size?: "icon" | "default";  onClick?: (event: MouseEvent) => void;  class?: string;  "aria-label"?: string;  children?: Children;}@Component()export class PaginationLink extends StatelessComponent<PaginationLinkProps> {  @Styled(ButtonStyles) $btn!: ButtonStyles;  render() {    const { href, isActive, size = "icon", onClick, class: cls, "aria-label": ariaLabel, children } = this.props;    const variant = isActive ? this.$btn.$variantOutline : this.$btn.$variantGhost;    const sizeClass = size === "icon" ? this.$btn.$sizeIcon : this.$btn.$sizeDefault;    return (      <a        href={href}        aria-current={isActive ? "page" : undefined}        aria-label={ariaLabel}        onClick={onClick}        class={cx(this.$btn.$root, variant, sizeClass, cls)}      >        {children}      </a>    );  }}@Component()export class PaginationPrevious extends StatelessComponent<Omit<PaginationLinkProps, "size" | "isActive">> {  @Styled(PaginationStyles) $s!: PaginationStyles;  render() {    const { href, onClick, class: cls, children } = this.props;    return (      <PaginationLink href={href} onClick={onClick} aria-label="Go to previous page" size="default" class={cx(this.$s.$previous, cls)}>        ‹ {children ?? "Previous"}      </PaginationLink>    );  }}@Component()export class PaginationNext extends StatelessComponent<Omit<PaginationLinkProps, "size" | "isActive">> {  @Styled(PaginationStyles) $s!: PaginationStyles;  render() {    const { href, onClick, class: cls, children } = this.props;    return (      <PaginationLink href={href} onClick={onClick} aria-label="Go to next page" size="default" class={cx(this.$s.$next, cls)}>        {children ?? "Next"} ›      </PaginationLink>    );  }}@Component()export class PaginationEllipsis extends StatelessComponent<{ class?: string }> {  @Styled(PaginationStyles) $s!: PaginationStyles;  render() {    const { class: cls } = this.props;    return (      <span aria-hidden={"true" as const} class={cx(this.$s.$ellipsis, cls)}>      </span>    );  }}

Examples

Usage

import {
  Pagination,
  PaginationContent,
  PaginationEllipsis,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination";

<Pagination>
  <PaginationContent>
    <PaginationItem><PaginationPrevious href="#" /></PaginationItem>
    <PaginationItem><PaginationLink href="#" isActive>1</PaginationLink></PaginationItem>
    <PaginationItem><PaginationLink href="#">2</PaginationLink></PaginationItem>
    <PaginationItem><PaginationEllipsis /></PaginationItem>
    <PaginationItem><PaginationNext href="#" /></PaginationItem>
  </PaginationContent>
</Pagination>

On this page