ATOM
Components (registry)Actions

Stepper

Numbered steps show progress through a sequence. Checkouts and multi-stage onboarding.

Preview

Editorial

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

## Ejemplos

Checkout progress:

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

<Stepper orientation="horizontal" steps={[ { state: 'completed', title: 'Cart', description: 'Items saved' }, { state: 'active', title: 'Payment' }, { state: 'upcoming', title: 'Confirm' }, ]} /> ```

## Accesibilidad

- Root is `role="list"` with `aria-label="Progress steps"`; the active step sets `aria-current="step"`. - Titles must be unique and human-readable — numbers alone are not enough.

## Cuándo no usar

- Single-page forms with no sequence — use headings / sections, not a stepper. - Tabs that switch content panels → `Tabs` (not a progress metaphor).

Uso

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

<Stepper
  orientation="horizontal"
  steps={[
    { state: 'completed', title: 'Account' },
    { state: 'active', title: 'Plan' },
    { state: 'upcoming', title: 'Pay' },
  ]}
/>

Props

  • state: 'completed' | 'active' | 'upcoming'
  • title: string (required)
  • description: string

Gotchas

  • react

    Stepper accepts orientation (horizontal|vertical) and steps: { state, title, description }[]. Keep exactly one step with state active.

  • a11y

    Titles must be unique and human-readable; do not rely on color alone to convey completed vs upcoming.

Anatomía CSS

<div class="stepper">
  <span class="stepper__connector"></span>
  <span class="stepper__connector--completed"></span>
  <span class="stepper__content"></span>
  <span class="stepper__description"></span>
  <span class="stepper__indicator"></span>
  <span class="stepper__step"></span>
  <span class="stepper__step--active"></span>
  <span class="stepper__step--completed"></span>
  <span class="stepper__step--upcoming"></span>
  <span class="stepper__title"></span>
</div>
ClasePropósito
stepperroot
stepper--horizontalmodifier
stepper--verticalmodifier
stepper__connectorelement
stepper__connector--completedelement
stepper__contentelement
stepper__descriptionelement
stepper__indicatorelement
stepper__stepelement
stepper__step--activeelement
stepper__step--completedelement
stepper__step--upcomingelement
stepper__titleelement

Tokens resueltos

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

VariantePropValorToken
allbg#0a0a0aprimary
allfg#525252muted.foreground

Animaciones

PropiedadDuraciónEasing
background-colorvar(--duration-200)var(--easing-in-out)
colorvar(--duration-200)var(--easing-in-out)
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).

/* ============================================================
   Stepper — numbered steps with states (completed, active, upcoming)
   Connector is a separate flex item between steps, not absolute.
   ============================================================ */

.stepper {
  display: flex;
  align-items: flex-start;
}

.stepper--horizontal {
  flex-direction: row;
}

.stepper--vertical {
  flex-direction: column;
}

/* ---- Step item ---- */
.stepper__step {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  flex-shrink: 0;
}

/* ---- Step indicator (number or checkmark) ---- */
.stepper__indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
  border-radius: 9999px;
  font-size: 12.8px;
  font-weight: 600;
  line-height: 1;
  transition:
    background-color 200ms cubic-bezier(0.4, 0, 0.2, 1),
    color 200ms cubic-bezier(0.4, 0, 0.2, 1);
}

.stepper__step--upcoming .stepper__indicator {
  background-color: #f5f5f5;
  color: #525252;
}

.stepper__step--active .stepper__indicator {
  background-color: #0a0a0a;
  color: #fafafa;
}

.stepper__step--completed .stepper__indicator {
  background-color: #0a0a0a;
  color: #fafafa;
}

/* ---- Step content ---- */
.stepper__content {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-top: 4px;
  min-width: 0;
}

.stepper__title {
  font-size: 12.8px;
  font-weight: 500;
  line-height: 1.3;
  color: #0a0a0a;
}

.stepper__step--upcoming .stepper__title {
  color: #525252;
}

.stepper__description {
  font-size: 10.24px;
  line-height: 1.4;
  color: #525252;
}

/* ---- Connector (flex item between steps) ---- */
.stepper__connector {
  background-color: #e5e5e5;
  flex-shrink: 0;
  transition: background-color 200ms cubic-bezier(0.4, 0, 0.2, 1);
}

.stepper__connector--completed {
  background-color: #0a0a0a;
}

/* Horizontal: line between steps, vertically centered on indicator */
.stepper--horizontal .stepper__connector {
  align-self: flex-start;
  margin-top: 1rem; /* center with 2rem indicator */
  height: 1px;
  width: 32px;
  transform: translateY(-0.5px);
}

/* Vertical: line between steps, horizontally centered on indicator */
.stepper--vertical .stepper__connector {
  margin-left: 1rem; /* center with 2rem indicator */
  width: 1px;
  height: 24px;
  transform: translateX(-0.5px);
}

/* ---- Reduced motion ---- */
@media (prefers-reduced-motion: reduce) {
  .stepper__indicator,
  .stepper__connector {
    transition-duration: 0ms;
  }
}

Codigo fuente

components-react
components/atoms/Stepper.tsx
import { forwardRef, Fragment } from 'react';

export type StepState = 'completed' | 'active' | 'upcoming';

export type StepProps = {
  state?: StepState;
  title: string;
  description?: string;
};

export type StepperProps = {
  orientation?: 'horizontal' | 'vertical';
  steps: StepProps[];
  className?: string;
};

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

function CheckIcon() {
  return (
    <svg
      width="14"
      height="14"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="3"
      strokeLinecap="round"
      strokeLinejoin="round"
    >
      <polyline points="20 6 9 17 4 12" />
    </svg>
  );
}

export const Stepper = forwardRef<HTMLDivElement, StepperProps>(
  ({ orientation = 'horizontal', steps, className }, ref) => {
    return (
      <div
        ref={ref}
        className={cn('stepper', `stepper--${orientation}`, className)}
        role="list"
        aria-label="Progress steps"
      >
        {steps.map((step, index) => {
          const state = step.state ?? 'upcoming';
          const isLast = index === steps.length - 1;

          return (
            <Fragment key={`step-${index}`}>
              <div
                className={cn('stepper__step', `stepper__step--${state}`)}
                role="listitem"
                aria-current={state === 'active' ? 'step' : undefined}
              >
                <div className="stepper__indicator" aria-hidden="true">
                  {state === 'completed' ? <CheckIcon /> : index + 1}
                </div>
                <div className="stepper__content">
                  <span className="stepper__title">{step.title}</span>
                  {step.description && (
                    <span className="stepper__description">{step.description}</span>
                  )}
                </div>
              </div>
              {!isLast && (
                <div
                  className={cn(
                    'stepper__connector',
                    state === 'completed' && 'stepper__connector--completed',
                  )}
                  aria-hidden="true"
                />
              )}
            </Fragment>
          );
        })}
      </div>
    );
  },
);

Stepper.displayName = 'Stepper';

Webflow

Webflowstepper

Pega en el Designer como application/json, luego convierte a Component (Atom / Stepper) y publica. Formato interno no documentado de Webflow — regenerable, no dependencia de runtime.

Setup del sitio (una vez)

Custom Code → Head:

<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/tokens.css">
<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/components.css">

Unsupported (no silencioso)

  • selector on .stepper__step--upcoming .stepper__indicatorcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .stepper__step--active .stepper__indicatorcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .stepper__step--completed .stepper__indicatorcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .stepper__step--upcoming .stepper__titlecompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .stepper--horizontal .stepper__connectorcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .stepper--vertical .stepper__connectorcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • @media on (prefers-reduced-motion: reduce)not a Designer breakpoint — moved to head Custom Code block

Tras pegar: Create component → nombre Atom / Stepper → Publish.

On this page

Detalles

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

Componente

Stepper

Source

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