Stepper - National Design System

Step-by-step progress indicators for registration forms, onboarding flows, and service applications, with horizontal, vertical, and radial layouts that can switch responsively across breakpoints

Horizontal Layout

The default layout. Steps run start to end with the connecting line between circles. Best for short wizards on wide screens, where every step label fits on one row.

Personal Information Identity details and contact information
Document Upload Upload required supporting documents
Review & Confirmation Verify information before submission
Application Submitted Confirmation and next steps
<div class="nds-stepper" id="demo-stepper-horizontal" data-current="2" data-total="4"> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="1"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Personal Information</span> <span class="nds-stepper-description">Identity details and contact information</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="2"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Document Upload</span> <span class="nds-stepper-description">Upload required supporting documents</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="3"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Review & Confirmation</span> <span class="nds-stepper-description">Verify information before submission</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="4"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Application Submitted</span> <span class="nds-stepper-description">Confirmation and next steps</span> </div> </div> </div> </div>

Vertical Layout

Steps run top to bottom. Reach for it when a step carries detailed content or action buttons, or when the column is narrow, such as a sidebar or a drawer.

Personal Information Identity details and contact information
Document Upload Upload required supporting documents
Review & Confirmation Verify information before submission
Application Submitted Confirmation and next steps
<div class="nds-stepper nds-vertical" id="demo-stepper-vertical" data-current="2" data-total="4"> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="1"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Personal Information</span> <span class="nds-stepper-description">Identity details and contact information</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="2"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Document Upload</span> <span class="nds-stepper-description">Upload required supporting documents</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="3"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Review & Confirmation</span> <span class="nds-stepper-description">Verify information before submission</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="4"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Application Submitted</span> <span class="nds-stepper-description">Confirmation and next steps</span> </div> </div> </div> </div>

Radial Layout

One step at a time inside a progress ring, with the next step named underneath. Use it where vertical space is tight. This layout needs the progress ring markup, which the other two hide.

Personal Information Identity details and contact information Next: Document Upload
Document Upload Upload required supporting documents Next: Review & Confirmation
Review & Confirmation Verify information before submission Next: Application Submitted
Application Submitted Confirmation and next steps
<div class="nds-stepper nds-radial" id="demo-stepper-radial" data-current="2" data-total="4"> <div class="nds-progress-circle"> <svg width="64" height="64" viewBox="0 0 24 24"> <circle class="nds-progress-bg" cx="12" cy="12" r="10" fill="none" stroke-width="3" /> <circle class="nds-progress-track" cx="12" cy="12" r="10" fill="none" stroke-width="3" stroke-dasharray="62.83" stroke-dashoffset="62.83" stroke-linecap="round" /> </svg> <div class="nds-progress-info"> <span class="nds-progress-steps"></span> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="1"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Personal Information</span> <span class="nds-stepper-description">Identity details and contact information</span> <span class="nds-stepper-next">Next: Document Upload</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="2"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Document Upload</span> <span class="nds-stepper-description">Upload required supporting documents</span> <span class="nds-stepper-next">Next: Review & Confirmation</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="3"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Review & Confirmation</span> <span class="nds-stepper-description">Verify information before submission</span> <span class="nds-stepper-next">Next: Application Submitted</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="4"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Application Submitted</span> <span class="nds-stepper-description">Confirmation and next steps</span> </div> </div> </div> </div>

Timeline with Divider Labels

A past-to-present record: each step opens with a divider carrying its date, and the rule doubles as the separator between entries. Add nds-reverse so the newest entry sits on top, and write the steps oldest first.

2013 — 2018
Assistant Professor Department of Industrial Engineering
2018 — 2020
Associate Professor Department of Industrial Engineering
2020 — 2022
Supervisor, Entrepreneurship Center Office of the Vice Rector for Research
2023 — Present
Head, Department of Industrial Engineering College of Engineering
  • Led the 2024 program accreditation review.
  • Opened two research labs with industry funding.
<div class="nds-stepper nds-vertical nds-dot nds-reverse" data-current="4" data-total="4"> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="1"></div> </div> <div class="nds-stepper-content"> <div class="nds-divider">2013 — 2018</div> <div class="nds-stepper-text"> <span class="nds-stepper-title">Assistant Professor</span> <span class="nds-stepper-description">Department of Industrial Engineering</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="2"></div> </div> <div class="nds-stepper-content"> <div class="nds-divider">2018 — 2020</div> <div class="nds-stepper-text"> <span class="nds-stepper-title">Associate Professor</span> <span class="nds-stepper-description">Department of Industrial Engineering</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="3"></div> </div> <div class="nds-stepper-content"> <div class="nds-divider">2020 — 2022</div> <div class="nds-stepper-text"> <span class="nds-stepper-title">Supervisor, Entrepreneurship Center</span> <span class="nds-stepper-description">Office of the Vice Rector for Research</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="4"></div> </div> <div class="nds-stepper-content"> <div class="nds-divider">2023 — Present</div> <div class="nds-stepper-text"> <span class="nds-stepper-title">Head, Department of Industrial Engineering</span> <span class="nds-stepper-description">College of Engineering</span> </div> <ul> <li>Led the 2024 program accreditation review.</li> <li>Opened two research labs with industry funding.</li> </ul> </div> </div> </div>

Responsive Stepper

A single stepper that morphs between variants as the viewport crosses breakpoints. Suited to flows that span mobile and desktop, where a compact radial on small screens should give way to a full vertical panel on desktop. Resize the window to see the transition

Personal Information Identity details and contact information Next: Document Upload
Document Upload Upload required supporting documents Next: Review & Confirmation
Review & Confirmation Verify information before submission Next: Application Submitted
Application Submitted Confirmation and next steps
<div class="nds-stepper" id="demo-stepper-responsive" data-current="2" data-total="4"> <div class="nds-progress-circle"> <svg width="64" height="64" viewBox="0 0 24 24"> <circle class="nds-progress-bg" cx="12" cy="12" r="10" fill="none" stroke-width="3" /> <circle class="nds-progress-track" cx="12" cy="12" r="10" fill="none" stroke-width="3" stroke-dasharray="62.83" stroke-dashoffset="62.83" stroke-linecap="round" /> </svg> <div class="nds-progress-info"> <span class="nds-progress-steps"></span> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="1"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Personal Information</span> <span class="nds-stepper-description">Identity details and contact information</span> <span class="nds-stepper-next">Next: Document Upload</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="2"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Document Upload</span> <span class="nds-stepper-description">Upload required supporting documents</span> <span class="nds-stepper-next">Next: Review & Confirmation</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="3"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Review & Confirmation</span> <span class="nds-stepper-description">Verify information before submission</span> <span class="nds-stepper-next">Next: Application Submitted</span> </div> </div> </div> <div class="nds-stepper-step"> <div class="nds-stepper-base"> <div class="nds-stepper-circle" data-step-text="4"></div> </div> <div class="nds-stepper-content"> <div class="nds-stepper-text"> <span class="nds-stepper-title">Application Submitted</span> <span class="nds-stepper-description">Confirmation and next steps</span> </div> </div> </div> </div>

Built-in Features

Auto-initialization

Activates when .nds-stepper is on the page. Step states, progress display, and control button handlers attach automatically.

Three Layout Modes

Horizontal, vertical, and radial layouts cover wide forms, narrow sidebars, and compact dashboard widgets respectively.

Automatic State Management

Set data-current and all steps update their completed, current, or upcoming states automatically.

Declarative Control Buttons

Add data-stepper-control to any button to navigate steps without writing JavaScript.

Step Change Events

The nds:stepper:change event fires on every navigation with current step, total, and percentage in the detail.

Responsive Layout

A single stepper adapts across breakpoints via modifier classes like nds-radial-sm and nds-vertical-lg (same pattern as nds-tableView-sm). One DOM tree renders as horizontal, vertical, or radial depending on viewport.

Look-Ahead Preview

Add .nds-stepper-next inside any step to show the upcoming step name. Automatically hidden on horizontal and vertical layouts; surfaces only in radial.

Programmatic Control

Navigate with NDS.Stepper.next(id), NDS.Stepper.previous(id), and NDS.Stepper.goTo(id, step).

Usage Guidelines

Best Practices

  • Use steppers for multi-step forms like registration, applications, and onboarding flows where the user completes discrete stages in order
  • Use the horizontal layout when you have enough width and want all steps visible at once. This is the default and works best with 3 to 5 steps
  • Use the vertical layout when steps need detailed content, action buttons, or the interface is narrow (sidebars, mobile drawers). Add nds-reverse to flip progress direction from bottom-to-top, useful for timelines or chat-like flows
  • Use the radial layout for compact spaces like dashboard cards or mobile headers where only the current step needs to be visible
  • Use the responsive modifier classes (nds-{horizontal|vertical|radial}-{sm|md|lg}) when one flow spans multiple breakpoints. Same convention as nds-tableView-sm. The single DOM tree carrying the combined radial + linear markup morphs via JS; no parallel steppers or manual visibility switching needed
  • Do not use a stepper for indeterminate or percentage-based progress. Use the Progress component instead
  • Do not use a stepper for navigation menus or tab-like interfaces. Use Tabs for switching between independent content panels
  • Use the dot variant when step labels are not needed and visual progress alone is sufficient, such as onboarding slides or image carousels
  • Connecting lines between steps are shown by default on all steps except the last
  • Keep step titles short (2 to 4 words). Use the description for additional context
  • In radial steppers, add .nds-stepper-next inside the step text to preview the upcoming step name. Omit it on the final step
  • Radial steppers work best with 3 to 6 steps. Fewer than 3 makes the circle progress hard to read; more than 6 makes step titles too compressed
  • A horizontal stepper runs out of room on small screens. Pair it with the radial variant there: nds-radial nds-horizontal-lg is radial by default and turns horizontal from the lg breakpoint up. The same pairing works for vertical
  • Use NDS.Stepper.next() for form steps, not data-stepper-control. A form step is gated by validation or by a request, and the attribute moves the stepper on every click. See Advancing the Stepper above
  • Always provide a unique id on the stepper container so control buttons and the JS API can target it

Advancing the Stepper

There are three ways to move a stepper. Pick by one question: can anything refuse the move?

  • Nothing can refuse it — use data-stepper-control on a button. The click moves the stepper, always. This fits a Back button, a demo, a walkthrough, and a goto that starts the flow over. It needs no JavaScript.
  • Something can refuse it — call NDS.Stepper.next(id) from the code that knows the answer. Validation, a request, a server check: the thing that decides is the thing that moves the stepper. Every form step is this case.
  • The step number lives in your own state — write data-current on the .nds-stepper. The component watches the attribute and re-renders. This fits a server-rendered page or a framework view that already holds the step number.

The stepper is a progress display. It never validates, never blocks, and never sends a request. A submit-typed button inside a form is handed to that form untouched: the stepper does not cancel the submit and does not move. Move it yourself once the form reports success.

<!-- Back: nothing can refuse it --> <button type="button" class="nds-btn nds-secondary-outline" data-stepper-control="previous" data-stepper-target="myStepper"> <span class="nds-label">Back</span> </button> <!-- Continue: validation can refuse it --> <button type="button" class="nds-btn nds-primary" id="myContinue"> <span class="nds-label">Continue</span> </button> // Continue: move only if the visible step validates. validateForm() skips // fields inside a hidden panel, so it checks the step on screen. document.getElementById('myContinue').addEventListener('click', (e) => { if (NDS.Forms.validateForm(e.currentTarget).valid) NDS.Stepper.next('myStepper'); }); // Final step: move only once the request succeeds. The form carries data-ajax, // so Forms validates, stops the POST and fires nds:formValid. document.getElementById('myForm').addEventListener('nds:formValid', () => { sendApplication().then(() => NDS.Stepper.next('myStepper')); });

Modifier Classes

ClassApplies toDescription
nds-verticalContainerSwitches to top-to-bottom layout with vertical connecting lines
nds-radialContainerCircular progress indicator showing one step at a time
nds-dotContainerReplaces numbered circles with 16px dots (horizontal and vertical)
nds-xsContainer40px radial circle, 12px title, 10px description (radial only)
nds-smContainer48px radial circle, 14px title, 12px description (radial only)
nds-mdContainer64px radial circle, 16px title, 14px description. This is the radial default, so the class is only needed to override a size set higher up (radial only)
nds-lgContainer40px linear circle. Radial: 80px circle, 16px title, 14px description
nds-xlContainer48px linear circle. Radial: 120px circle, 20px title, 14px description
nds-centerContainerCenters step content beneath each circle (horizontal only)
nds-oncolorContainerAdapts colors for dark or branded backgrounds
nds-reverseContainerReverses vertical stepper direction so progress flows bottom-to-top (vertical only)
nds-cardViewContainerGives the step a card surface: padding, border, rounded corners, and card background. Vertical cards each step's content and lifts the box so its first row sits level with the circle. Radial cards the whole stepper, because it shows one step at a time beside the progress ring. Has no effect in the horizontal layout. Combine with nds-oncolor and the surface switches to the same translucent white a card uses on a colored background. Use it for timelines and history lists. Do not nest a card inside the step as well
nds-neutralContainerNeutral gray progress circle color (radial only)
nds-horizontal-sm / -md / -lgContainerForces horizontal layout on mobile / tablet / desktop respectively. Combine with other breakpoint-scoped variants to compose a responsive layout
nds-vertical-sm / -md / -lgContainerForces vertical layout on mobile / tablet / desktop respectively
nds-radial-sm / -md / -lgContainerForces radial layout on mobile / tablet / desktop respectively. Example: nds-radial-sm nds-vertical-lg = radial on mobile, horizontal on tablet, vertical on desktop
nds-loadingContainerRenders all steps as animated skeleton placeholders. Use while step data is being loaded. Equivalent to setting data-state="loading" on the root
nds-stepper-action.nds-stepper-contentFlex row container for action buttons (.nds-btn children) placed inside a step's content area. Each button stretches to fill equal width

Data Attributes

AttributeDescription
data-currentSet on the .nds-stepper container. The active step number (starting from 1). Updating this attribute triggers an automatic UI refresh.
data-totalSet on the .nds-stepper container. Total number of steps. Updated automatically on init but can be set manually.
data-step-textSet on .nds-stepper-circle. Overrides the auto-generated step number with custom text.
data-stepper-controlSet on any button. Values: next, previous, or goto. Buttons inside a stepper target their parent automatically. The move is unconditional: the click always moves the stepper. A submit-typed button inside a form is handed to that form instead, so the stepper neither cancels the submit nor moves — see Advancing the Stepper above.
data-stepper-targetSet on control buttons outside a stepper. The ID of the stepper to control.
data-stepper-valueSet on goto control buttons. The step number to navigate to.
data-state="completed"Set on .nds-stepper-step. Marks the step as completed with a checkmark icon. Managed automatically by JS.
data-state="current"Set on .nds-stepper-step. Marks the step as the active step. Managed automatically by JS.
data-state="upcoming"Set on .nds-stepper-step. Marks the step as a future step with muted styling. Managed automatically by JS.
data-state="loading"Set on the .nds-stepper container. Renders all steps as animated skeleton placeholders. Equivalent to adding nds-loading.

CSS Custom Properties

PropertyDefaultDescription
--stepper-size32pxCircle diameter for linear steppers. Overridden by size classes
--stepper-gapDerived from the layout and sizeSpacing between steps. Left unset it follows the layout: calc(var(--stepper-size) * 1.5) vertical, calc(var(--stepper-size) / 2) for a vertical nds-cardView without nds-dot, and the indicator gap scaled by the size classes when horizontal. Set it to override any of them
--stepper-indicator-gapvar(--spacing-md)Base spacing between steps that --stepper-gap derives from; the size classes scale it by 1.2 and 1.5
--divider-liftcalc(var(--stepper-size) / 4)Drops a label divider inside a step so its rule meets the centre of the step circle. Scales with the circle, so the size classes carry it. Set 0 to leave the divider where it falls
--stepper-content-widthvar(--paragraph-max-width)Maximum width of a step's content, so a step stays readable in a wide column and its text and any label divider end on the same edge. Set none on a step that holds something wide, such as a table or a full-bleed image
--stepper-text-paddingvar(--spacing-xl)Gap between the step indicator and its text block
--stepper-card-liftcalc(var(--stepper-size) / 4), or var(--stepper-size) with nds-dotHow far nds-cardView raises each card so its first row meets the circle (vertical only). Scales with the circle, so the size classes carry it. Set it when your content starts with something taller or shorter than a title
--gapvar(--spacing-xl)Vertical gap between the step's text block and any .nds-stepper-action buttons. Set on the .nds-stepper container
--stepper-title-FSSet by the size classFont size of the step title in a radial stepper. Radial text does not scale with the ring, so set this for a large title beside a small circle
--stepper-title-LHSet by the size classLine height of the step title in a radial stepper. Set it with --stepper-title-FS, so the pair stays matched
--stepper-description-FSSet by the size classFont size of the step description and the next-step line in a radial stepper. Works the same way as --stepper-title-FS
--stepper-description-LHSet by the size classLine height of the step description and the next-step line in a radial stepper
--progress-sizevar(--stepper-size)Circle diameter for radial steppers. Inherits from --stepper-size, which radial size classes override

JavaScript API

Steppers auto-initialize on page load. Access instances via NDS.Stepper.get(id) or call convenience methods directly. The nds:stepper:change event fires on every step change.

// ── Global convenience methods ────────────────────── NDS.Stepper.next('my-stepper'); // Advance to next step NDS.Stepper.previous('my-stepper'); // Go back one step NDS.Stepper.goTo('my-stepper', 3); // Jump to step 3 // ── Instance methods ──────────────────────────────── const stepper = NDS.Stepper.get('my-stepper'); stepper.next(); // Advance (marks last step completed on linear steppers) stepper.previous(); // Go back (un-completes last step if completed) stepper.goTo(2); // Jump to specific step stepper.destroy(); // Disconnect observer and clean up // ── Instance properties ───────────────────────────── stepper.current; // Current step number stepper.total; // Total number of steps stepper.progress; // Completion percentage (0 to 100) // ── Listen for step changes ───────────────────────── document.addEventListener('nds:stepper:change', (e) => { e.detail.currentStep; // Active step number e.detail.totalSteps; // Total steps e.detail.progressPercentage; // Completion percentage }); // ── Data attribute control (alternative) ──────────── // Updating data attributes triggers automatic UI refresh const el = document.getElementById('my-stepper'); el.dataset.current = '2'; // Moves to step 2 el.dataset.total = '5'; // Updates total steps // ── Create a stepper instance dynamically ─────────── // Stamps responsive layout + data-state + progress on the element, // then registers it so get(id) / control(id, …) work. const stepper = NDS.Stepper.create(el); // ── Fallback layout variant ────────────────────────── // The fallback is the authored variant used when no breakpoint-scoped // class (nds-{variant}-{sm|md|lg}) matches the current viewport. NDS.Stepper.getFallback('my-stepper'); // Returns 'horizontal' | 'vertical' | 'radial' NDS.Stepper.setFallback('my-stepper', 'vertical'); // Change at runtime // ── Re-initialize after dynamic HTML ──────────────── NDS.Stepper.init();
Last Modified Date: 28/08/2026 - 07:47 PM
Was this page useful?
60% of users said Yes from 2843 Feedbacks