Components
Breadcrumb
A breadcrumb trail with separators and an ellipsis. Purely presentational.
Installation
npx kosmesis add breadcrumbpnpm dlx kosmesis add breadcrumbyarn dlx kosmesis add breadcrumbbunx kosmesis add breadcrumbInstall the following dependencies:
npm install @morphos/iconspnpm add @morphos/iconsyarn add @morphos/iconsbun add @morphos/iconsCopy 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 { Icon } from "@morphos/icons";import { cn } from "@/lib/utils";export interface BreadcrumbProps { class?: string; "aria-label"?: string; children?: Children;}@Component()export class Breadcrumb extends StatelessComponent<BreadcrumbProps> { render() { const { class: cls, "aria-label": ariaLabel = "breadcrumb", children } = this.props; return ( <nav aria-label={ariaLabel} class={cls}> {children} </nav> ); }}export interface BreadcrumbListProps { class?: string; children?: Children;}@Component()export class BreadcrumbList extends StatelessComponent<BreadcrumbListProps> { render() { const { class: cls, children } = this.props; return ( <ol class={cn( "flex flex-wrap items-center gap-1.5 text-sm break-words text-muted-foreground sm:gap-2.5", cls, )} > {children} </ol> ); }}@Component()export class BreadcrumbItem extends StatelessComponent<BreadcrumbListProps> { render() { const { class: cls, children } = this.props; return <li class={cn("inline-flex items-center gap-1.5", cls)}>{children}</li>; }}export interface BreadcrumbLinkProps { href?: string; onClick?: (event: MouseEvent) => void; class?: string; children?: Children;}@Component()export class BreadcrumbLink extends StatelessComponent<BreadcrumbLinkProps> { render() { const { href, onClick, class: cls, children } = this.props; return ( <a href={href} onClick={onClick} class={cn("transition-colors hover:text-foreground", cls)}> {children} </a> ); }}@Component()export class BreadcrumbPage extends StatelessComponent<BreadcrumbListProps> { render() { const { class: cls, children } = this.props; return ( <span role="link" aria-disabled={"true" as const} aria-current="page" class={cn("font-normal text-foreground", cls)}> {children} </span> ); }}@Component()export class BreadcrumbSeparator extends StatelessComponent<BreadcrumbListProps> { render() { const { class: cls, children } = this.props; return ( <li role="presentation" aria-hidden={"true" as const} class={cn("[&>svg]:size-3.5", cls)}> {children ?? "/"} </li> ); }}@Component()export class BreadcrumbEllipsis extends StatelessComponent<{ class?: string }> { render() { const { class: cls } = this.props; return ( <span role="presentation" aria-hidden={"true" as const} class={cn("flex size-9 items-center justify-center", cls)}> <Icon name="Ellipsis" size={16} /> <span class="sr-only">More</span> </span> ); }}Install the following dependencies:
npm install @praxisjs/css @morphos/iconspnpm add @praxisjs/css @morphos/iconsyarn add @praxisjs/css @morphos/iconsbun add @praxisjs/css @morphos/iconsCopy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Icon } from "@morphos/icons";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class BreadcrumbStyles extends Stylesheet { $list = this.css({ display: "flex", flexWrap: "wrap", alignItems: "center", gap: "0.375rem", fontSize: "0.875rem", wordBreak: "break-word", color: t.mutedForeground, }).media("(min-width: 640px)", { gap: "0.625rem" }); $item = this.css({ display: "inline-flex", alignItems: "center", gap: "0.375rem" }); $link = this.css({ transition: "color 120ms ease" }).hover({ color: t.foreground }); $page = this.css({ fontWeight: 400, color: t.foreground }); $separator = this.css({}).on("& > svg", { width: "0.875rem", height: "0.875rem" }); $ellipsis = this.css({ display: "flex", width: "2.25rem", height: "2.25rem", alignItems: "center", justifyContent: "center" }); $srOnly = this.css({ position: "absolute", width: "1px", height: "1px", padding: "0", margin: "-1px", overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", border: "0", });}export interface BreadcrumbProps { class?: string; "aria-label"?: string; children?: Children;}@Component()export class Breadcrumb extends StatelessComponent<BreadcrumbProps> { render() { const { class: cls, "aria-label": ariaLabel = "breadcrumb", children } = this.props; return ( <nav aria-label={ariaLabel} class={cls}> {children} </nav> ); }}export interface BreadcrumbListProps { class?: string; children?: Children;}@Component()export class BreadcrumbList extends StatelessComponent<BreadcrumbListProps> { @Styled(BreadcrumbStyles) $s!: BreadcrumbStyles; render() { const { class: cls, children } = this.props; return <ol class={cx(this.$s.$list, cls)}>{children}</ol>; }}@Component()export class BreadcrumbItem extends StatelessComponent<BreadcrumbListProps> { @Styled(BreadcrumbStyles) $s!: BreadcrumbStyles; render() { const { class: cls, children } = this.props; return <li class={cx(this.$s.$item, cls)}>{children}</li>; }}export interface BreadcrumbLinkProps { href?: string; onClick?: (event: MouseEvent) => void; class?: string; children?: Children;}@Component()export class BreadcrumbLink extends StatelessComponent<BreadcrumbLinkProps> { @Styled(BreadcrumbStyles) $s!: BreadcrumbStyles; render() { const { href, onClick, class: cls, children } = this.props; return ( <a href={href} onClick={onClick} class={cx(this.$s.$link, cls)}> {children} </a> ); }}@Component()export class BreadcrumbPage extends StatelessComponent<BreadcrumbListProps> { @Styled(BreadcrumbStyles) $s!: BreadcrumbStyles; render() { const { class: cls, children } = this.props; return ( <span role="link" aria-disabled={"true" as const} aria-current="page" class={cx(this.$s.$page, cls)}> {children} </span> ); }}@Component()export class BreadcrumbSeparator extends StatelessComponent<BreadcrumbListProps> { @Styled(BreadcrumbStyles) $s!: BreadcrumbStyles; render() { const { class: cls, children } = this.props; return ( <li role="presentation" aria-hidden={"true" as const} class={cx(this.$s.$separator, cls)}> {children ?? "/"} </li> ); }}@Component()export class BreadcrumbEllipsis extends StatelessComponent<{ class?: string }> { @Styled(BreadcrumbStyles) $s!: BreadcrumbStyles; render() { const { class: cls } = this.props; return ( <span role="presentation" aria-hidden={"true" as const} class={cx(this.$s.$ellipsis, cls)}> <Icon name="Ellipsis" size={16} /> <span class={this.$s.$srOnly}>More</span> </span> ); }}Examples
Usage
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/components">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>Props
Breadcrumb
| Prop | Type | Default |
|---|---|---|
aria-label | string | "breadcrumb" |
BreadcrumbLink
| Prop | Type | Default |
|---|---|---|
href | string | — |
onClick | (event: MouseEvent) => void | — |
BreadcrumbSeparator
| Prop | Type | Default |
|---|---|---|
children | Children | "/" — overrides the default separator glyph |
BreadcrumbList, BreadcrumbItem, BreadcrumbPage, and BreadcrumbEllipsis take no props
beyond class/children.