ATOM
Components (registry)Surfaces

ProgressNav

A stepped rail that fills as you advance through a flow. Wizards and multi-step forms.

Preview

Editorial

<!-- F12c editorial — non-derivable only. Review: Karen. -->

## Ejemplos

Anchored section rail:

```tsx import { ProgressNav } from '@/components/atoms/ProgressNav';

<ProgressNav items={[ { id: 'features', label: 'Features' }, { id: 'pricing', label: 'Pricing' }, ]} cta={{ label: 'Contact', href: '#contact' }} /> ```

## Accesibilidad

- Root is a `nav`; keep labels short. Section `id`s on the page must match `items[].id`. - Motion hooks read `data-progress-nav-*` — without them the fill indicator will not track scroll.

## Cuándo no usar

- Discrete checkout steps with state → `Stepper`. - In-panel tab switching without page sections → `Tabs`.

Uso

import { ProgressNav } from '@/components/atoms/ProgressNav';

<ProgressNav
  items={[{ id: 'features', label: 'Features' }, { id: 'pricing', label: 'Pricing' }]}
  cta={{ label: 'Contact', href: '#contact' }}
/>

Props

  • items: ProgressNavItem[] (required)
  • logo: ReactNode
  • cta: { label: string
  • href: string (required)

Gotchas

  • react

    items is required [{id,label}]. Anchors must match section ids on the page. Motion hooks read data-progress-nav-* attributes.

  • a11y

    Root is a nav; keep labels short. Logo links to topAnchor (default top); CTA is a plain href.

Anatomía CSS

<div class="progress-nav">
  <span class="progress-nav__btn"></span>
  <span class="progress-nav__btn-text"></span>
  <span class="progress-nav__contact-btn"></span>
  <span class="progress-nav__indicator"></span>
  <span class="progress-nav__inner"></span>
  <span class="progress-nav__list"></span>
  <span class="progress-nav__logo"></span>
  <span class="progress-nav__logo-svg"></span>
  <span class="progress-nav__wrapper"></span>
</div>
ClasePropósito
progress-navroot
progress-nav__btnelement
progress-nav__btn-textelement
progress-nav__contact-btnelement
progress-nav__indicatorelement
progress-nav__innerelement
progress-nav__listelement
progress-nav__logoelement
progress-nav__logo-svgelement
progress-nav__wrapperelement

Tokens resueltos

Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.

VariantePropValorToken
allfg#fafafaprimary.foreground
allbg#0a0a0aprimary
allhover-bg#262626primary.hover

Animaciones

PropiedadDuraciónEasing
allvar(--duration-700)cubic-bezier(0.16
1
0.3
1)
transformvar(--duration-500)cubic-bezier(0.16
1
0.3
1)
background-colorvar(--duration-200)var(--easing-in-out)

CSS mínimo funcional

Autocontenido: sin imports ni tokens. Para previews y prototipos — en producción se consume el CSS del DS (@atom-uikit/css/components.css</code>, <code>@atom-uikit/tokens/tokens.css).

/* ============================================================
   ProgressNav — fixed one-page navigation with scroll indicator
   Uses GSAP ScrollTrigger via @atom-uikit/animations
   ============================================================ */

.progress-nav {
  width: 100%;
  padding: 24px;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 30;
}

.progress-nav__inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;
}

/* ---- Logo ---- */
.progress-nav__logo {
  color: inherit;
  text-decoration: none;
  display: flex;
  align-items: center;
}

.progress-nav__logo-svg {
  width: 8em;
  height: auto;
}

/* ---- Pill wrapper (contains the nav list) ---- */
.progress-nav__wrapper {
  background-color: #f5f5f5;
  border-radius: 9999px;
  padding: 4px;
}

/* ---- Nav list ---- */
.progress-nav__list {
  border-radius: 9999px;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  position: relative;
  overflow: hidden;
}

/* ---- Sliding indicator ---- */
.progress-nav__indicator {
  z-index: 2;
  background-color: #ffffff;
  border-radius: 9999px;
  width: 2.5em;
  height: 2.5em;
  position: absolute;
  left: -2.5em;
  transition: all 700ms cubic-bezier(0.16, 1, 0.3, 1);
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}

/* ---- Nav buttons ---- */
.progress-nav__btn {
  z-index: 3;
  cursor: pointer;
  color: inherit;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 2.5em;
  padding-left: 1em;
  padding-right: 1em;
  text-decoration: none;
  position: relative;
  overflow: hidden;
}

/* Hidden sentinel buttons (before/after) */
.progress-nav__btn.is--before {
  z-index: 1;
  width: 2.5em;
  height: 2.5em;
  padding: 0;
  position: absolute;
  right: 100%;
}

.progress-nav__btn.is--after {
  z-index: 1;
  width: 2.5em;
  height: 2.5em;
  padding: 0;
  position: absolute;
  left: 100%;
}

/* ---- Text with hover slide animation ---- */
.progress-nav__btn-text {
  white-space: nowrap;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  font-size: 16px;
  font-weight: 500;
  transition: transform 500ms cubic-bezier(0.16, 1, 0.3, 1);
  transform: translateY(0%) rotate(0.001deg);
}

.progress-nav__btn-text.is--duplicate {
  position: absolute;
  top: 100%;
}

.progress-nav__btn:hover .progress-nav__btn-text,
.progress-nav__contact-btn:hover .progress-nav__btn-text {
  transform: translateY(-100%) rotate(0.001deg);
}

/* ---- CTA button ---- */
.progress-nav__contact-btn {
  color: #fafafa;
  background-color: #0a0a0a;
  border-radius: 9999px;
  height: 3.5em;
  padding-left: 24px;
  padding-right: 24px;
  text-decoration: none;
  display: flex;
  align-items: center;
  position: relative;
  overflow: hidden;
  transition: background-color 200ms cubic-bezier(0.4, 0, 0.2, 1);
}

.progress-nav__contact-btn:hover {
  background-color: #262626;
}

/* ---- Reduced motion ---- */
@media (prefers-reduced-motion: reduce) {
  .progress-nav__indicator {
    transition-duration: 0ms;
  }
  .progress-nav__btn-text {
    transition-duration: 0ms;
  }
  .progress-nav__contact-btn {
    transition-duration: 0ms;
  }
}

Codigo fuente

components-react
components/atoms/ProgressNav.tsx
import { forwardRef, type ReactNode } from 'react';

export type ProgressNavItem = {
  id: string;
  label: string;
};

export type ProgressNavProps = {
  items: ProgressNavItem[];
  logo?: ReactNode;
  cta?: {
    label: string;
    href: string;
  };
  topAnchor?: string;
  bottomAnchor?: string;
  className?: string;
};

function cn(...classes: (string | false | undefined | null)[]) {
  return classes.filter(Boolean).join(' ');
}

function NavButton({ id, label }: { id: string; label: string }) {
  return (
    <a
      data-progress-nav-target={`#${id}`}
      href={`#${id}`}
      className="progress-nav__btn"
    >
      <span className="progress-nav__btn-text">{label}</span>
      <span className="progress-nav__btn-text is--duplicate">{label}</span>
    </a>
  );
}

export const ProgressNav = forwardRef<HTMLElement, ProgressNavProps>(
  ({ items, logo, cta, topAnchor = 'top', bottomAnchor = 'bottom', className }, ref) => {
    return (
      <nav ref={ref} className={cn('progress-nav', className)}>
        <div className="progress-nav__inner">
          {logo && (
            <a href={`#${topAnchor}`} className="progress-nav__logo">
              {logo}
            </a>
          )}

          <div className="progress-nav__wrapper">
            <div data-progress-nav-list="" className="progress-nav__list">
              <div className="progress-nav__indicator" />
              <div
                data-progress-nav-target={`#${topAnchor}`}
                className="progress-nav__btn is--before"
              />
              {items.map((item) => (
                <NavButton key={item.id} id={item.id} label={item.label} />
              ))}
              <div
                data-progress-nav-target={`#${bottomAnchor}`}
                className="progress-nav__btn is--after"
              />
            </div>
          </div>

          {cta && (
            <a href={cta.href} className="progress-nav__contact-btn">
              <span className="progress-nav__btn-text">{cta.label}</span>
              <span className="progress-nav__btn-text is--duplicate">{cta.label}</span>
            </a>
          )}
        </div>
      </nav>
    );
  },
);

ProgressNav.displayName = 'ProgressNav';

On this page

Detalles

Publicado14 de mayo de 2026
Categoriasurfaces
Lectura...
Visitas...
Ayuda?Slack

Componente

ProgressNav

Source

components-react / cssDisponible via MCP: atom_uikit_source("progress-nav")