Components

Steps

Shows progress through a sequence with composable steps, states, colors, and responsive orientations.

Preview

  1. 1 Register
  2. 2 Choose plan
  3. 3 Purchase
  4. 4 Receive product

More examples

Vertical and responsive

Use a vertical sequence or switch automatically from vertical on mobile to horizontal on larger screens.

Vertical

  1. Account
  2. Profile
  3. Preferences

Responsive

  1. 1 Draft
  2. 2 Review
  3. 3 Approve
  4. 4 Publish

Colors and custom indicators

Communicate success, warnings, and errors with semantic variants and snippet-based indicator content.

  1. Uploaded
  2. Processed
  3. Needs review
  4. ! Resolve issues

Installation

Install directly from the hosted registry with the shadcn-svelte CLI.

Terminal
npx shadcn-svelte@latest add https://ui.akcodeworks.com/r/steps.json

Usage

Compose Root and Item. Root controls horizontal, vertical, or responsive layout. Each Item accepts a pending, current, or complete state and an optional semantic color variant. Pass indicator as text, a number, or a snippet for icons and custom markup. The underlying ordered list preserves the process order for assistive technology.

API reference

PropTypeDefaultDescription
Root.directionhorizontal | vertical | responsivehorizontalSets the horizontal or vertical layout direction.
Item.statepending | current | completependingSets the semantic state of an individual item.
Item.variantprimary | secondary | accent | neutral | info | success | warning | destructiveprimarySelects the visual treatment for an individual item.
Item.indicatorstring | number | Snippet—Custom snippet rendered as the item indicator.
classstring—Tailwind CSS classes applied to the component root.
Item.indicatorClassstring—Tailwind CSS classes applied to the item indicator.
Item.labelClassstring—Tailwind CSS classes applied to the item label.
childrenSnippetrequiredContent rendered inside the component.

Source

These files are included in the registry item and copied into the consuming project.

steps.svelte
<script lang="ts">	import type { Snippet } from 'svelte';	import { setContext } from 'svelte';	import type { HTMLOlAttributes } from 'svelte/elements';	import { cn } from '$lib/utils.js';	import { STEPS_CONTEXT, type StepsDirection } from './steps-context';	let {		children,		direction = 'horizontal',		class: className,		...restProps	}: HTMLOlAttributes & {		children: Snippet;		direction?: StepsDirection;	} = $props();	setContext<() => StepsDirection>(STEPS_CONTEXT, () => direction);</script><ol	{...restProps}	data-slot="steps"	data-direction={direction}	class={cn(		'w-full',		{			'flex items-start overflow-x-auto': direction === 'horizontal',			'flex flex-col': direction === 'vertical',			'flex flex-col sm:flex-row sm:items-start sm:overflow-x-auto': direction === 'responsive'		},		className	)}>	{@render children()}</ol>
steps-item.svelte
<script lang="ts" module>	export type StepState = 'pending' | 'current' | 'complete';	export type StepVariant =		'primary' | 'secondary' | 'accent' | 'neutral' | 'info' | 'success' | 'warning' | 'destructive';</script><script lang="ts">	import type { Snippet } from 'svelte';	import { getContext } from 'svelte';	import type { HTMLLiAttributes } from 'svelte/elements';	import { cn } from '$lib/utils.js';	import { STEPS_CONTEXT, type StepsDirection } from './steps-context';	type Props = HTMLLiAttributes & {		children: Snippet;		state?: StepState;		variant?: StepVariant;		indicator?: string | number | Snippet;		indicatorClass?: string;		labelClass?: string;	};	let {		children,		state = 'pending',		variant = 'primary',		indicator,		indicatorClass,		labelClass,		class: className,		'aria-current': ariaCurrent,		...restProps	}: Props = $props();	const getDirection = getContext<() => StepsDirection>(STEPS_CONTEXT);	const direction = $derived(getDirection?.() ?? 'horizontal');	const indicatorColors: Record<StepVariant, string> = {		primary: 'border-primary bg-primary text-primary-foreground',		secondary: 'border-violet-500 bg-violet-500 text-white',		accent: 'border-cyan-400 bg-cyan-400 text-cyan-950',		neutral: 'border-zinc-700 bg-zinc-700 text-white',		info: 'border-sky-500 bg-sky-500 text-white',		success: 'border-emerald-500 bg-emerald-500 text-white',		warning: 'border-amber-400 bg-amber-400 text-amber-950',		destructive: 'border-red-500 bg-red-500 text-white'	};	const beforeColors: Record<StepVariant, string> = {		primary: 'before:bg-primary',		secondary: 'before:bg-violet-500',		accent: 'before:bg-cyan-400',		neutral: 'before:bg-zinc-700',		info: 'before:bg-sky-500',		success: 'before:bg-emerald-500',		warning: 'before:bg-amber-400',		destructive: 'before:bg-red-500'	};	const afterColors: Record<StepVariant, string> = {		primary: 'after:bg-primary',		secondary: 'after:bg-violet-500',		accent: 'after:bg-cyan-400',		neutral: 'after:bg-zinc-700',		info: 'after:bg-sky-500',		success: 'after:bg-emerald-500',		warning: 'after:bg-amber-400',		destructive: 'after:bg-red-500'	};</script><li	{...restProps}	data-slot="step"	data-state={state}	data-variant={variant}	aria-current={ariaCurrent ?? (state === 'current' ? 'step' : undefined)}	class={cn(		'relative grid list-none before:absolute before:z-0 before:bg-border after:absolute after:z-0 after:bg-border',		{			'min-w-28 flex-1 grid-rows-[2rem_auto] justify-items-center gap-y-2 px-2 text-center before:top-[0.9375rem] before:left-0 before:h-0.5 before:w-1/2 after:top-[0.9375rem] after:right-0 after:h-0.5 after:w-1/2 first:before:hidden last:after:hidden':				direction === 'horizontal',			'min-h-16 grid-cols-[2rem_minmax(0,1fr)] items-start gap-x-3 text-left before:top-0 before:left-[0.9375rem] before:h-4 before:w-0.5 after:top-4 after:bottom-0 after:left-[0.9375rem] after:w-0.5 first:before:hidden last:after:hidden':				direction === 'vertical',			'min-h-16 grid-cols-[2rem_minmax(0,1fr)] items-start gap-x-3 text-left before:top-0 before:left-[0.9375rem] before:h-4 before:w-0.5 after:top-4 after:bottom-0 after:left-[0.9375rem] after:w-0.5 first:before:hidden last:after:hidden sm:min-h-0 sm:min-w-28 sm:flex-1 sm:grid-cols-1 sm:grid-rows-[2rem_auto] sm:justify-items-center sm:gap-x-0 sm:gap-y-2 sm:px-2 sm:text-center sm:before:top-[0.9375rem] sm:before:left-0 sm:before:h-0.5 sm:before:w-1/2 sm:after:top-[0.9375rem] sm:after:right-0 sm:after:bottom-auto sm:after:left-auto sm:after:h-0.5 sm:after:w-1/2':				direction === 'responsive'		},		state !== 'pending' && beforeColors[variant],		state === 'complete' && afterColors[variant],		className	)}>	<span		data-slot="step-indicator"		class={cn(			'relative z-10 col-start-1 row-start-1 inline-flex size-8 items-center justify-center rounded-full border-2 text-xs font-semibold',			state === 'pending'				? 'border-border bg-background text-muted-foreground'				: indicatorColors[variant],			state === 'current' && 'ring-4 ring-ring/20',			indicatorClass		)}	>		{#if typeof indicator === 'function'}			{@render indicator()}		{:else if indicator !== undefined}			{indicator}		{:else if state === 'complete'}			<span aria-hidden="true">✓</span>		{:else}			<span class="size-1.5 rounded-full bg-current" aria-hidden="true"></span>		{/if}	</span>	<span		data-slot="step-label"		class={cn(			'col-start-2 row-start-1 pt-1.5 text-sm sm:col-start-auto',			state === 'pending' ? 'text-muted-foreground' : 'font-medium text-foreground',			(direction === 'horizontal' || direction === 'responsive') &&				'row-start-2 pt-0 sm:col-start-1',			direction === 'responsive' && 'col-start-2 row-start-1 pt-1.5 sm:row-start-2 sm:pt-0',			labelClass		)}	>		{@render children()}	</span></li>
steps-context.ts
export type StepsDirection = 'horizontal' | 'vertical' | 'responsive';export const STEPS_CONTEXT = Symbol('codeworks-ui:steps');
index.ts
import Root from './steps.svelte';import Item from './steps-item.svelte';export { Root, Item };export { Root as Steps, Item as Step };export type { StepsDirection } from './steps-context';export type { StepState, StepVariant } from './steps-item.svelte';