Breadcrumb que se colapsa
Las migas se quedan en icono y despliegan su nombre con un muelle al pasar o enfocar.
Ajustes
Código
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Box, Folder, House, Layers, Palette } from "lucide-react";
import type { LucideIcon } from "lucide-react";
const CRUMB_PATH: { label: string; icon: LucideIcon }[] = [
{ label: "Inicio", icon: House },
{ label: "Proyectos", icon: Folder },
{ label: "Aimran", icon: Box },
{ label: "Diseño", icon: Palette },
{ label: "Navegación", icon: Layers },
];
const CRUMB_SPRING = { type: "spring", stiffness: 460, damping: 36 } as const;
interface CollapsingBreadcrumbProps {
visible?: number;
accent?: string;
}
export default function CollapsingBreadcrumb({
visible = 0,
accent = "#0070f3",
}: CollapsingBreadcrumbProps) {
const [depth, setDepth] = useState(CRUMB_PATH.length - 1);
const [peek, setPeek] = useState<number | null>(null);
return (
<nav aria-label="Ruta" className="max-w-full">
<motion.ol
layout
transition={CRUMB_SPRING}
onPointerLeave={() => setPeek(null)}
className="flex h-12 max-w-full items-center rounded-full bg-white px-1.5 text-[13px] text-[#1d1d1f] shadow-[0_1px_2px_rgba(0,0,0,0.04),0_10px_32px_rgba(0,0,0,0.07)] ring-1 ring-black/[0.06] dark:bg-[#1c1c1e] dark:text-[#f5f5f7] dark:shadow-[0_10px_32px_rgba(0,0,0,0.4)] dark:ring-white/10"
>
{CRUMB_PATH.map((crumb, i) => {
const Icon = crumb.icon;
const current = i === depth;
const ahead = i > depth;
const named = current || (i < depth && i >= depth - visible) || peek === i;
return (
<motion.li
key={crumb.label}
layout
transition={CRUMB_SPRING}
className="flex min-w-0 items-center"
>
{i > 0 && (
<span
aria-hidden
className="select-none px-0.5 text-[16px] font-light text-black/15 dark:text-white/20"
>
/
</span>
)}
<button
type="button"
onClick={() => setDepth(i)}
onPointerEnter={() => setPeek(i)}
onFocus={() => setPeek(i)}
onBlur={() => setPeek(null)}
aria-current={current ? "page" : undefined}
aria-label={ahead ? "Volver a " + crumb.label : crumb.label}
className={
"group flex h-9 min-w-9 items-center justify-center rounded-full px-2 outline-none transition-[background-color,color,opacity] duration-200 focus-visible:ring-2 active:scale-[0.96] " +
(current
? "font-medium"
: "text-[#86868b] hover:bg-black/[0.04] hover:text-[#1d1d1f] dark:hover:bg-white/[0.07] dark:hover:text-[#f5f5f7]") +
(ahead ? " opacity-40 hover:opacity-100 focus-visible:opacity-100" : "")
}
style={{ ["--tw-ring-color" as string]: accent + "66" }}
>
<Icon
className="h-4 w-4 shrink-0"
strokeWidth={1.5}
style={{ color: current ? accent : undefined }}
/>
<AnimatePresence initial={false}>
{named && (
<motion.span
key="label"
initial={{ width: 0, opacity: 0, filter: "blur(3px)" }}
animate={{ width: "auto", opacity: 1, filter: "blur(0px)" }}
exit={{ width: 0, opacity: 0, filter: "blur(3px)" }}
transition={CRUMB_SPRING}
className="block overflow-hidden whitespace-nowrap"
>
<span className="block max-w-[8.5rem] truncate pl-1.5">{crumb.label}</span>
</motion.span>
)}
</AnimatePresence>
</button>
</motion.li>
);
})}
</motion.ol>
</nav>
);
}
Prompt para IA
Pégalo en ChatGPT, Claude o Cursor y adaptará el bloque a tu proyecto con estos ajustes, aunque no uses React.
Más en Navegación
Dónde usarlo
Las rutas largas se comen el espacio: un gestor de archivos, un centro de ayuda con secciones dentro de secciones, una categoría de tienda como Inicio / Mujer / Calzado / Zapatillas. Estas migas de pan en React dejan con nombre solo la página actual y el resto en icono. Al subir, los niveles que dejas atrás quedan en fantasma para volver.
En pantallas táctiles no hay hover, así que los nombres solo aparecen al tocar. Si tus iconos no se entienden solos, sube «Padres con nombre» a uno o dos. Y usa iconos que la gente ya conoce: una casa para Inicio, una carpeta para proyectos.