Components
Pagination
Page navigation links, composing Button's variants. Purely presentational.
Installation
npx kosmesis add paginationpnpm dlx kosmesis add paginationyarn dlx kosmesis add paginationbunx kosmesis add paginationThis 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.
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/csspnpm add @praxisjs/cssyarn add @praxisjs/cssbun add @praxisjs/cssThis 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.
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>