Ir al contenido
English

Navegar

Escribe para buscar. Pulsa Escape para cerrar.

    Componentes · js

    Pasos (stepper)

    Un formulario largo partido en pasos: un índice numerado con estado, un panel por paso y controles para moverse entre ellos. El servidor envía una lista ordenada de enlaces y los paneles uno bajo otro, así que todo se lee y se rellena sin script. En init solo queda visible el panel actual, el índice marca lo hecho, lo actual y lo que falta, y los botones data-iv-step se niegan a avanzar mientras los campos del panel no sean válidos.

    CSS: css/components/stepper.css (en ivolt.css). JS: @intervolutions/ivolt/stepper o auto.js. Añadido en la v0.6.

    Compra en tres pasosfixtures/stepper/basic.htmlAbrir sola — Compra en tres pasos
    <form class="iv-form" data-iv-component="form" novalidate aria-describedby="step-note">
      <p class="iv-u-text-sm iv-u-text-muted" id="step-note">Demo checkout: nothing is submitted, no order is placed and no payment is taken. The fields are validated in the browser so the steps can refuse to advance, and the last button only announces that the flow is complete.</p>
      <div class="iv-stepper" data-iv-component="stepper">
        <ol class="iv-stepper__list" aria-label="Checkout">
          <li class="iv-stepper__step" data-iv-state="current">
            <a class="iv-stepper__trigger" href="#step-account" aria-current="step">
              <span class="iv-stepper__marker" aria-hidden="true">1</span>
              <span class="iv-stepper__label">Account</span>
              <span class="iv-stepper__hint">Email and team</span>
            </a>
          </li>
          <li class="iv-stepper__step">
            <a class="iv-stepper__trigger" href="#step-payment">
              <span class="iv-stepper__marker" aria-hidden="true">2</span>
              <span class="iv-stepper__label">Payment</span>
              <span class="iv-stepper__hint">Card holder and country</span>
            </a>
          </li>
          <li class="iv-stepper__step">
            <a class="iv-stepper__trigger" href="#step-review">
              <span class="iv-stepper__marker" aria-hidden="true">3</span>
              <span class="iv-stepper__label">Review</span>
              <span class="iv-stepper__hint">Confirm the demo</span>
            </a>
          </li>
        </ol>
        <div class="iv-stepper__progress" aria-hidden="true"></div>
        <div class="iv-stepper__panels">
          <section class="iv-stepper__panel" id="step-account" aria-labelledby="step-account-title" tabindex="-1">
            <h3 class="iv-u-mt-0" id="step-account-title">Account</h3>
            <div class="iv-field">
              <label class="iv-label" for="step-email">Work email <span class="iv-label__required" aria-hidden="true">*</span></label>
              <input class="iv-input" id="step-email" name="email" type="email" required autocomplete="email">
            </div>
            <div class="iv-field">
              <label class="iv-label" for="step-team">Team name <span class="iv-label__required" aria-hidden="true">*</span></label>
              <input class="iv-input" id="step-team" name="team" type="text" required minlength="2" autocomplete="organization">
            </div>
            <div class="iv-stepper__controls" hidden>
              <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev" disabled>Back</button>
              <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Continue</button>
            </div>
          </section>
          <section class="iv-stepper__panel" id="step-payment" aria-labelledby="step-payment-title" tabindex="-1">
            <h3 class="iv-u-mt-0" id="step-payment-title">Payment</h3>
            <div class="iv-field">
              <label class="iv-label" for="step-holder">Card holder <span class="iv-label__required" aria-hidden="true">*</span></label>
              <input class="iv-input" id="step-holder" name="holder" type="text" required autocomplete="cc-name">
            </div>
            <div class="iv-field">
              <label class="iv-label" for="step-country">Billing country <span class="iv-label__required" aria-hidden="true">*</span></label>
              <select class="iv-select" id="step-country" name="country" required>
                <option value="">Select a country</option>
                <option value="es">Spain</option>
                <option value="pt">Portugal</option>
                <option value="fr">France</option>
              </select>
            </div>
            <p class="iv-u-text-sm iv-u-text-muted iv-u-m-0">No card number is asked for: this demo has nothing to charge.</p>
            <div class="iv-stepper__controls" hidden>
              <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev">Back</button>
              <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Continue</button>
            </div>
          </section>
          <section class="iv-stepper__panel" id="step-review" aria-labelledby="step-review-title" tabindex="-1">
            <h3 class="iv-u-mt-0" id="step-review-title">Review</h3>
            <p>Nothing on this page leaves the browser. Confirming only emits the completion event of the component, which a real site would use to submit its own form.</p>
            <div class="iv-field">
              <label class="iv-check"><input id="step-confirm" name="confirm" type="checkbox" required> I understand this demo sends nothing <span class="iv-label__required" aria-hidden="true">*</span></label>
            </div>
            <div class="iv-stepper__controls" hidden>
              <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev">Back</button>
              <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Finish the demo</button>
            </div>
          </section>
        </div>
      </div>
    </form>
    // data-iv-component="form"
    // data-iv-component="stepper"
    {
      state: "current",
      step: "prev",
    }

    Índice vertical

    iv-stepper--vertical lleva el índice a una columna junto a los paneles desde el punto de ruptura md, con el conector bajando en lugar de cruzando. Por debajo es la misma disposición apilada que la variante por defecto.

    Índice verticalfixtures/stepper/vertical.htmlAbrir sola — Índice vertical
    <div class="iv-stepper iv-stepper--vertical" data-iv-component="stepper" data-iv-validate="false">
      <ol class="iv-stepper__list" aria-label="Release">
        <li class="iv-stepper__step">
          <a class="iv-stepper__trigger" href="#rel-draft">
            <span class="iv-stepper__marker" aria-hidden="true">1</span>
            <span class="iv-stepper__label">Draft</span>
            <span class="iv-stepper__hint">Write the notes</span>
          </a>
        </li>
        <li class="iv-stepper__step">
          <a class="iv-stepper__trigger" href="#rel-review">
            <span class="iv-stepper__marker" aria-hidden="true">2</span>
            <span class="iv-stepper__label">Review</span>
            <span class="iv-stepper__hint">Two pairs of eyes</span>
          </a>
        </li>
        <li class="iv-stepper__step">
          <a class="iv-stepper__trigger" href="#rel-build">
            <span class="iv-stepper__marker" aria-hidden="true">3</span>
            <span class="iv-stepper__label">Build</span>
            <span class="iv-stepper__hint">Sizes and tests</span>
          </a>
        </li>
        <li class="iv-stepper__step">
          <a class="iv-stepper__trigger" href="#rel-ship">
            <span class="iv-stepper__marker" aria-hidden="true">4</span>
            <span class="iv-stepper__label">Ship</span>
            <span class="iv-stepper__hint">Only with a signature</span>
          </a>
        </li>
      </ol>
      <div class="iv-stepper__panels">
        <section class="iv-stepper__panel" id="rel-draft" aria-labelledby="rel-draft-title" tabindex="-1">
          <h3 class="iv-u-mt-0" id="rel-draft-title">Draft</h3>
          <p>Sample flow, invented for this fixture: no release is prepared or published from this page. The vertical variant keeps the index in a column beside the panels from the <code>md</code> breakpoint upwards.</p>
          <div class="iv-stepper__controls" hidden>
            <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev" disabled>Back</button>
            <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Continue</button>
          </div>
        </section>
        <section class="iv-stepper__panel" id="rel-review" aria-labelledby="rel-review-title" tabindex="-1">
          <h3 class="iv-u-mt-0" id="rel-review-title">Review</h3>
          <p>The connector between markers fills up to the step you are on, and every step already done shows a check instead of its number.</p>
          <div class="iv-stepper__controls" hidden>
            <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev">Back</button>
            <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Continue</button>
          </div>
        </section>
        <section class="iv-stepper__panel" id="rel-build" aria-labelledby="rel-build-title" tabindex="-1">
          <h3 class="iv-u-mt-0" id="rel-build-title">Build</h3>
          <p>There is nothing to validate in this fixture, so the panels advance freely: the steps carry text, not fields.</p>
          <div class="iv-stepper__controls" hidden>
            <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev">Back</button>
            <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Continue</button>
          </div>
        </section>
        <section class="iv-stepper__panel" id="rel-ship" aria-labelledby="rel-ship-title" tabindex="-1">
          <h3 class="iv-u-mt-0" id="rel-ship-title">Ship</h3>
          <p>The last step does not publish anything: it announces that the flow is complete and leaves the decision to whoever is reading.</p>
          <div class="iv-stepper__controls" hidden>
            <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev">Back</button>
            <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Finish the demo</button>
          </div>
        </section>
      </div>
    </div>
    // data-iv-component="stepper"
    {
      validate: false,
      step: "prev",
    }

    Compacto

    iv-stepper--compact dibuja solo los marcadores y la etiqueta del paso actual; las demás etiquetas siguen en la página para los lectores de pantalla, así que ningún paso pierde su nombre. Este además corre con data-iv-linear="false", de modo que cualquier paso es alcanzable desde el índice en cualquier momento.

    Compacto y no linealfixtures/stepper/compact.htmlAbrir sola — Compacto y no lineal
    <div class="iv-stepper iv-stepper--compact" data-iv-component="stepper" data-iv-linear="false" data-iv-validate="false">
      <ol class="iv-stepper__list" aria-label="Onboarding">
        <li class="iv-stepper__step">
          <a class="iv-stepper__trigger" href="#onb-profile">
            <span class="iv-stepper__marker" aria-hidden="true">1</span>
            <span class="iv-stepper__label">Profile</span>
          </a>
        </li>
        <li class="iv-stepper__step">
          <a class="iv-stepper__trigger" href="#onb-workspace">
            <span class="iv-stepper__marker" aria-hidden="true">2</span>
            <span class="iv-stepper__label">Workspace</span>
          </a>
        </li>
        <li class="iv-stepper__step">
          <a class="iv-stepper__trigger" href="#onb-invite">
            <span class="iv-stepper__marker" aria-hidden="true">3</span>
            <span class="iv-stepper__label">Invites</span>
          </a>
        </li>
        <li class="iv-stepper__step">
          <a class="iv-stepper__trigger" href="#onb-done">
            <span class="iv-stepper__marker" aria-hidden="true">4</span>
            <span class="iv-stepper__label">Done</span>
          </a>
        </li>
      </ol>
      <div class="iv-stepper__progress" aria-hidden="true"></div>
      <div class="iv-stepper__panels">
        <section class="iv-stepper__panel" id="onb-profile" aria-labelledby="onb-profile-title" tabindex="-1">
          <h3 class="iv-u-mt-0" id="onb-profile-title">Profile</h3>
          <p>Sample onboarding, invented for this fixture: no account exists and nothing is saved. In the compact variant only the markers and the label of the current step are drawn; the other labels stay in the page for screen readers, so every step keeps its name.</p>
          <div class="iv-stepper__controls" hidden>
            <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev" disabled>Back</button>
            <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Continue</button>
          </div>
        </section>
        <section class="iv-stepper__panel" id="onb-workspace" aria-labelledby="onb-workspace-title" tabindex="-1">
          <h3 class="iv-u-mt-0" id="onb-workspace-title">Workspace</h3>
          <p>This stepper is not linear, so every step can be reached from the index at any time and the arrow keys walk the whole row.</p>
          <div class="iv-stepper__controls" hidden>
            <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev">Back</button>
            <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Continue</button>
          </div>
        </section>
        <section class="iv-stepper__panel" id="onb-invite" aria-labelledby="onb-invite-title" tabindex="-1">
          <h3 class="iv-u-mt-0" id="onb-invite-title">Invites</h3>
          <p>No invitation is sent from here: there is no address book and no mail behind this page.</p>
          <div class="iv-stepper__controls" hidden>
            <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev">Back</button>
            <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Continue</button>
          </div>
        </section>
        <section class="iv-stepper__panel" id="onb-done" aria-labelledby="onb-done-title" tabindex="-1">
          <h3 class="iv-u-mt-0" id="onb-done-title">Done</h3>
          <p>The progress bar above reads the same custom property the connector uses, so both tell the same story.</p>
          <div class="iv-stepper__controls" hidden>
            <button class="iv-button iv-button--secondary" type="button" data-iv-step="prev">Back</button>
            <button class="iv-button iv-button--primary" type="button" data-iv-step="next">Finish the demo</button>
          </div>
        </section>
      </div>
    </div>
    // data-iv-component="stepper"
    {
      linear: false,
      validate: false,
      step: "prev",
    }

    Opciones

    OpciónAtributoPor defectoEfecto
    lineardata-iv-lineartrueSolo son alcanzables desde el índice los pasos ya visitados y el actual; el resto llevan aria-disabled="true". Volver atrás y saltar de nuevo adelante no vuelve a validar.
    validatedata-iv-validatetrueComprueba los controles del panel actual antes de avanzar.
    hashdata-iv-hashfalseSincroniza location.hash con el panel actual y responde a hashchange.
    focusdata-iv-focuspanelDónde cae el foco tras un cambio: el panel nuevo, o none.
    statusTextdata-iv-status-textStep {index} of {total}: {label}Frase que se anuncia en la región en vivo tras cada cambio.

    Métodos y eventos

    go(index, reason), next(), prev(), reset(), setState(index, state), destroy(); lectura de index (base 0), steps, isFirst e isLast. Con setState tu código marca un paso como error o done cuando responde un servidor.

    EventoCuándodetail
    iv:changeiv:changedantes y después de moverse; el primero es cancelableindex, previousIndex, reason: trigger, next, prev, api o hash
    iv:completenext en el último paso, con su validación ya superadaindex

    El componente no envía nada. iv:complete es donde tu código manda el formulario, y cancelar iv:change es donde se niega a un movimiento por un motivo propio.

    Validación

    Con validate activo, next comprueba antes el panel actual. Si el panel está dentro de un <form> con instancia de Form, cada control del panel pasa por validateField y los mensajes aparecen donde ese componente los coloca; si no, el reportValidity() del navegador se detiene en el primer control inválido. Volver atrás no valida nunca. Las comprobaciones asíncronas, los pasos condicionales y el guardado de borradores quedan fuera de alcance: hazlos en tu propio manejador y llama a go() o setState() con la respuesta.

    Teclado y tecnología asistiva

    El índice es una <ol> de enlaces con etiqueta, y el paso actual lleva aria-current="step". Izquierda y Derecha, Inicio y Fin mueven el foco entre los disparadores alcanzables; Enter o Espacio activa uno. Los marcadores son decoración aria-hidden —el número que dibujan ya es el orden de la lista— y cada panel es una sección con encabezado y tabindex="-1" para que el foco pueda posarse en él. El estado se anuncia en una región educada oculta a la vista, y un paso con error añade un «has errors» invisible a su etiqueta, así que el color nunca va solo.

    Sin JavaScript

    Todos los paneles llegan visibles, uno bajo otro, y el índice es una lista de anclas que saltan a cada sección. Los botones de avance y retroceso se sirven dentro de un contenedor hidden, porque un botón que no puede avanzar es peor que ningún botón; init los muestra. No se esconde nada ni queda ningún campo inalcanzable: el formulario vuelve a ser uno largo, que es lo que era antes de partirlo.

    Tokens y locales

    LocalPor defectoQué mueve
    --iv-stepper-marker2remDiámetro del círculo numerado.
    --iv-stepper-linevar(--iv-color-border)Color del conector entre marcadores.
    --iv-stepper-fillvar(--iv-color-primary)Color del tramo de conector ya recorrido.
    --iv-stepper-track2pxGrosor del conector y de la barra de progreso.
    --iv-stepper-gapvar(--iv-space-6)Espacio entre el índice y los paneles.
    --iv-stepper-step-gapvar(--iv-space-5)Espacio entre pasos del índice.

    El componente escribe --iv-stepper-progress (de 0 a 1) en la raíz, y es lo que escala la barra opcional iv-stepper__progress. Cada paso lleva data-iv-state con done, current, upcoming o error: un marcador hecho muestra una marca de verificación en lugar de su número, el actual toma el halo de foco común y uno con error pasa al color de peligro. Con movimiento reducido los paneles aparecen sin desplazarse.

    Errores frecuentes

    • Pasos sin encabezado. Cada panel necesita el suyo: es el nombre del paso para quien no ve el índice.
    • Servir los paneles ocultos, o los controles visibles. Lo primero pierde el formulario sin script; lo segundo ofrece botones que no hacen nada.
    • Más de cinco o seis pasos. A partir de ahí el índice es un sumario y el formulario pide varias páginas.
    • Esperar que el componente envíe. Anuncia que se ha terminado; mandar los datos sigue siendo tuyo.