Breadcrumbs
Flexible breadcrumb components for displaying navigation hierarchy. Supports various styles, dividers, and customization options. previews<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span class="size-5 inline-block w-3.5 h-3.5 mr-1">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
d="M11.47 3.841a.75.75 0 0 1 1.06 0l8.69 8.69a.75.75 0 1 0 1.06-1.061l-8.689-8.69a2.25 2.25 0 0 0-3.182 0l-8.69 8.69a.75.75 0 1 0 1.061 1.06l8.69-8.689Z"></path>
<path
d="m12 5.432 8.159 8.159c.03.03.06.058.091.086v6.198c0 1.035-.84 1.875-1.875 1.875H15a.75.75 0 0 1-.75-.75v-4.5a.75.75 0 0 0-.75-.75h-3a.75.75 0 0 0-.75.75V21a.75.75 0 0 1-.75.75H5.625a1.875 1.875 0 0 1-1.875-1.875v-6.198a2.29 2.29 0 0 0 .091-.086L12 5.432Z"></path>
</svg>
</span>
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span class="size-5 inline-block w-3.5 h-3.5 mr-1">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M7.5 6v.75H5.513c-.96 0-1.764.724-1.865 1.679l-1.263 12A1.875 1.875 0 0 0 4.25 22.5h15.5a1.875 1.875 0 0 0 1.865-2.071l-1.263-12a1.875 1.875 0 0 0-1.865-1.679H16.5V6a4.5 4.5 0 1 0-9 0ZM12 3a3 3 0 0 0-3 3v.75h6V6a3 3 0 0 0-3-3Zm-3 8.25a3 3 0 1 0 6 0v-.75a.75.75 0 0 1 1.5 0v.75a4.5 4.5 0 1 1-9 0v-.75a.75.75 0 0 1 1.5 0v.75Z"
clip-rule="evenodd"></path>
</svg>
</span>
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span class="size-5 inline-block w-3.5 h-3.5 mr-1">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm11.378-3.917c-.89-.777-2.366-.777-3.255 0a.75.75 0 0 1-.988-1.129c1.454-1.272 3.776-1.272 5.23 0 1.513 1.324 1.513 3.518 0 4.842a3.75 3.75 0 0 1-.837.552c-.676.328-1.028.774-1.028 1.152v.75a.75.75 0 0 1-1.5 0v-.75c0-1.279 1.06-2.107 1.875-2.502.182-.088.351-.199.503-.331.83-.727.83-1.857 0-2.584ZM12 18a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
clip-rule="evenodd"></path>
</svg>
<span class="sr-only">Unknown icon</span>
</span>
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/", icon: "HomeIcon" },
{ label: "Products", href: "/products", icon: "ShoppingBagIcon" },
{ label: "Laptops", icon: "DeviceLaptopIcon" },
]}
showIcons
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
<div class="grid grid-cols-1 gap-6">
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">></li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">></li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">•</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">•</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">→</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">→</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">|</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">|</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
</div>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
divider="slash"
/>
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
divider="chevron"
/>
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
divider="bullet"
/>
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
divider="arrow"
/>
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
divider="custom"
customDivider="|"
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<span
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span class="size-5 inline-block w-5 h-5">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
d="M11.47 3.841a.75.75 0 0 1 1.06 0l8.69 8.69a.75.75 0 1 0 1.06-1.061l-8.689-8.69a2.25 2.25 0 0 0-3.182 0l-8.69 8.69a.75.75 0 1 0 1.061 1.06l8.69-8.689Z"></path>
<path
d="m12 5.432 8.159 8.159c.03.03.06.058.091.086v6.198c0 1.035-.84 1.875-1.875 1.875H15a.75.75 0 0 1-.75-.75v-4.5a.75.75 0 0 0-.75-.75h-3a.75.75 0 0 0-.75.75V21a.75.75 0 0 1-.75.75H5.625a1.875 1.875 0 0 1-1.875-1.875v-6.198a2.29 2.29 0 0 0 .091-.086L12 5.432Z"></path>
</svg>
</span>
</span>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span class="size-5 inline-block w-5 h-5">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M7.5 6v.75H5.513c-.96 0-1.764.724-1.865 1.679l-1.263 12A1.875 1.875 0 0 0 4.25 22.5h15.5a1.875 1.875 0 0 0 1.865-2.071l-1.263-12a1.875 1.875 0 0 0-1.865-1.679H16.5V6a4.5 4.5 0 1 0-9 0ZM12 3a3 3 0 0 0-3 3v.75h6V6a3 3 0 0 0-3-3Zm-3 8.25a3 3 0 1 0 6 0v-.75a.75.75 0 0 1 1.5 0v.75a4.5 4.5 0 1 1-9 0v-.75a.75.75 0 0 1 1.5 0v.75Z"
clip-rule="evenodd"></path>
</svg>
</span>
</span>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span class="size-5 inline-block w-5 h-5">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
data-slot="icon">
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm11.378-3.917c-.89-.777-2.366-.777-3.255 0a.75.75 0 0 1-.988-1.129c1.454-1.272 3.776-1.272 5.23 0 1.513 1.324 1.513 3.518 0 4.842a3.75 3.75 0 0 1-.837.552c-.676.328-1.028.774-1.028 1.152v.75a.75.75 0 0 1-1.5 0v-.75c0-1.279 1.06-2.107 1.875-2.502.182-.088.351-.199.503-.331.83-.727.83-1.857 0-2.584ZM12 18a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
clip-rule="evenodd"></path>
</svg>
<span class="sr-only">Unknown icon</span>
</span>
</span>
</li>
</ol>
</nav>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ icon: "HomeIcon", href: "/" },
{ icon: "ShoppingBagIcon", href: "/products" },
{ icon: "DeviceLaptopIcon" },
]}
showIcons
iconOnly
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
Breadcrumb with Background
A breadcrumb component with a background and different shapes.
<div class="grid grid-cols-1 gap-6">
<nav
aria-label="Breadcrumb"
class="flex items-center bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-100 border-slate-200 dark:border-slate-600 py-2 px-4 rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
<nav
aria-label="Breadcrumb"
class="flex items-center bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-100 border-slate-200 dark:border-slate-600 py-2 px-4 w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
<nav
aria-label="Breadcrumb"
class="flex items-center bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-100 border-slate-200 dark:border-slate-600 py-2 px-4 rounded-full w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
</div>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
background
backgroundShape="rounded"
/>
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
background
backgroundShape="square"
/>
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
background
backgroundShape="pill"
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
<div class="space-y-2 grid grid-cols-1 gap-4">
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-blue-500 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-blue-500 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-gray-500 dark:text-gray-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-slate-500 dark:text-slate-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
</div>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
color="blue"
activeColor="indigo"
/>
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
color="gray"
activeColor="emerald"
/>
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
color="slate"
activeColor="rose"
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products/electronics"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Electronics</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>...</span>
</span>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Gaming Laptops</span>
</span>
</li>
</ol>
</nav>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Electronics", href: "/products/electronics" },
{ label: "Computers", href: "/products/electronics/computers" },
{ label: "Laptops", href: "/products/electronics/computers/laptops" },
{ label: "Gaming Laptops" },
]}
truncate
maxItems={4}
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline px-2 py-1 rounded-full text-sm bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-100 border-gray-200 dark:border-gray-600">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline px-2 py-1 rounded-full text-sm bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-100 border-gray-200 dark:border-gray-600">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold px-2 py-1 rounded-full text-sm bg-blue-100 dark:bg-blue-800 hover:bg-blue-200 dark:hover:bg-blue-700 text-blue-700 dark:text-blue-100 border-blue-200 dark:border-blue-600"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
badgeStyle
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
Breadcrumb with Status Indicators
A breadcrumb component with status indicators for items.
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
<span
class="inline-flex items-center font-medium bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-100 border-gray-200 dark:border-gray-600 rounded px-2 py-1 text-xs ml-2">
New
</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
<span
class="inline-flex items-center font-medium bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-100 border-blue-200 dark:border-blue-600 rounded px-2 py-1 text-xs ml-2">
Sale
</span>
</span>
</li>
</ol>
</nav>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products", status: "New" },
{ label: "Laptops", status: "Sale" },
]}
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
<div class="space-y-4">
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-xs text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
<nav aria-label="Breadcrumb" class="flex items-center rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-base text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-base text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-base text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
</div>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
size="sm"
/>
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
size="md"
/>
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
size="lg"
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
Breadcrumb with Custom Background Color
A breadcrumb component with a custom background color.
<nav
aria-label="Breadcrumb"
class="flex items-center bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-100 border-blue-200 dark:border-blue-600 py-2 px-4 rounded-md w-fit">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
background
backgroundColor="blue"
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
<nav
aria-label="Breadcrumb"
class="flex items-center bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-100 border-slate-200 dark:border-slate-600 py-2 px-4 rounded-md w-full">
<ol class="flex flex-wrap items-center gap-2">
<li class="flex items-center">
<a
href="/"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Home</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<a
href="/products"
class="flex items-center flex items-center font-medium hover:underline text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<span>Products</span>
</a>
</li>
<li class="text-gray-400">/</li>
<li class="flex items-center">
<span
class="flex items-center flex items-center font-semibold text-sm text-blue-500 dark:text-blue-400"
aria-current="page">
<span>Laptops</span>
</span>
</li>
</ol>
</nav>
---
import { Breadcrumb } from '@/components/ui/breadcrumb';
---
<Breadcrumb
items={[
{ label: "Home", href: "/" },
{ label: "Products", href: "/products" },
{ label: "Laptops" },
]}
width="full"
background
/> ---
// src/components/elements/Breadcrumb.astro
// Imports
import { type HTMLAttributes } from "astro/types";
import { tv, type VariantProps } from "@utils/custom-tv";
import Icon from "./Icon.astro";
import Badge from "./Badge.astro";
import {
getDefaultClasses,
getSoftClasses,
type ColorName,
colorPalette,
getHardTextClass,
getDefaultTextClass,
getSoftTextClass,
getOutlinedTextClass,
} from "@utils/colorUtils";
import { twMerge } from "tailwind-merge";
import { escapeHTML } from "astro/runtime/server/escape.js";
// PropTypes for documentation
export const propTypes = {
items: {
type: "Array<{ label: string; href?: string; icon?: string; status?: string }>",
description: "Array of breadcrumb items",
},
divider: {
type: ["slash", "chevron", "bullet", "arrow", "custom"],
description: "Type of divider between breadcrumb items",
default: "slash",
},
customDivider: {
type: "string",
description: "Custom divider content (used when divider is 'custom')",
},
showIcons: {
type: "boolean",
description: "Whether to show icons for breadcrumb items",
default: false,
},
iconOnly: {
type: "boolean",
description: "Whether to show only icons (no text)",
default: false,
},
background: {
type: "boolean",
description: "Whether to show a background",
default: false,
},
backgroundShape: {
type: ["rounded", "square", "pill"],
description: "Shape of the background",
default: "rounded",
},
color: {
type: Object.keys(colorPalette),
description: "Color scheme for the breadcrumb",
default: "gray",
},
activeColor: {
type: Object.keys(colorPalette),
description: "Color for the active breadcrumb item",
default: "blue",
},
truncate: {
type: "boolean",
description: "Whether to truncate middle items",
default: false,
},
maxItems: {
type: "number",
description: "Maximum number of items to display when truncating",
default: 3,
},
badgeStyle: {
type: "boolean",
description: "Whether to use badge style for items",
default: false,
},
class: {
type: "string",
description: "Additional CSS classes to apply to the breadcrumb",
},
spacing: {
type: ["none", "small", "medium", "large"],
description: "Spacing between breadcrumb items",
default: "medium",
},
size: {
type: ["sm", "md", "lg"],
description: "Size of the breadcrumb items",
default: "md",
},
width: {
type: ["full", "fit"],
description: "Width of the breadcrumb",
default: "fit",
},
backgroundColor: {
type: Object.keys(colorPalette),
description: "Background color for the breadcrumb",
},
};
// Types and Interfaces
type BreadcrumbVariants = VariantProps<typeof breadcrumbStyles>;
interface BreadcrumbItem {
label: string;
href?: string;
icon?: string;
status?: string;
}
interface Props extends HTMLAttributes<"nav">, BreadcrumbVariants {
items: BreadcrumbItem[];
divider?: "slash" | "chevron" | "bullet" | "arrow" | "custom";
customDivider?: string;
showIcons?: boolean;
iconOnly?: boolean;
background?: boolean;
backgroundShape?: "rounded" | "square" | "pill";
color?: ColorName;
activeColor?: ColorName;
truncate?: boolean;
maxItems?: number;
badgeStyle?: boolean;
class?: string;
spacing?: "none" | "small" | "medium" | "large";
size?: "sm" | "md" | "lg";
width?: "full" | "fit";
backgroundColor?: ColorName;
}
// Component Logic
const {
items,
divider = "slash",
customDivider,
showIcons = false,
iconOnly = false,
background = false,
backgroundShape = "rounded",
color = "gray",
activeColor = "blue",
truncate = false,
maxItems = 3,
badgeStyle = false,
class: className = "",
spacing = "medium",
size = "md",
width = "fit",
backgroundColor = "slate",
} = Astro.props as Props;
// Styles
const breadcrumbStyles = tv({
base: "flex items-center",
variants: {
background: {
true: `${getSoftClasses(backgroundColor, { includeHover: false })} py-2 px-4`,
false: "",
},
backgroundShape: {
rounded: "rounded-md",
square: "",
pill: "rounded-full",
},
width: {
full: "w-full",
fit: "w-fit",
},
},
defaultVariants: {
background: false,
backgroundShape: "rounded",
width: "fit",
},
});
const listStyles = tv({
base: "flex flex-wrap items-center",
variants: {
spacing: {
none: "gap-0",
small: "gap-1",
medium: "gap-2",
large: "gap-3",
},
},
defaultVariants: {
spacing: "medium",
},
});
const itemStyles = tv({
base: `flex items-center font-medium `,
variants: {
active: {
true: "font-semibold",
false: "hover:underline",
},
badgeStyle: {
true: "px-2 py-1 rounded-full",
false: "",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
compoundVariants: [
{
active: false,
badgeStyle: false,
class: `${getOutlinedTextClass(color, { includeHover: true })}`,
},
{
active: true,
badgeStyle: false,
class: `${getOutlinedTextClass(activeColor, { includeHover: false })}`,
},
{
active: true,
badgeStyle: true,
class: getSoftClasses(activeColor, { includeHover: true }),
},
{
active: false,
badgeStyle: true,
class: getSoftClasses(color, { includeHover: true }),
},
],
});
const getDividerContent = (dividerType: Props["divider"]) => {
switch (dividerType) {
case "slash":
return "/";
case "chevron":
return `>`;
case "bullet":
return "•";
case "arrow":
return `→`;
case "custom":
return customDivider ? escapeHTML(customDivider) : "/";
default:
return "/";
}
};
const visibleItems = truncate
? [
items[0],
...items.slice(1, -1).slice(0, maxItems - 2),
...(items.length > maxItems ? [{ label: "..." }] : []),
items[items.length - 1],
]
: items;
---
<nav
aria-label="Breadcrumb"
class={twMerge(
breadcrumbStyles({
background,
backgroundShape,
width,
backgroundColor: backgroundColor as ColorName,
}),
className,
)}
>
<ol class={listStyles({ spacing })}>
{
visibleItems.map((item, index) => (
<>
<li class="flex items-center">
{item.href && !iconOnly ? (
<a
href={item.href}
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</a>
) : (
<span
class={`flex items-center ${itemStyles({
active: index === items.length - 1,
badgeStyle,
size,
})}`}
aria-current={index === items.length - 1 ? "page" : undefined}
>
{showIcons && item.icon && (
<Icon
name={item.icon}
class={`inline-block ${iconOnly ? "w-5 h-5" : "w-3.5 h-3.5 mr-1"}`}
/>
)}
{!iconOnly && <span>{item.label}</span>}
{item.status && (
<Badge
content={item.status}
variant="soft"
color={index === items.length - 1 ? activeColor : color}
size="sm"
class="ml-2"
/>
)}
</span>
)}
</li>
{index < visibleItems.length - 1 && (
<li class="text-gray-400" set:html={getDividerContent(divider)} />
)}
</>
))
}
</ol>
</nav>
Component Properties
| Property | Type | Default | Description |
|---|---|---|---|
| items | Array<{ label: string; href?: string; icon?: string; status?: string }> | - | Array of breadcrumb items |
| divider | slash | chevron | bullet | arrow | custom | "slash" | Type of divider between breadcrumb items |
| customDivider | string | - | Custom divider content (used when divider is 'custom') |
| showIcons | boolean | false | Whether to show icons for breadcrumb items |
| iconOnly | boolean | false | Whether to show only icons (no text) |
| background | boolean | false | Whether to show a background |
| backgroundShape | rounded | square | pill | "rounded" | Shape of the background |
| color | white | slate | gray | zinc | neutral | stone | red | orange | amber | yellow | lime | green | emerald | teal | cyan | sky | blue | indigo | violet | purple | fuchsia | pink | rose | "gray" | Color scheme for the breadcrumb |
| activeColor | white | slate | gray | zinc | neutral | stone | red | orange | amber | yellow | lime | green | emerald | teal | cyan | sky | blue | indigo | violet | purple | fuchsia | pink | rose | "blue" | Color for the active breadcrumb item |
| truncate | boolean | false | Whether to truncate middle items |
| maxItems | number | 3 | Maximum number of items to display when truncating |
| badgeStyle | boolean | false | Whether to use badge style for items |
| class | string | - | Additional CSS classes to apply to the breadcrumb |
| spacing | none | small | medium | large | "medium" | Spacing between breadcrumb items |
| size | sm | md | lg | "md" | Size of the breadcrumb items |
| width | full | fit | "fit" | Width of the breadcrumb |
| backgroundColor | white | slate | gray | zinc | neutral | stone | red | orange | amber | yellow | lime | green | emerald | teal | cyan | sky | blue | indigo | violet | purple | fuchsia | pink | rose | - | Background color for the breadcrumb |