Como crear un breadcrumb animado con iconos de progreso en React
Un breadcrumb visual con estado de progreso en React renderiza cada paso como un badge de icono con un estado completed/current/upcoming, los conecta con lineas animadas y escalona su entrada usando Framer Motion. Tres variantes de estado controlan todos los colores a traves de una unica funcion getStatusStyles que lee las CSS custom properties.
- Stack: React 18 + Framer Motion 11 + lucide-react + Tailwind v4, ~207 lineas, cero dependencias runtime adicionales.
- Animacion: entrada escalonada de scale + opacity (0.1s de retraso por item) con un ease spring [0.16, 1, 0.3, 1].
- El paso actual pulsa con una animacion scale infinita sobre el borde del anillo para atraer la atencion.
- Accesible: renderizado como <nav aria-label> + lista <ol>, HTML semantico para los lectores de pantalla.
- Responsive en pantallas pequenas, pero los conjuntos de etiquetas largas pueden desbordarse: recorta o ajusta las etiquetas por debajo de 480px.
Breadcrumb Path Visual es un componente de navegacion React que va mas alla de un simple rastro de enlaces. Cada paso lleva un icono, un estado de progreso y una corta linea de conexion animada, convirtiendo un indicador de ubicacion estatico en un mapa legible del avance del usuario en un flujo multipaso. El stagger de entrada y el anillo pulsante sobre el paso actual le dan un acabado de calidad de producto.
Anatomía
El componente es un <nav> semantico que envuelve una lista <ol>. Cada <li> contiene un ancla con dos piezas visuales: un badge de icono circular de 32px y una etiqueta de texto. Los pasos completados muestran ademas un badge CheckCircle de 12px junto a la etiqueta. Entre los items, una corta linea horizontal animada (20px, entrada en scaleX) y una flecha ChevronRight actuan como separador visual. Todos los colores provienen de CSS custom properties, de modo que el componente se adapta a cualquier preset de tema sin tocar el TSX.
Cómo funciona
Cada item de la lista se monta con un par initial/animate de Framer Motion (opacity 0 -> 1, scale 0.9 -> 1) y un retraso de i * 0.1s para que los items caigan en cascada de izquierda a derecha. La linea de conexion entre los items usa un motion.div separado con initial scaleX:0, animate scaleX:1 y un retraso ligeramente posterior (i * 0.1 + 0.2s) para que la linea se dibuje despues de que aparezca el badge del paso. El anillo pulsante sobre el paso actual es un motion.div posicionado en absoluto que repite scale [1, 1.4, 1] y opacity [0.4, 0, 0.4] en un ciclo de 2 segundos, creando una pulsacion tipo sonar sin temporizador JavaScript. La funcion getStatusStyles actua como la unica fuente de verdad para los tres estados, devolviendo literales de objeto que mapean a valores de CSS custom properties en lugar de colores hardcodeados.
Cómo crearlo en React
Definir la forma de los datos del paso y el mapa de iconos
Crea una interface PathStep con label, href, icon (clave string) y un tipo union status. Construye un objeto iconMap que mapee claves string a componentes de iconos Lucide. Asi los datos mock siguen siendo JSON simple mientras el componente resuelve los iconos en runtime.
interface PathStep { label: string; href?: string; icon?: string; status: "completed" | "current" | "upcoming"; } const iconMap: Record<string, React.ElementType> = { Home, Package, Settings, CheckCircle, };Asociar cada estado a tokens de CSS custom properties
Escribe una funcion getStatusStyles que devuelva un objeto de valores de estilo inline por estado. Los pasos completados usan un fondo accent tintado, el paso actual usa el accent solido, y los pasos proximos usan una mezcla de borde atenuado. Leer desde las variables CSS significa que la misma funcion funciona en todos los presets de tema.
function getStatusStyles(status: PathStep["status"]) { switch (status) { case "completed": return { iconBg: "color-mix(in srgb, var(--color-accent) 15%, transparent)", iconColor: "var(--color-accent)", }; case "current": return { iconBg: "var(--color-accent)", iconColor: "var(--color-background)", }; case "upcoming": return { iconBg: "color-mix(in srgb, var(--color-border) 50%, transparent)", iconColor: "var(--color-foreground-light)", }; } }Escalonar la entrada con Framer Motion
Envuelve cada item de la lista en un motion.li con opacity/scale inicial y un retraso basado en el indice. Usa el ease custom [0.16, 1, 0.3, 1] para una sensacion spring sin rebote. Para la linea de conexion, anade un segundo motion.div con una entrada scaleX y un retraso algo mas largo para que se dibuje despues del badge del paso precedente.
<motion.li initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} transition={{ delay: i * 0.1, duration: 0.4, ease: [0.16, 1, 0.3, 1] }} > {/* step content */} {!isLast && ( <motion.div initial={{ scaleX: 0 }} animate={{ scaleX: 1 }} transition={{ delay: i * 0.1 + 0.2, duration: 0.3 }} style={{ width: "20px", height: "2px", transformOrigin: "left" }} /> )} </motion.li>Anadir el anillo pulsante para el paso actual
Dentro de la div del badge de icono, renderiza condicionalmente un motion.div posicionado en absoluto cuando el estado es 'current'. Anima scale de 1 a 1.4 y opacity de 0.4 a 0 con una repeticion infinita. Una duracion de 2 segundos mantiene la pulsacion tranquila en lugar de frenetica.
{step.status === "current" && ( <motion.div animate={{ scale: [1, 1.4, 1], opacity: [0.4, 0, 0.4] }} transition={{ duration: 2, repeat: Infinity }} style={{ position: "absolute", inset: "-3px", borderRadius: "50%", border: "2px solid var(--color-accent)", }} /> )}
Cuándo usarlo
Usa este componente en flujos de checkout, wizards de onboarding o formularios multipaso donde mostrar el contexto de progreso reduce la ansiedad del usuario. Funciona especialmente bien en dashboards SaaS y embudos de e-commerce. Evitalo en navegaciones planas de una sola pagina, donde un breadcrumb estandar con enlaces de texto es mas ligero y menos distractor. En movil con muchos pasos, considera plegar los pasos completados a un solo icono para evitar el desbordamiento horizontal.
Usado por
- Stripe, Usa indicadores de pasos visuales con badges de iconos y estados de completado a lo largo de sus flujos de pago y onboarding.
- Linear, Emplea una navegacion consciente del progreso en sus wizards de configuracion de proyecto, con diferenciacion visual entre pasos completados y activos.
- Shopify, El flujo de checkout usa breadcrumbs de pasos guiados por iconos para acompanar a comerciantes y compradores en procesos de varias etapas.
FAQ
Como anado mas iconos al mapa de iconos ?
Importa cualquier icono Lucide al inicio del archivo y anadelo al objeto iconMap con una clave string. Luego pasa esa clave como prop icon en tu array de steps. No se necesita ningun otro cambio.
Puedo usarlo como stepper en lugar de breadcrumb ?
El componente ya se comporta visualmente como un stepper. Para una semantica de stepper pura, reemplaza los elementos <a> por elementos <button> y controla el paso activo con estado local en lugar de la navegacion por href.
La animacion se reproduce de nuevo cuando cambia la prop steps ?
Por defecto Framer Motion solo reproduce la entrada una vez por montaje. Para reproducirla en cada cambio de datos, anade una prop key basada en el array steps al elemento <ol> para que React lo vuelva a montar en cada actualizacion.
Hay riesgo de mismatch de hidratacion con SSR ?
El componente usa la directiva 'use client' y no contiene valores aleatorios ni basados en la fecha, por lo que los renders de servidor y cliente producen un markup identico. No se necesita ninguna proteccion SSR especial.