Stepper
A component for displaying steps in a process or wizard
Orientations
Horizontal and vertical layout options.
1
2<script lang="ts">
3 import { Button, CardPrimitives, Stepper } from "@kareyes/aether";
4
5 const { Card, CardHeader, CardTitle, CardDescription, CardContent } = CardPrimitives;
6
7 let basicStep = $state(1);
8 let verticalStep = $state(0);
9</script>
10
11<div class="grid gap-8 lg:grid-cols-2">
12 <Card>
13 <CardHeader>
14 <CardTitle>Horizontal Stepper</CardTitle>
15 <CardDescription>Default layout, perfect for wide screens</CardDescription>
16 </CardHeader>
17 <CardContent class="space-y-6">
18 <Stepper
19 bind:activeStep={basicStep}
20 steps={[
21 { label: "Step 1", description: "Choose a plan" },
22 { label: "Step 2", description: "Enter details" },
23 { label: "Step 3", description: "Confirm" },
24 ]}
25 />
26 <div class="flex gap-3">
27 <Button variant="outline" disabled={basicStep === 0} onclick={() => basicStep--}>
28 Previous
29 </Button>
30 <Button disabled={basicStep === 2} onclick={() => basicStep++}>
31 Next
32 </Button>
33 </div>
34 </CardContent>
35 </Card>
36
37 <Card>
38 <CardHeader>
39 <CardTitle>Vertical Stepper</CardTitle>
40 <CardDescription>Great for mobile and detailed steps</CardDescription>
41 </CardHeader>
42 <CardContent class="space-y-6">
43 <Stepper
44 orientation="vertical"
45 bind:activeStep={verticalStep}
46 steps={[
47 { label: "Personal Info", description: "Enter your basic information" },
48 { label: "Address", description: "Add your address details" },
49 { label: "Review", description: "Review and submit" },
50 ]}
51 />
52 <div class="flex gap-3">
53 <Button variant="outline" disabled={verticalStep === 0} onclick={() => verticalStep--}>
54 Previous
55 </Button>
56 <Button disabled={verticalStep === 2} onclick={() => verticalStep++}>
57 Next
58 </Button>
59 </div>
60 </CardContent>
61 </Card>
62</div>Sizes & Variants
Different sizes and visual styles.
1
2<script lang="ts">
3 import { CardPrimitives, Stepper } from "@kareyes/aether";
4
5 const { Card, CardHeader, CardTitle, CardDescription, CardContent } = CardPrimitives;
6</script>
7
8<div class="grid gap-6">
9 <Card>
10 <CardHeader><CardTitle>Extra Small Size</CardTitle></CardHeader>
11 <CardContent>
12 <Stepper size="xs" activeStep={1} steps={[
13 { label: "Start" },
14 { label: "Progress" },
15 { label: "Complete" },
16 ]} />
17 </CardContent>
18 </Card>
19
20 <Card>
21 <CardHeader><CardTitle>Small Size</CardTitle></CardHeader>
22 <CardContent>
23 <Stepper size="sm" activeStep={1} steps={[
24 { label: "Start" },
25 { label: "Progress" },
26 { label: "Complete" },
27 ]} />
28 </CardContent>
29 </Card>
30
31 <Card>
32 <CardHeader><CardTitle>Default Size</CardTitle></CardHeader>
33 <CardContent>
34 <Stepper size="default" activeStep={1} steps={[
35 { label: "Start" },
36 { label: "Progress" },
37 { label: "Complete" },
38 ]} />
39 </CardContent>
40 </Card>
41
42 <Card>
43 <CardHeader><CardTitle>Large Size</CardTitle></CardHeader>
44 <CardContent>
45 <Stepper size="lg" activeStep={1} steps={[
46 { label: "Start" },
47 { label: "Progress" },
48 { label: "Complete" },
49 ]} />
50 </CardContent>
51 </Card>
52
53 <Card>
54 <CardHeader><CardTitle>Outline Variant</CardTitle></CardHeader>
55 <CardContent>
56 <Stepper variant="outline" activeStep={1} steps={[
57 { label: "Start", description: "Begin process" },
58 { label: "Progress", description: "In progress" },
59 { label: "Complete", description: "Finish" },
60 ]} />
61 </CardContent>
62 </Card>
63
64 <Card>
65 <CardHeader><CardTitle>Ghost Variant</CardTitle></CardHeader>
66 <CardContent>
67 <Stepper variant="ghost" activeStep={1} steps={[
68 { label: "Start", description: "Begin process" },
69 { label: "Progress", description: "In progress" },
70 { label: "Complete", description: "Finish" },
71 ]} />
72 </CardContent>
73 </Card>
74
75 <Card>
76 <CardHeader>
77 <CardTitle>Flat Variant</CardTitle>
78 <CardDescription>Horizontal progress bar style</CardDescription>
79 </CardHeader>
80 <CardContent class="pt-8 mt-8">
81 <Stepper variant="flat" activeStep={1} steps={[
82 { label: "Personal Info", description: "Completed" },
83 { label: "Education", description: "Completed" },
84 { label: "Company", description: "Pending" },
85 { label: "Testing", description: "Pending" },
86 { label: "Review", description: "Pending" },
87 ]} />
88 </CardContent>
89 </Card>
90</div>Interactive Features
Clickable steps and custom icons.
1
2<script lang="ts">
3 import { CardPrimitives, Stepper } from "@kareyes/aether";
4 import { User, CreditCard, CircleCheck } from "@lucide/svelte";
5
6 const { Card, CardHeader, CardTitle, CardDescription, CardContent } = CardPrimitives;
7
8 let clickableStep = $state(1);
9 let result = $state("");
10</script>
11
12<div class="grid gap-6">
13 <Card>
14 <CardHeader>
15 <CardTitle>Clickable Steps</CardTitle>
16 <CardDescription>Click on any step to navigate directly</CardDescription>
17 </CardHeader>
18 <CardContent>
19 <Stepper
20 clickable
21 bind:activeStep={clickableStep}
22 onStepClick={(step: number) => result = `Clicked step ${step + 1}`}
23 steps={[
24 { label: "Step 1", description: "First step" },
25 { label: "Step 2", description: "Second step" },
26 { label: "Step 3", description: "Third step" },
27 ]}
28 />
29 </CardContent>
30 </Card>
31
32 <Card>
33 <CardHeader>
34 <CardTitle>With Custom Icons</CardTitle>
35 <CardDescription>Pass icon components via the steps array</CardDescription>
36 </CardHeader>
37 <CardContent>
38 <Stepper
39 activeStep={1}
40 steps={[
41 { label: "Account", description: "Create account", icon: User },
42 { label: "Payment", description: "Add payment", icon: CreditCard },
43 { label: "Done", description: "All set!", icon: CircleCheck },
44 ]}
45 />
46 </CardContent>
47 </Card>
48
49 <Card>
50 <CardHeader>
51 <CardTitle>Numbers Only</CardTitle>
52 <CardDescription>Minimal stepper with just numbers</CardDescription>
53 </CardHeader>
54 <CardContent>
55 <Stepper activeStep={2} steps={[{}, {}, {}, {}]} />
56 </CardContent>
57 </Card>
58</div>Step States
Per-step error, loading, and disabled states override the normal completed/active/inactive appearance. Priority: disabled β error β loading β completed β active β inactive.
1
2<script lang="ts">
3 import { CardPrimitives, Stepper } from "@kareyes/aether";
4 import { ShoppingCart, CreditCard, CircleCheck } from "@lucide/svelte";
5
6 const { Card, CardHeader, CardTitle, CardDescription, CardContent } = CardPrimitives;
7</script>
8
9<div class="grid gap-6">
10 <Card>
11 <CardHeader>
12 <CardTitle>Error State</CardTitle>
13 <CardDescription>Renders a red X icon with destructive colors</CardDescription>
14 </CardHeader>
15 <CardContent>
16 <Stepper
17 activeStep={1}
18 steps={[
19 { label: "Upload", description: "File uploaded" },
20 { label: "Validate", description: "Checksum failed", error: true },
21 { label: "Process", description: "Waiting" },
22 ]}
23 />
24 </CardContent>
25 </Card>
26
27 <Card>
28 <CardHeader>
29 <CardTitle>Loading State</CardTitle>
30 <CardDescription>Renders a spinning loader with active colors</CardDescription>
31 </CardHeader>
32 <CardContent>
33 <Stepper
34 activeStep={1}
35 steps={[
36 { label: "Upload", description: "Done" },
37 { label: "Processing", description: "Converting fileβ¦", loading: true },
38 { label: "Complete", description: "Waiting" },
39 ]}
40 />
41 </CardContent>
42 </Card>
43
44 <Card>
45 <CardHeader>
46 <CardTitle>Disabled Steps</CardTitle>
47 <CardDescription>Grayed out and not clickable even when the stepper is clickable</CardDescription>
48 </CardHeader>
49 <CardContent>
50 <Stepper
51 activeStep={1}
52 clickable
53 steps={[
54 { label: "Starter", description: "Current plan" },
55 { label: "Pro", description: "Active step" },
56 { label: "Enterprise", description: "Contact sales", disabled: true },
57 { label: "Custom", description: "Contact sales", disabled: true },
58 ]}
59 />
60 </CardContent>
61 </Card>
62
63 <Card>
64 <CardHeader>
65 <CardTitle>Mixed States</CardTitle>
66 <CardDescription>error, loading, disabled, and normal steps together</CardDescription>
67 </CardHeader>
68 <CardContent>
69 <Stepper
70 activeStep={2}
71 variant="ghost"
72 steps={[
73 { label: "Upload", description: "Done" },
74 { label: "Validate", description: "Checksum error", error: true },
75 { label: "Process", description: "Convertingβ¦", loading: true },
76 { label: "Review", description: "Pending" },
77 { label: "Deploy", description: "Restricted", disabled: true },
78 ]}
79 />
80 </CardContent>
81 </Card>
82
83 <Card>
84 <CardHeader>
85 <CardTitle>Icons + States</CardTitle>
86 <CardDescription>Custom icons still render in inactive/completed β error and loading override them</CardDescription>
87 </CardHeader>
88 <CardContent>
89 <Stepper
90 activeStep={1}
91 steps={[
92 { label: "Cart", description: "Items ready", icon: ShoppingCart },
93 { label: "Payment", description: "Card declined", icon: CreditCard, error: true },
94 { label: "Confirm", description: "Waiting", icon: CircleCheck, disabled: true },
95 ]}
96 />
97 </CardContent>
98 </Card>
99</div>Circular Progress Mode
mobileLayout="circular" β below md (768px) the step list is replaced by a radial progress ring. Desktop stays horizontal. Resize the browser to see the switch.
1
2<script lang="ts">
3 import { CardPrimitives, Stepper } from "@kareyes/aether";
4 import { ShoppingCart, CreditCard, CircleCheck } from "@lucide/svelte";
5
6 const { Card, CardHeader, CardTitle, CardDescription, CardContent } = CardPrimitives;
7
8 let circularStep = $state(0);
9 let circularAlwaysStep = $state(1);
10</script>
11
12<div class="grid gap-6">
13 <Card>
14 <CardHeader>
15 <CardTitle>Basic Circular (resize to see)</CardTitle>
16 <CardDescription>Switches to ring below md breakpoint</CardDescription>
17 </CardHeader>
18 <CardContent>
19 <Stepper
20 bind:activeStep={circularStep}
21 clickable
22 mobileLayout="circular"
23 steps={[
24 { label: "Account", description: "Create your account" },
25 { label: "Profile", description: "Complete your profile" },
26 { label: "Billing", description: "Add a payment method" },
27 { label: "Review", description: "Confirm your details" },
28 { label: "Done", description: "All set!" },
29 ]}
30 />
31 </CardContent>
32 </Card>
33
34 <Card>
35 <CardHeader>
36 <CardTitle>Circular Always</CardTitle>
37 <CardDescription>circularAlways β ring visible at all breakpoints</CardDescription>
38 </CardHeader>
39 <CardContent>
40 <Stepper
41 bind:activeStep={circularAlwaysStep}
42 clickable
43 mobileLayout="circular"
44 circularAlways
45 steps={[
46 { label: "Plan", description: "Define scope" },
47 { label: "Build", description: "Implement features" },
48 { label: "Test", description: "QA and review" },
49 { label: "Ship", description: "Deploy to production" },
50 ]}
51 />
52 </CardContent>
53 </Card>
54
55 <Card>
56 <CardHeader>
57 <CardTitle>Large Ring (circularSize=200)</CardTitle>
58 <CardDescription>Custom ring diameter</CardDescription>
59 </CardHeader>
60 <CardContent>
61 <Stepper
62 mobileLayout="circular"
63 circularAlways
64 circularSize={200}
65 activeStep={1}
66 steps={[
67 { label: "Research", description: "Gather requirements" },
68 { label: "Design", description: "Create wireframes" },
69 { label: "Implement", description: "Write the code" },
70 { label: "Launch", description: "Go live π" },
71 ]}
72 />
73 </CardContent>
74 </Card>
75
76 <Card>
77 <CardHeader>
78 <CardTitle>Non-expandable Ring</CardTitle>
79 <CardDescription>circularExpandable=false β no drawer on click</CardDescription>
80 </CardHeader>
81 <CardContent>
82 <Stepper
83 activeStep={1}
84 mobileLayout="circular"
85 circularAlways
86 circularExpandable={false}
87 steps={[
88 { label: "Step 1", description: "First step" },
89 { label: "Step 2", description: "Second step" },
90 { label: "Step 3", description: "Third step" },
91 { label: "Step 4", description: "Final step" },
92 ]}
93 />
94 </CardContent>
95 </Card>
96</div>Practical Examples
Real-world use cases and implementations.
1
2<script lang="ts">
3 import { Button, Input, Label, Textarea, CardPrimitives, Stepper } from "@kareyes/aether";
4 import { User, CreditCard, CircleCheck, ShoppingCart, Package, Mail, Briefcase, FileText } from "@lucide/svelte";
5
6 const { Card, CardHeader, CardTitle, CardDescription, CardContent } = CardPrimitives;
7
8 // Registration flow
9 let registrationStep = $state(0);
10 let registrationData = $state({ email: "", password: "", name: "", phone: "", address: "" });
11
12 function nextRegistrationStep() { if (registrationStep < 2) registrationStep++; }
13 function previousRegistrationStep() { if (registrationStep > 0) registrationStep--; }
14
15 // Checkout flow
16 let checkoutStep = $state(1);
17 let checkoutData = $state({ items: 3, total: "$127.50", cardNumber: "", shippingMethod: "" });
18
19 function nextCheckoutStep() { if (checkoutStep < 3) checkoutStep++; }
20 function previousCheckoutStep() { if (checkoutStep > 0) checkoutStep--; }
21
22 // Job application flow
23 let applicationStep = $state(0);
24 let applicationData = $state({ fullName: "", email: "", position: "", experience: "", education: "" });
25
26 function nextApplicationStep() { if (applicationStep < 2) applicationStep++; }
27 function previousApplicationStep() { if (applicationStep > 0) applicationStep--; }
28</script>
29
30<div class="grid gap-6">
31 <!-- Registration Flow -->
32 <Card>
33 <CardHeader>
34 <CardTitle>Registration Flow</CardTitle>
35 <CardDescription>Multi-step account creation process</CardDescription>
36 </CardHeader>
37 <CardContent class="space-y-6">
38 <Stepper
39 bind:activeStep={registrationStep}
40 steps={[
41 { label: "Account", description: "Create your account", icon: Mail },
42 { label: "Profile", description: "Personal information", icon: User },
43 { label: "Complete", description: "Review and finish", icon: CircleCheck },
44 ]}
45 />
46
47 <div class="min-h-[200px]">
48 {#if registrationStep === 0}
49 <div class="space-y-4">
50 <h3 class="text-lg font-semibold">Create Your Account</h3>
51 <div class="space-y-2">
52 <Label for="reg-email">Email</Label>
53 <Input id="reg-email" type="email" bind:value={registrationData.email} placeholder="john@example.com" />
54 </div>
55 <div class="space-y-2">
56 <Label for="reg-password">Password</Label>
57 <Input id="reg-password" type="password" bind:value={registrationData.password} placeholder="β’β’β’β’β’β’β’β’" />
58 </div>
59 </div>
60 {:else if registrationStep === 1}
61 <div class="space-y-4">
62 <h3 class="text-lg font-semibold">Personal Information</h3>
63 <div class="space-y-2">
64 <Label for="reg-name">Full Name</Label>
65 <Input id="reg-name" bind:value={registrationData.name} placeholder="John Doe" />
66 </div>
67 <div class="space-y-2">
68 <Label for="reg-phone">Phone Number</Label>
69 <Input id="reg-phone" bind:value={registrationData.phone} placeholder="+1 (555) 000-0000" />
70 </div>
71 </div>
72 {:else}
73 <div class="space-y-4">
74 <h3 class="text-lg font-semibold">Review Your Information</h3>
75 <dl class="space-y-3">
76 <div><dt class="font-medium">Email:</dt><dd class="text-muted-foreground">{registrationData.email || "Not provided"}</dd></div>
77 <div><dt class="font-medium">Name:</dt><dd class="text-muted-foreground">{registrationData.name || "Not provided"}</dd></div>
78 <div><dt class="font-medium">Phone:</dt><dd class="text-muted-foreground">{registrationData.phone || "Not provided"}</dd></div>
79 </dl>
80 </div>
81 {/if}
82 </div>
83
84 <div class="flex gap-3">
85 {#if registrationStep > 0}
86 <Button variant="outline" onclick={previousRegistrationStep}>Previous</Button>
87 {/if}
88 {#if registrationStep < 2}
89 <Button onclick={nextRegistrationStep}>Next</Button>
90 {:else}
91 <Button>Complete Registration</Button>
92 {/if}
93 </div>
94 </CardContent>
95 </Card>
96
97 <!-- Checkout Process -->
98 <Card>
99 <CardHeader>
100 <CardTitle>Checkout Process</CardTitle>
101 <CardDescription>E-commerce checkout flow with vertical stepper</CardDescription>
102 </CardHeader>
103 <CardContent class="space-y-6">
104 <div class="grid gap-6 lg:grid-cols-[300px_1fr]">
105 <Stepper
106 orientation="vertical"
107 bind:activeStep={checkoutStep}
108 size="sm"
109 steps={[
110 { label: "Cart", description: "Review items", icon: ShoppingCart },
111 { label: "Payment", description: "Payment details", icon: CreditCard },
112 { label: "Shipping", description: "Shipping method", icon: Package },
113 { label: "Confirm", description: "Place order", icon: CircleCheck },
114 ]}
115 />
116
117 <div class="min-h-[200px]">
118 {#if checkoutStep === 0}
119 <div class="space-y-4">
120 <h3 class="text-lg font-semibold">Your Cart</h3>
121 <p class="text-sm text-muted-foreground">You have {checkoutData.items} items in your cart</p>
122 <div class="text-2xl font-bold">{checkoutData.total}</div>
123 </div>
124 {:else if checkoutStep === 1}
125 <div class="space-y-4">
126 <h3 class="text-lg font-semibold">Payment Information</h3>
127 <div class="space-y-2">
128 <Label for="card-number">Card Number</Label>
129 <Input id="card-number" bind:value={checkoutData.cardNumber} placeholder="1234 5678 9012 3456" />
130 </div>
131 <div class="grid grid-cols-2 gap-4">
132 <div class="space-y-2">
133 <Label for="expiry">Expiry Date</Label>
134 <Input id="expiry" placeholder="MM/YY" />
135 </div>
136 <div class="space-y-2">
137 <Label for="cvv">CVV</Label>
138 <Input id="cvv" placeholder="123" />
139 </div>
140 </div>
141 </div>
142 {:else if checkoutStep === 2}
143 <div class="space-y-4">
144 <h3 class="text-lg font-semibold">Shipping Method</h3>
145 <div class="space-y-2">
146 <label class="flex items-center gap-3 rounded-lg border p-4 cursor-pointer hover:bg-muted">
147 <input type="radio" name="shipping" value="standard" bind:group={checkoutData.shippingMethod} />
148 <div>
149 <div class="font-medium">Standard Shipping</div>
150 <div class="text-sm text-muted-foreground">5-7 business days - Free</div>
151 </div>
152 </label>
153 <label class="flex items-center gap-3 rounded-lg border p-4 cursor-pointer hover:bg-muted">
154 <input type="radio" name="shipping" value="express" bind:group={checkoutData.shippingMethod} />
155 <div>
156 <div class="font-medium">Express Shipping</div>
157 <div class="text-sm text-muted-foreground">2-3 business days - $12.99</div>
158 </div>
159 </label>
160 </div>
161 </div>
162 {:else}
163 <div class="space-y-4">
164 <h3 class="text-lg font-semibold">Review Order</h3>
165 <dl class="space-y-2">
166 <div class="flex justify-between"><dt>Items:</dt><dd>{checkoutData.items}</dd></div>
167 <div class="flex justify-between"><dt>Subtotal:</dt><dd>{checkoutData.total}</dd></div>
168 <div class="flex justify-between"><dt>Shipping:</dt><dd>{checkoutData.shippingMethod === "express" ? "$12.99" : "Free"}</dd></div>
169 <div class="flex justify-between text-lg font-bold border-t pt-2">
170 <dt>Total:</dt>
171 <dd>{checkoutData.shippingMethod === "express" ? "$140.49" : checkoutData.total}</dd>
172 </div>
173 </dl>
174 </div>
175 {/if}
176 </div>
177 </div>
178
179 <div class="flex gap-3">
180 {#if checkoutStep > 0}
181 <Button variant="outline" onclick={previousCheckoutStep}>Previous</Button>
182 {/if}
183 {#if checkoutStep < 3}
184 <Button onclick={nextCheckoutStep}>Continue</Button>
185 {:else}
186 <Button>Place Order</Button>
187 {/if}
188 </div>
189 </CardContent>
190 </Card>
191
192 <!-- Job Application -->
193 <Card>
194 <CardHeader>
195 <CardTitle>Job Application</CardTitle>
196 <CardDescription>Multi-step job application form</CardDescription>
197 </CardHeader>
198 <CardContent class="space-y-6">
199 <Stepper
200 bind:activeStep={applicationStep}
201 clickable
202 steps={[
203 { label: "Personal", description: "Basic info", icon: User },
204 { label: "Experience", description: "Work history", icon: Briefcase },
205 { label: "Review", description: "Submit", icon: FileText },
206 ]}
207 />
208
209 <div class="min-h-[200px]">
210 {#if applicationStep === 0}
211 <div class="space-y-4">
212 <h3 class="text-lg font-semibold">Personal Information</h3>
213 <div class="grid gap-4 sm:grid-cols-2">
214 <div class="space-y-2">
215 <Label for="app-name">Full Name</Label>
216 <Input id="app-name" bind:value={applicationData.fullName} />
217 </div>
218 <div class="space-y-2">
219 <Label for="app-email">Email</Label>
220 <Input id="app-email" type="email" bind:value={applicationData.email} />
221 </div>
222 </div>
223 <div class="space-y-2">
224 <Label for="app-position">Position Applied For</Label>
225 <Input id="app-position" bind:value={applicationData.position} />
226 </div>
227 </div>
228 {:else if applicationStep === 1}
229 <div class="space-y-4">
230 <h3 class="text-lg font-semibold">Experience & Education</h3>
231 <div class="space-y-2">
232 <Label for="app-experience">Work Experience</Label>
233 <Textarea id="app-experience" bind:value={applicationData.experience} rows={4} />
234 </div>
235 <div class="space-y-2">
236 <Label for="app-education">Education</Label>
237 <Textarea id="app-education" bind:value={applicationData.education} rows={3} />
238 </div>
239 </div>
240 {:else}
241 <div class="space-y-4">
242 <h3 class="text-lg font-semibold">Review Application</h3>
243 <dl class="space-y-2">
244 <div><dt class="font-medium">Name:</dt><dd class="text-muted-foreground">{applicationData.fullName || "Not provided"}</dd></div>
245 <div><dt class="font-medium">Email:</dt><dd class="text-muted-foreground">{applicationData.email || "Not provided"}</dd></div>
246 <div><dt class="font-medium">Position:</dt><dd class="text-muted-foreground">{applicationData.position || "Not provided"}</dd></div>
247 </dl>
248 </div>
249 {/if}
250 </div>
251
252 <div class="flex gap-3">
253 {#if applicationStep > 0}
254 <Button variant="outline" onclick={previousApplicationStep}>Previous</Button>
255 {/if}
256 {#if applicationStep < 2}
257 <Button onclick={nextApplicationStep}>Next</Button>
258 {:else}
259 <Button>Submit Application</Button>
260 {/if}
261 </div>
262 </CardContent>
263 </Card>
264</div>
265
266
267
268
269Custom Label & Description
Using children snippet for custom step content.
1
2<script lang="ts">
3 import { CardPrimitives, StepperPrimitives } from "@kareyes/aether";
4 import { User, Settings, CircleCheck, Package, ShoppingCart, MapPin, FileText, Briefcase, GraduationCap } from "@kareyes/aether/icons";
5
6 const { StepperRoot, StepperStep, StepperSeparator } = StepperPrimitives;
7 const { Card, CardHeader, CardTitle, CardDescription, CardContent } = CardPrimitives;
8</script>
9
10<Card>
11 <CardHeader>
12 <CardTitle>Custom Rich Content</CardTitle>
13 <CardDescription>Use StepperRoot for per-step children snippets and rich content</CardDescription>
14 </CardHeader>
15 <CardContent class="space-y-6">
16 <StepperRoot activeStep={1}>
17 <StepperStep step={0}>
18 {#snippet icon()}
19 <User class="size-4" />
20 {/snippet}
21 <div class="space-y-1">
22 <div class="font-medium">Account Setup</div>
23 <div class="text-sm text-muted-foreground">Create your credentials</div>
24 </div>
25 </StepperStep>
26 <StepperSeparator completed />
27 <StepperStep step={1}>
28 {#snippet icon()}
29 <Settings class="size-4" />
30 {/snippet}
31 <div class="space-y-1">
32 <div class="font-medium flex items-center gap-2">
33 Configuration
34 <span class="text-xs bg-primary/10 text-primary px-2 py-0.5 rounded-full">Active</span>
35 </div>
36 <div class="text-sm text-muted-foreground">Customize your settings</div>
37 </div>
38 </StepperStep>
39 <StepperSeparator />
40 <StepperStep step={2}>
41 {#snippet icon()}
42 <CircleCheck class="size-4" />
43 {/snippet}
44 <div class="space-y-1">
45 <div class="font-medium">Completion</div>
46 <div class="text-sm text-muted-foreground">Review and launch</div>
47 </div>
48 </StepperStep>
49 </StepperRoot>
50 </CardContent>
51</Card>
52 <Card>
53 <CardHeader>
54 <CardTitle>Custom Styling</CardTitle>
55 <CardDescription>Apply custom styles and components within step content</CardDescription>
56 </CardHeader>
57 <CardContent class="space-y-6">
58 <div class="max-w-md">
59 <StepperRoot orientation="vertical" activeStep={1} size="sm">
60 <StepperStep step={0}>
61 {#snippet icon()}
62 <Package class="size-3" />
63 {/snippet}
64 <div class="space-y-2 pb-4">
65 <div class="font-semibold text-base">Order Placed</div>
66 <div class="text-sm text-muted-foreground">
67 Your order <span class="font-mono text-foreground">#ORD-12345</span> has been confirmed
68 </div>
69 <div class="text-xs text-muted-foreground">Jan 14, 2026 β’ 10:30 AM</div>
70 </div>
71 </StepperStep>
72 <StepperSeparator completed />
73 <StepperStep step={1}>
74 {#snippet icon()}
75 <ShoppingCart class="size-3" />
76 {/snippet}
77 <div class="space-y-2 pb-4">
78 <div class="font-semibold text-base flex items-center gap-2">
79 Processing
80 <span class="inline-flex h-2 w-2 rounded-full bg-primary animate-pulse"></span>
81 </div>
82 <div class="text-sm text-muted-foreground">Your order is being prepared for shipment</div>
83 <div class="text-xs text-muted-foreground">Estimated: 2-3 hours</div>
84 </div>
85 </StepperStep>
86 <StepperSeparator />
87 <StepperStep step={2}>
88 {#snippet icon()}
89 <MapPin class="size-3" />
90 {/snippet}
91 <div class="space-y-2">
92 <div class="font-semibold text-base">Out for Delivery</div>
93 <div class="text-sm text-muted-foreground">Package will arrive at your address</div>
94 <div class="text-xs text-muted-foreground">Pending</div>
95 </div>
96 </StepperStep>
97 </StepperRoot>
98 </div>
99 </CardContent>
100 </Card>
101
102 <div class="grid gap-6 md:grid-cols-2">
103 <Card>
104 <CardHeader>
105 <CardTitle>With Status Badges</CardTitle>
106 </CardHeader>
107 <CardContent>
108 <StepperRoot activeStep={1} variant="outline">
109 <StepperStep step={0}>
110 {#snippet icon()}
111 <FileText class="size-4" />
112 {/snippet}
113 <div class="space-y-1">
114 <div class="font-medium text-sm">Draft</div>
115 <div class="text-xs text-green-600 dark:text-green-400">β Completed</div>
116 </div>
117 </StepperStep>
118 <StepperSeparator completed />
119 <StepperStep step={1}>
120 {#snippet icon()}
121 <User class="size-4" />
122 {/snippet}
123 <div class="space-y-1">
124 <div class="font-medium text-sm">Review</div>
125 <div class="text-xs text-blue-600 dark:text-blue-400">β³ In Progress</div>
126 </div>
127 </StepperStep>
128 <StepperSeparator />
129 <StepperStep step={2}>
130 {#snippet icon()}
131 <CircleCheck class="size-4" />
132 {/snippet}
133 <div class="space-y-1">
134 <div class="font-medium text-sm">Publish</div>
135 <div class="text-xs text-muted-foreground">β Pending</div>
136 </div>
137 </StepperStep>
138 </StepperRoot>
139 </CardContent>
140 </Card>
141
142 <Card>
143 <CardHeader>
144 <CardTitle>With Time Estimates</CardTitle>
145 </CardHeader>
146 <CardContent>
147 <StepperRoot activeStep={0} variant="ghost">
148 <StepperStep step={0}>
149 {#snippet icon()}
150 <Briefcase class="size-4" />
151 {/snippet}
152 <div class="space-y-0.5">
153 <div class="font-medium text-sm">Interview</div>
154 <div class="text-xs text-muted-foreground">~30 minutes</div>
155 </div>
156 </StepperStep>
157 <StepperSeparator />
158 <StepperStep step={1}>
159 {#snippet icon()}
160 <GraduationCap class="size-4" />
161 {/snippet}
162 <div class="space-y-0.5">
163 <div class="font-medium text-sm">Assessment</div>
164 <div class="text-xs text-muted-foreground">~45 minutes</div>
165 </div>
166 </StepperStep>
167 <StepperSeparator />
168 <StepperStep step={2}>
169 {#snippet icon()}
170 <CircleCheck class="size-4" />
171 {/snippet}
172 <div class="space-y-0.5">
173 <div class="font-medium text-sm">Results</div>
174 <div class="text-xs text-muted-foreground">Instant</div>
175 </div>
176 </StepperStep>
177 </StepperRoot>
178 </CardContent>
179 </Card>
180 </div>
181
182
183
184Features
- π― Multiple Orientations: Horizontal and vertical layouts
- π± Fully Responsive: Mobile scroll or vertical auto-switch, tablet density reduction
- ποΈ Animated Connector: Smooth fill animation on step progress (horizontal & vertical)
- π Size Options: xs, sm, default, lg - flexible sizing
- π¨ Variants: Default, outline, ghost, flat styles
- β¨ Interactive: Optional clickable steps for navigation
- β Completion States: Automatic step completion tracking
- π Custom Icons: Per-step icon via
iconinsteps[](declarative) or{#snippet icon()}(primitive) - β οΈ Step States: Per-step
error,loading, anddisabledstates with distinct visuals - π Reactive: Built with Svelte 5's modern reactivity system
- βΏ Accessible: Semantic HTML and ARIA support
- π― Type-Safe: Full TypeScript support with Svelte 5 runes
Implementation Details
The Stepper component provides a flexible system for displaying step-by-step processes with support for different orientations, sizes, and interaction patterns.
Component Architecture
Stepper (Declarative β primary API)
- Pass a
stepsarray β no manual<StepperStep>or<StepperSeparator>needed - Automatically assigns step indices and derives completed state from
activeStep - Auto-inserts separators between steps
- Thin wrapper around
StepperRootβ all props are forwarded
StepperRoot (Primitive β advanced composition)
- Root component that manages stepper state and context
- Controls orientation, size, and variant
- Use when you need custom icons per step or inline step content
StepperStep
- Individual step indicator
- Displays step number or custom icon snippet
- Shows label and optional description
- Automatically displays check icon when completed
StepperSeparator
- Visual connector between steps
- Styled based on completion state
- Adapts to horizontal/vertical orientation
Usage
Declarative API (recommended)
<script lang="ts">
import { Stepper } from "@kareyes/aether";
let currentStep = $state(0);
</script>
<Stepper
bind:activeStep={currentStep}
clickable
steps={[
{ label: "Account", description: "Create your account" },
{ label: "Profile", description: "Complete your profile" },
{ label: "Billing", description: "Add payment method" },
{ label: "Done", description: "All set!" },
]}
/>
Vertical orientation
<Stepper
orientation="vertical"
bind:activeStep={currentStep}
clickable
steps={[
{ label: "Personal Info", description: "Enter your basic information" },
{ label: "Address", description: "Add your address details" },
{ label: "Review", description: "Review and submit" },
]}
/>
With onStepClick callback
<Stepper
clickable
bind:activeStep={currentStep}
onStepClick={(step) => console.log("Navigated to", step)}
steps={[...]}
/>
Primitive API β for custom icons or per-step content
Use StepperRoot when you need icon snippets or children inside individual steps:
<script lang="ts">
import { StepperRoot, StepperStep, StepperSeparator } from "@kareyes/aether";
import { User, CreditCard, CheckCircle } from "@lucide/svelte";
let currentStep = $state(0);
</script>
<StepperRoot bind:activeStep={currentStep} clickable>
<StepperStep step={0} label="Account">
{#snippet icon()}<User class="size-4" />{/snippet}
</StepperStep>
<StepperSeparator completed={currentStep > 0} />
<StepperStep step={1} label="Payment">
{#snippet icon()}<CreditCard class="size-4" />{/snippet}
</StepperStep>
<StepperSeparator completed={currentStep > 1} />
<StepperStep step={2} label="Done">
{#snippet icon()}<CheckCircle class="size-4" />{/snippet}
</StepperStep>
</StepperRoot>
Migration: Primitive β Declarative
<!-- Before (primitive API) -->
<Stepper bind:activeStep={step} clickable>
<StepperStep step={0} label="Account" description="Create account" />
<StepperSeparator completed={step > 0} />
<StepperStep step={1} label="Profile" description="Complete profile" />
<StepperSeparator completed={step > 1} />
<StepperStep step={2} label="Billing" description="Add payment" />
</Stepper>
<!-- After (declarative API) -->
<Stepper
bind:activeStep={step}
clickable
steps={[
{ label: "Account", description: "Create account" },
{ label: "Profile", description: "Complete profile" },
{ label: "Billing", description: "Add payment" },
]}
/>
Sizes
Extra Small
<Stepper size="xs" steps={[{ label: "Extra Small" }, { label: "Step" }]} />
Small
<Stepper size="sm" steps={[{ label: "Small" }, { label: "Step" }]} />
Default
<Stepper size="default" steps={[{ label: "Default" }, { label: "Step" }]} />
Large
<Stepper size="lg" steps={[{ label: "Large" }, { label: "Step" }]} />
Custom Pixel Size
Override the indicator diameter precisely. Font, icon, and connector thickness scale proportionally.
<!-- 20px β smaller than xs -->
<Stepper indicatorSize={20} steps={[{ label: "Tiny" }, { label: "Steps" }]} />
<!-- 56px β larger than lg -->
<Stepper indicatorSize={56} steps={[{ label: "Jumbo" }, { label: "Steps" }]} />
Disable Responsive Scaling
By default, lg and default sizes scale down one level on tablet (β€768px). Use disableResponsiveScaling to lock the size.
<Stepper size="lg" disableResponsiveScaling steps={[{ label: "Always lg" }, { label: "48px always" }]} />
Variants
Default Variant
Filled background for active and completed steps.
<Stepper variant="default" steps={[{ label: "Default" }, { label: "Variant" }]} />
Outline Variant
Outlined style with border emphasis.
<Stepper variant="outline" steps={[{ label: "Outline" }, { label: "Variant" }]} />
Ghost Variant
Subtle background without borders.
<Stepper variant="ghost" steps={[{ label: "Ghost" }, { label: "Variant" }]} />
Flat Variant
Horizontal progress bar style with labels above.
<Stepper
variant="flat"
steps={[
{ label: "Personal Info", description: "Completed" },
{ label: "Education", description: "Completed" },
{ label: "Company", description: "Pending" },
{ label: "Review", description: "Pending" },
]}
/>
Advanced Examples
Registration Flow
<script lang="ts">
import { Stepper } from "@kareyes/aether";
import { Button } from "@kareyes/aether";
let currentStep = $state(0);
</script>
<div class="space-y-6">
<Stepper
bind:activeStep={currentStep}
steps={[
{ label: "Create Account", description: "Enter your email and password" },
{ label: "Personal Info", description: "Tell us about yourself" },
{ label: "Complete", description: "Review and confirm" },
]}
/>
<div class="flex gap-3">
<Button variant="outline" disabled={currentStep === 0} onclick={() => currentStep--}>
Previous
</Button>
<Button disabled={currentStep === 2} onclick={() => currentStep++}>
{currentStep === 2 ? "Complete" : "Next"}
</Button>
</div>
</div>
Form Wizard with Content
<script lang="ts">
import { Stepper } from "@kareyes/aether";
import { Button, Input, Label } from "@kareyes/aether";
let currentStep = $state(0);
let formData = $state({ name: "", email: "", address: "" });
</script>
<div class="space-y-8">
<Stepper
bind:activeStep={currentStep}
clickable
steps={[
{ label: "Basic Info" },
{ label: "Contact" },
{ label: "Review" },
]}
/>
<!-- step content rendered below the stepper -->
<div class="min-h-[200px]">
{#if currentStep === 0}
<Label for="name">Full Name</Label>
<Input id="name" bind:value={formData.name} />
{:else if currentStep === 1}
<Label for="email">Email</Label>
<Input id="email" type="email" bind:value={formData.email} />
{:else}
<p>Name: {formData.name}</p>
<p>Email: {formData.email}</p>
{/if}
</div>
</div>
Custom icons β declarative API
Pass any Svelte component (e.g. a Lucide icon) as the icon field in the steps array:
<script lang="ts">
import { Stepper } from "@kareyes/aether";
import { ShoppingCart, Truck, CreditCard, CheckCircle } from "@lucide/svelte";
let step = $state(0);
</script>
<Stepper
bind:activeStep={step}
clickable
steps={[
{ label: "Cart", description: "Review items", icon: ShoppingCart },
{ label: "Shipping", description: "Enter address", icon: Truck },
{ label: "Payment", description: "Add card", icon: CreditCard },
{ label: "Confirm", description: "Place order", icon: CheckCircle },
]}
/>
Icons are automatically sized to match the size prop. Completed steps show the checkmark instead; error steps show X; loading steps show a spinner.
Custom icons β primitive API
Use StepperRoot when you need icon snippets combined with inline children per step:
<script lang="ts">
import { StepperRoot, StepperStep, StepperSeparator } from "@kareyes/aether";
import { ShoppingCart, CreditCard, Package, CheckCircle } from "@lucide/svelte";
let currentStep = $state(1);
</script>
<StepperRoot orientation="vertical" bind:activeStep={currentStep}>
<StepperStep step={0} label="Cart" description="Review your items" completed>
{#snippet icon()}<ShoppingCart class="size-4" />{/snippet}
</StepperStep>
<StepperSeparator completed />
<StepperStep step={1} label="Payment" description="Enter payment details">
{#snippet icon()}<CreditCard class="size-4" />{/snippet}
</StepperStep>
<StepperSeparator />
<StepperStep step={2} label="Shipping" description="Choose shipping method">
{#snippet icon()}<Package class="size-4" />{/snippet}
</StepperStep>
<StepperSeparator />
<StepperStep step={3} label="Confirm" description="Review and place order">
{#snippet icon()}<CheckCircle class="size-4" />{/snippet}
</StepperStep>
</StepperRoot>
Step States
Per-step error, loading, and disabled flags override the default completed/active/inactive appearance.
Priority (highest β lowest): disabled β error β loading β completed β active β inactive
<!-- Error: red X icon, destructive colors -->
<Stepper
activeStep={1}
steps={[
{ label: "Upload", description: "Done" },
{ label: "Validate", description: "Checksum failed", error: true },
{ label: "Process", description: "Waiting" },
]}
/>
<!-- Loading: spinning loader, active/primary colors -->
<Stepper
activeStep={1}
steps={[
{ label: "Upload", description: "Done" },
{ label: "Processing", description: "Convertingβ¦", loading: true },
{ label: "Complete", description: "Waiting" },
]}
/>
<!-- Disabled: muted colors, not clickable even when stepper is clickable -->
<Stepper
activeStep={1}
clickable
steps={[
{ label: "Starter", description: "Active plan" },
{ label: "Pro", description: "Current step" },
{ label: "Enterprise", description: "Contact sales", disabled: true },
]}
/>
States also work on StepperStep in the primitive API:
<StepperStep step={1} label="Payment" error />
<StepperStep step={2} label="Processing" loading />
<StepperStep step={3} label="Locked" disabled />
Numbers Only (No Labels)
<Stepper steps={[{}, {}, {}, {}]} />
Circular Progress Mode
mobileLayout="circular" replaces the vertical step list below the md breakpoint (768px) with a radial progress ring. The full horizontal stepper is preserved on desktop.
What the ring shows
- Progress arc β animated fill from 0% to 100% as steps advance
- Counter β current step / total (e.g.
2 / 5) with fade-scale animation on change - Percentage β numeric fill percentage below the counter
- Step label β current step's label below the ring, animates on change
- Checkmark β scale-in animation when the final step is reached
- Expand hint β a subtle chevron indicating the ring is tappable
Opening the step drawer
Tapping the ring opens a bottom Sheet listing all steps with their state (completed/active/inactive), label, and description. If clickable is true, tapping a step in the drawer navigates to it.
Code example
<script lang="ts">
import { Stepper } from "@kareyes/aether";
let step = $state(0);
</script>
<!-- Circular on mobile, normal horizontal on desktop -->
<Stepper
bind:activeStep={step}
clickable
mobileLayout="circular"
steps={[
{ label: "Account", description: "Create your account" },
{ label: "Profile", description: "Complete your profile" },
{ label: "Billing", description: "Add payment method" },
{ label: "Done", description: "All set!" },
]}
/>
<!-- Force circular on ALL breakpoints (desktop preview) -->
<Stepper bind:activeStep={step} clickable mobileLayout="circular" circularAlways circularSize={200} steps={[...]} />
<!-- Non-expandable ring (no drawer on tap) -->
<Stepper bind:activeStep={step} mobileLayout="circular" circularAlways circularExpandable={false} steps={[...]} />
Circular mode props
| Prop | Type | Default | Description |
|---|---|---|---|
circularSize |
number |
160 |
Ring diameter in pixels |
circularExpandable |
boolean |
true |
Tap opens step-list drawer |
circularAlways |
boolean |
false |
Show ring at all breakpoints |
Architecture note
When circular mode is active, both the desktop stepper (hidden below md via CSS) and the circular widget are always present in the DOM. This ensures:
- Steps register themselves in context so the ring and drawer have accurate data
- No layout shift when resizing β only
displaytoggles, not component mounts/unmounts
Responsive Behavior
Mobile (< 640px)
mobileLayout="vertical" (default)
- Horizontal steppers automatically switch to vertical layout
- Step buttons get
min-height: 44px/min-width: 44pxfor touch friendliness - Use
hideDescriptionOnMobileto hide descriptions and save space
mobileLayout="scroll"
- Stepper stays horizontal with
overflow-x: autoscrolling - Steps have
shrink-0so they don't collapse - Scrollbar is hidden for a clean look
- Best for many steps that won't fit on screen
Tablet (β€ 768px)
- Layout remains horizontal
lgbuttons scale down todefaultsize,defaultscales tosm- Label and description font sizes reduce one step
- Connector margin-top adjusts to stay aligned with smaller buttons
Animated Connector
- Horizontal: Connector fills left-to-right via
widthanimation - Vertical: Connector fills top-to-bottom via CSS
::afterheightanimation - Both use 500ms
ease-in-outtransition - Completion resets instantly when going back to a previous step
<!-- Auto-switch to vertical on mobile (default) -->
<Stepper mobileLayout="vertical" hideDescriptionOnMobile>
...
</Stepper>
<!-- Horizontal scroll on mobile -->
<Stepper mobileLayout="scroll">
...
</Stepper>
API Reference
Stepper (declarative wrapper)
Preferred API β pass a steps array and let the component handle the rest.
| Prop | Type | Default | Description |
|---|---|---|---|
steps |
StepItem[] |
[] |
Array of step definitions β auto-renders indicators and separators |
orientation |
"horizontal" | "vertical" |
"horizontal" |
Layout orientation |
size |
"xs" | "sm" | "default" | "lg" |
"default" |
Size of the stepper |
variant |
"default" | "outline" | "ghost" | "flat" |
"default" |
Visual variant |
activeStep |
number |
0 |
Current active step (bindable) |
clickable |
boolean |
false |
Allow clicking steps to navigate |
mobileLayout |
"vertical" | "scroll" | "circular" |
"circular" |
Mobile behavior: vertical switch, horizontal scroll, or circular progress ring |
hideDescriptionOnMobile |
boolean |
false |
Hide step descriptions on mobile screens |
indicatorSize |
number |
- | Override indicator circle diameter in pixels; font, icon, and connector scale proportionally |
disableResponsiveScaling |
boolean |
false |
Opt-out of automatic one-level size scale-down on tablet (β€768px) |
circularSize |
number |
72 |
Diameter of the circular progress ring in px |
circularExpandable |
boolean |
true |
Whether tapping the ring opens a step-list bottom drawer |
circularAlways |
boolean |
false |
Force circular mode at all breakpoints (desktop override) |
onStepClick |
(step: number) => void |
- | Callback when step is clicked |
ref |
HTMLDivElement |
- | Root element reference (bindable) |
class |
string |
- | Additional CSS classes |
StepItem
Shape of each object in the steps array.
| Field | Type | Description |
|---|---|---|
label |
string |
Step label text |
description |
string |
Optional short description |
icon |
Component |
Svelte component for the indicator icon (e.g. a Lucide icon) |
error |
boolean |
Show error state β X icon, destructive colors |
loading |
boolean |
Show loading state β spinner, primary colors |
disabled |
boolean |
Disable the step β muted colors, not clickable |
StepperRoot (primitive base)
Identical props to Stepper minus steps, plus children (required for composition).
Use when steps need custom icon snippets or inline content children.
| Prop | Type | Default | Description |
|---|---|---|---|
(all Stepper props except steps) |
|||
children |
Snippet |
- | <StepperStep> and <StepperSeparator> nodes |
StepperStep
Individual step component.
| Prop | Type | Default | Description |
|---|---|---|---|
step |
number |
required | Step index (0-based) |
label |
string |
- | Step label text |
description |
string |
- | Step description text |
icon |
Snippet |
- | Custom icon snippet rendered in the indicator |
completed |
boolean |
false |
Manually mark as completed |
error |
boolean |
false |
Error state β X icon, destructive colors |
loading |
boolean |
false |
Loading state β spinner, primary colors |
disabled |
boolean |
false |
Disabled β muted colors, not clickable |
ref |
HTMLDivElement |
- | Step element reference (bindable) |
class |
string |
- | Additional CSS classes |
children |
Snippet |
- | Custom content rendered below label/description |
StepperSeparator
Visual separator between steps.
| Prop | Type | Default | Description |
|---|---|---|---|
completed |
boolean |
false |
Mark separator as completed |
ref |
HTMLDivElement |
- | Separator element reference (bindable) |
class |
string |
- | Additional CSS classes |
Accessibility
The Stepper component follows accessibility best practices:
- Semantic HTML: Uses proper HTML elements for structure
- Button Elements: Steps are actual
<button>elements when clickable - Disabled State: Non-clickable steps are properly disabled
- Visual States: Clear visual indication of active, completed, and inactive states
- Keyboard Navigation: Full keyboard support when clickable
Best Practices
- Keep it simple - Don't use too many steps (3-5 is ideal)
- Clear labels - Use concise, descriptive labels for each step
- Show progress - Make it clear which step is active and completed
- Vertical for mobile - Consider using vertical orientation on small screens
- Enable navigation - Use clickable steps for non-linear workflows
- Provide feedback - Use descriptions to guide users
- Validate before advancing - Don't let users skip required steps
Common Use Cases
- Registration flows - Multi-step account creation
- Checkout processes - Shopping cart to payment to confirmation
- Form wizards - Breaking long forms into manageable steps
- Onboarding - Guiding new users through setup
- Progress tracking - Showing completion status
- Order tracking - Displaying order/shipping status
Demo & Storybook
- Demo Page:
/stepper-demo- Comprehensive examples and use cases - Storybook:
Components/Stepper- Interactive component playground
Technical Implementation
Variant System
The Stepper component uses tailwind-variants for the styling system:
export const stepperVariants = tv({
slots: {
root: ["flex gap-2"],
step: ["flex items-center gap-2 relative"],
stepButton: ["flex items-center justify-center rounded-full"],
stepContent: ["flex flex-col gap-1"],
separator: ["flex-1"],
},
variants: {
orientation: { horizontal: {...}, vertical: {...} },
size: { sm: {...}, default: {...}, lg: {...} },
variant: { default: {...}, outline: {...}, ghost: {...} },
}
});
State Management
- Uses Svelte 5 context API for sharing state between components
- Automatic step completion tracking based on activeStep
- Support for manual completion overrides
- Reactive updates across all child components