Breadcrumbs

Flexible breadcrumb components for displaying navigation hierarchy. Supports various styles, dividers, and customization options. previews

Default Breadcrumb

A basic breadcrumb component with default settings.

 
<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>

Icon-Only Breadcrumb

A breadcrumb component showing only icons.

 
<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>

Truncated Breadcrumb

A breadcrumb component with truncated middle 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>
      </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>

Badge-Style Breadcrumb

A breadcrumb component with badge-style 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 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>

Full-width Breadcrumb

A full-width breadcrumb component.

 
<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