Stepper

The <nys-stepper> is a reusable web component for use in New York State digital products. It indicates to a user how many steps are in a process. It updates to reflect the user's progress through the form.

Team Information

Provide details about your team. Who are you working with? What is your role?

Copy Code
<div class="nys-grid-row">
  <nys-stepper
    label="Register for Design System Office Hours"
    class="nys-desktop:nys-grid-col-4"
  >
    <nys-step
      label="Personal Details"
    ></nys-step>
    <nys-step
      label="Team Info"
      selected
    ></nys-step>
    <nys-step
      label="Usage Survey"
      current
    ></nys-step>
    <nys-step
      label="Newsletter Opt-In"
    ></nys-step>
  </nys-stepper>
  <div class="nys-desktop:nys-grid-col-8" id="nys-stepper-content">
    <div style="padding: 2rem; background-color: #fff;">
      <h2>Team Information</h2>
      <p>Provide details about your team. Who are you working with? What is your role?</p>
      <nys-textinput label="Agency Name" required width="md"></nys-textinput>
      <nys-textinput label="Team Name" required width="md"></nys-textinput>
    </div>
  </div>
</div>

Can't use NYS design System web components in your project? Try using the CSS Variables instead.


Options

Multi Page vs. Single Page Stepper

The nys-stepper can be used in both multi-page and single-page applications.

Multi-Page

  • The href should contain the URL of the page to navigate to when the step is clicked.
  • You will be responsible for managing the current and selected states of the steps as the user progresses through the pages.
  • Be sure to add the nys-stepper to each page of the multi-page application, since each step will be a separate page load.

Single-Page

  • The href should contain only the inner HTML of the step, not a full page URL.
  • The selected step will dynamically update as the user clicks on different steps.
  • You will be responsible for managing the current state of the steps as the user progresses through the steps.
  • You will need to listen for the nys-step-click event to load the content of the step into a container on the page.
Loading...
Copy Code
<div class="nys-grid-row">
  <nys-stepper
    label="Register for Design System Office Hours"
    class="nys-desktop:nys-grid-col-4"
    id="nys-stepper-dynamic"
  >
    <nys-step
      label="Personal Details"
      href="/stepper-pages/personal.html"
    ></nys-step>
    <nys-step
      label="Team Info"
      selected
      href="/stepper-pages/team.html"
    ></nys-step>
    <nys-step
      label="Usage Survey"
      current
      href="/stepper-pages/survey.html"
    ></nys-step>
    <nys-step
      label="Newsletter Opt-In"
      href="/stepper-pages/newsletter.html"
    ></nys-step>
  </nys-stepper>
  <div class="nys-desktop:nys-grid-col-8" id="nys-stepper-content-dynamic">
    Loading...
  </div>
</div>
<style>
  #nys-stepper-content-dynamic {
    background-color: #fff;
  }
</style>
<script>
  document.addEventListener("DOMContentLoaded", async () => {
    const stepper = document.getElementById("nys-stepper-dynamic");
    if (stepper?.updateComplete) {
      await stepper.updateComplete;
    }
    const selectedStep = stepper.querySelector("nys-step[selected]");
    if (selectedStep) {
      const href = selectedStep.getAttribute("href");
      if (href) {
        try {
          const res = await fetch(href);
          if (!res.ok) throw new Error("Failed to load " + href);
          const html = await res.text();
          const container = document.querySelector("#nys-stepper-content-dynamic");
          if (container) container.innerHTML = html;
        } catch (err) {
          console.error("Error loading initial step content:", err);
        }
      }
    }
  });
  document.addEventListener("nys-step-click", async (e) => {
    const href = e.detail?.href;
    if (!href) return;
    e.preventDefault();
    try {
      const res = await fetch(href);
      if (!res.ok) throw new Error("Failed to fetch ", href);
      const html = await res.text();
      const container = document.querySelector("#nys-stepper-content-dynamic");
      if (container) {
        container.innerHTML = html;
      }
    } catch (err) {
      console.error("Error loading innerHTML:", err);
    }
  });
</script>

Compact

On small screens, the nys-stepper will render in a compact mode where the progress is indicated by bars rather than complete steps. You can expand to see the names of steps by clicking on "Step x of y"

Actions Slot

  • You can add actions to the stepper by using the actions slot. The action slot only accepts nys-button and will render at the top of the nys-stepper on desktop and at the end of the stepper on mobile.

Step Options

Each step is represented by a <nys-step> element inside the <nys-stepper>.

Label

Copy Code
<nys-step label="Personal Details">

href and onClick

Add a href if the content of the step is plain html:

Copy Code
<nys-step 
  label="Personal Details"
  href="/nys-stepper/personal.html">
</nys-step>

Add an onClick if the content of the step is retrieved from an API or a function needs to be called:

Copy Code
<nys-step 
  label="Personal Details" 
  onClick="yourFunction()">
</nys-step>

States

Selected

  • Represents which step is being displayed to the user.
  • The selected step by default will match current step if selected is not defined.
  • selected cannot exist on a step later than the current step, if this is done by mistake it will correct to match current.

Current

  • Represents which step is the user is up to in the stepper's progress.
  • This is the last step the user is able to navigate to.
  • Users can go back and review past completed steps, therefor current and selected might not be the same at a given moment.
  • As the user completes steps, update the current flag to the next step, this needs to be done on the user side.
  • if current is not defined, the first step is marked by default.

Previous

  • This prop is automatically applied to all steps before the current step and is used to style the steps properly.
  • Do not add previous to a step on the implementation side.

Usage

When to use this component

  • Use a stepper for linear, ordered forms with more than 2 sections.
  • Use a stepper to show progress through a multi-step process.

When to consider something else

  • If there are only 1 or 2 sections to a form do not use a stepper.
  • Forms that are nonlinear and can be completed in any order should not use a stepper.

Do

  • Use a stepper for linear, ordered forms with more than 2 sections.
  • Use a stepper to show progress through a multi-step process.
  • Ensure that users can navigate back to previous steps to review or change information.

Don't

  • Do not use the stepper if there are only 1 or 2 sections to the form.
  • Do not use the stepper for forms that are nonlinear and can be completed in any order.

Accessibility

The nys-stepper component includes the following accessibility-focused features:

  • Proper ARIA roles and attributes to ensure screen readers can interpret the steps correctly.
  • Keyboard navigation support, allowing users to iterate the steps using the keyboard.
  • Visual focus indicators to help users navigate the component.

Properties

Property Type Component
id String nys-stepper
name String nys-stepper
label String both
selected boolean nys-step
current boolean nys-step
href String (URL) nys-step
onClick JS function nys-step

CSS Variables

There are no existing CSS variables for this component. Explore existing options, or propose a new one with a Component Proposal.


Events

The <nys-stepper> component emits the following events:

  1. nys-step-click - Emitted when a nys-step is clicked

You can listen to these events using JavaScript:

Copy Code
// Select the stepper component
const stepper = document.querySelector("nys-stepper");
// Listen for the 'nys-step-click' event
stepper.addEventListener("nys-step-click", () => {
	console.log("nys-step clicked");
});

Suggest a New Component

Do you have an idea for a new NYS Design System web component? Look through the existing proposals in our GitHub discussions board to see if someone already proposed something similar. If not, feel free to submit one.