Stepper
OrganismMulti-step progress indicator with complete, active, error, and pending states. Supports horizontal and vertical orientations.
-
Account
-
2
Profile
-
3
Payment
-
4
Confirm
<%- include('modules/ui/Stepper', {
steps: [
{ label: 'Account', state: 'complete' },
{ label: 'Profile', state: 'active' },
{ label: 'Payment', state: 'pending' },
{ label: 'Confirm', state: 'pending' },
]
}) %>
-
Create account
Enter your email and password.
-
Verify email
Check your inbox.
-
3
Set up profile
Add your name and photo.
-
4
Invite your team
Optional.
<%- include('modules/ui/Stepper', {
orientation: 'vertical',
steps: [
{ label: 'Create account', description: 'Enter your email and password.', state: 'complete' },
{ label: 'Verify email', description: 'Check your inbox.', state: 'complete' },
{ label: 'Set up profile', description: 'Add your name and photo.', state: 'active' },
{ label: 'Invite your team', description: 'Optional.', state: 'pending' },
]
}) %>
-
Upload
-
Validate
-
3
Process
<%- include('modules/ui/Stepper', {
steps: [
{ label: 'Upload', state: 'complete' },
{ label: 'Validate', state: 'error' },
{ label: 'Process', state: 'pending' },
]
}) %>
<%
var _steps = locals.steps || [];
var _orientation = locals.orientation || 'horizontal';
var _className = locals.className || '';
var stateStyles = {
complete: { circle: 'bg-success text-text-inverse border-success', text: 'text-text-primary', line: 'bg-success' },
active: { circle: 'bg-primary text-primary-fg border-primary', text: 'text-text-primary font-semibold', line: 'bg-border' },
error: { circle: 'bg-error text-text-inverse border-error', text: 'text-error-fg', line: 'bg-border' },
pending: { circle: 'bg-surface-base text-text-disabled border-border', text: 'text-text-disabled', line: 'bg-border' },
};
%>
<% if (_orientation === 'vertical') { %>
<ol class="flex flex-col gap-0<%= _className ? ' ' + _className : '' %>">
<% _steps.forEach(function (step, i) { %>
<%
var state = step.state || 'pending';
var s = stateStyles[state] || stateStyles.pending;
var isLast = i === _steps.length - 1;
%>
<li class="flex gap-3 items-start">
<div class="flex flex-col items-center shrink-0">
<div class="h-8 w-8 rounded-full border-2 flex items-center justify-center text-xs font-bold shrink-0 <%= s.circle %>"
aria-label="Step <%= i + 1 %>: <%= step.label %> — <%= state %>">
<% if (state === 'complete') { %><span class="w-3.5 h-3.5 inline-flex items-center justify-center"><i class="fa-solid fa-check" aria-hidden="true"></i></span>
<% } else if (state === 'error') { %><span class="w-3.5 h-3.5 inline-flex items-center justify-center"><i class="fa-solid fa-xmark" aria-hidden="true"></i></span>
<% } else { %><%= i + 1 %><% } %>
</div>
<% if (!isLast) { %><div class="w-0.5 flex-1 min-h-[2rem] mt-1 <%= s.line %>" aria-hidden="true"></div><% } %>
</div>
<div class="<%= isLast ? 'pb-0' : 'pb-6' %>">
<p class="text-sm <%= s.text %>"><%= step.label %></p>
<% if (step.description) { %><p class="text-xs text-text-secondary mt-0.5"><%= step.description %></p><% } %>
</div>
</li>
<% }); %>
</ol>
<% } else { %>
<ol class="flex items-center<%= _className ? ' ' + _className : '' %>">
<% _steps.forEach(function (step, i) { %>
<%
var state = step.state || 'pending';
var s = stateStyles[state] || stateStyles.pending;
var isLast = i === _steps.length - 1;
%>
<li class="flex items-center<%= !isLast ? ' flex-1' : '' %>">
<div class="flex flex-col items-center gap-1 shrink-0">
<div class="h-8 w-8 rounded-full border-2 flex items-center justify-center text-xs font-bold <%= s.circle %>"
aria-label="Step <%= i + 1 %>: <%= step.label %> — <%= state %>">
<% if (state === 'complete') { %><span class="w-3.5 h-3.5 inline-flex items-center justify-center"><i class="fa-solid fa-check" aria-hidden="true"></i></span>
<% } else if (state === 'error') { %><span class="w-3.5 h-3.5 inline-flex items-center justify-center"><i class="fa-solid fa-xmark" aria-hidden="true"></i></span>
<% } else { %><%= i + 1 %><% } %>
</div>
<div class="text-center">
<p class="text-xs whitespace-nowrap <%= s.text %>"><%= step.label %></p>
<% if (step.description) { %><p class="text-xs text-text-secondary"><%= step.description %></p><% } %>
</div>
</div>
<% if (!isLast) { %><div class="h-0.5 flex-1 mx-2 mt-[-1.25rem] <%= s.line %>" aria-hidden="true"></div><% } %>
</li>
<% }); %>
</ol>
<% } %>