Navigation

Breadcrumb

Ruta de navegación jerárquica con separadores

Preview

size
items

Code

import css from "./Breadcrumb.module.css";

import { CaretRightIcon } from "@phosphor-icons/react/dist/csr/CaretRight";

type Size = "sm" | "md";

interface ResponsiveSize {
  base: Size;
  mobile?: Size;
  tablet?: Size;
  desktop?: Size;
  wide?: Size;
}

interface BreadcrumbItem {
  label: string;
  href?: string;
}

interface BreadcrumbProps {
  items: BreadcrumbItem[];
  size: Size | ResponsiveSize;
  "aria-label": string;
}

function getSizeClasses(size: Size | ResponsiveSize): string {
  if (typeof size === "string") {
    return css[size];
  }

  const classes = [css[`${size.base}_base`]];

  if (size.mobile) classes.push(css[`${size.mobile}_mobile`]);
  if (size.tablet) classes.push(css[`${size.tablet}_tablet`]);
  if (size.desktop) classes.push(css[`${size.desktop}_desktop`]);
  if (size.wide) classes.push(css[`${size.wide}_wide`]);

  return classes.join(" ");
}

export default function Breadcrumb({
  items,
  size,
  "aria-label": ariaLabel,
}: BreadcrumbProps) {
  return (
    <nav
      className={`
        ${css.breadcrumb}
        ${getSizeClasses(size)}
      `}
      aria-label={ariaLabel}
    >
      <ol className={css.breadcrumb_list} role="list">
        {items.map((item, index) => {
          const last = index === items.length - 1;
          const Tag = item.href ? "a" : "span";

          return (
            <li key={item.label} className={css.breadcrumb_item}>
              {last ? (
                <span className={css.breadcrumb_current} aria-current="page">
                  {item.label}
                </span>
              ) : (
                <Tag className={css.breadcrumb_link} href={item.href}>
                  {item.label}
                </Tag>
              )}

              {!last && (
                <CaretRightIcon
                  weight="bold"
                  aria-hidden="true"
                  className={css.breadcrumb_separator}
                />
              )}
            </li>
          );
        })}
      </ol>
    </nav>
  );
}

Props

PropTypeRequiredDescription
itemsBreadcrumbItem[]requiredRuta ordenada: { label, href? } por nivel. El último es la página actual (aria-current); un nivel sin href se pinta como texto plano.
size"sm" | "md" | ResponsiveSizerequiredTamaño del texto y el separador. Fijo o responsive.
aria-labelstringrequiredNombre del <nav> para lectores de pantalla; distingue esta navegación de las demás de la página.