NavPill

Navegación flotante en forma de píldora, único elemento de navegación global

stable v3.2.0

Preview, desktop

Componente real en iframe (es position:fixed; montarlo suelto taparía esta página). Arriba a la izquierda, indicador deslizante en hover/focus, smart-hide al hacer scroll dentro del recuadro.

Preview, mobile

A <768px: abajo, centrada, siempre visible, ancho intrínseco.

Props

PropTipoNota
logostringRequerida. Ruta al símbolo; <img> en caja fija 30×30 (evita reflow). El asset llega listo: debe ser legible sobre --nav-bg tal cual; el componente no lo re-colorea.
items{ key, href, label, icon?, isActive }[]Mín 2; máx: los que quepan a 375px con labels visibles. icon es SVG inline opcional.
locale'es' | 'en'Default 'es'. Labels de accesibilidad.
homeUrlstringDefault '/'. Destino del símbolo.

Tokens

PropiedadValorToken
Background#000000, ambos modos de color (excepción deliberada: máximo contraste, invariante al tema)--nav-bg
Texto#FFFFFF--nav-text
Border-radius16px, deriva de la escala: var(--radius-lg)--nav-radius
Altura52px--nav-height
Sombra0 8px 24px -4px rgba(0,0,0,0.15)--nav-shadow
TipografíaGeorama 14px / 600--text-nav
Item padding8px 12px (desktop) / 6px 8px (móvil)--nav-item-padding (desktop)
Gap entre itemsspace-sm (desktop) / space-xs (móvil)n/a
Item radius8px, deriva de la escala: var(--radius-md)--nav-item-radius
Fondo activo (indicador en reposo)rgba(255,255,255,0.12)--nav-active-bg
Fondo hover (indicador)rgba(255,255,255,0.2)--nav-hover-bg

Estados, indicador deslizante

Un único elemento (.nav-pill__indicator) hace de highlight: los items no tienen fondo propio. Sigue al item con hover o focus y descansa sobre el item activo (aria-current="page"). Sin item activo, se oculta. Todos los items siempre a opacidad completa.

EstadoTratamiento
InactivoTexto blanco, sin fondo. Opacidad completa.
Hover / focusEl indicador se desliza bajo el item apuntado.
Activo (reposo)El indicador descansa bajo el item con aria-current.
Sin item activoIndicador oculto (opacity 0).

El indicador se posiciona con getBoundingClientRect; salta sin transición en el primer paint y bajo prefers-reduced-motion, y se recalcula en document.fonts.ready y en resize. Al salir puntero y foco del nav, vuelve al item activo.

Posición

ViewportPosiciónComportamiento
Desktop (≥768px)Top, izquierdaSmart scroll: se oculta al bajar (>80px), aparece al subir. translateY, 0.3s ease-out.
Mobile (<768px)Bottom, centradoFijo, ancho intrínseco. Se desliza fuera mientras el <footer> está en pantalla (IntersectionObserver) para no tapar sus links; sin footer en la página, nunca se oculta.

Restricciones

  • Mínimo 2 items; máximo, los que quepan a 375px con todos los labels visibles (en la práctica 3–4 cortos). El invariante es el fit, no el número.
  • Labels siempre visibles. Nunca solo iconos. Sin truncar, sin scroll, sin menú overflow.
  • Sin dropdowns.
  • Sin selector de idioma (el Footer lo maneja).
  • Sin theme toggle. Sin badges ni contadores.
  • El logo llega listo para la píldora: el componente nunca lo re-colorea.

Decisiones

DecisiónPor qué
Alineado a la izquierda en desktopCon 2-3 items, centrar deja espacio muerto
Se oculta al bajar (smart scroll)Sitio narrativo: nav fijo roba espacio de lectura
Centrado abajo en mobileAccesible al pulgar. Ancho auto evita estiramiento
Radio 16px, no pill completoMás suave, menos aspecto de app
Sin opacity dimmingItems a 0.5 de opacidad parecían rotos. Background fill es más limpio.
Hover bg > active bgTres estados claros sin tocar la opacidad del texto
Ocultarse en el footer (móvil)La píldora fija abajo tapaba los links del footer justo cuando el lector llegaba a ellos (promovido de elastic)
Máximo por fit, no por número"Máx 3" era la estimación inicial; elastic y teresa demostraron que 4 labels cortos caben. El invariante real es caber a 375px.
Sin re-colorear el logo (v3)El brightness/invert asumía iconos monocromos oscuros y destrozaba logos fotográficos (teresa necesitó un fork !important). El sitio provee el asset listo.