Field
A wrapper component for form inputs with labels and validation states
Complete Form Example
A full form using Field with form controls passed as slots.
1
2<script lang="ts">
3 import { Button, Input, Textarea, Checkbox, Switch, Select, Field, FieldPrimitives } from "@kareyes/aether";
4
5 let username = $state("");
6 let email = $state("");
7 let password = $state("");
8 let bio = $state("");
9 let country = $state("");
10 let newsletter = $state(false);
11 let notifications = $state(true);
12 let plan = $state("");
13 let errors = $state<Record<string, string>>({});
14
15 const countries = [
16 { value: "us", label: "United States" },
17 { value: "uk", label: "United Kingdom" },
18 { value: "ca", label: "Canada" },
19 ];
20
21 const plans = [
22 { value: "free", label: "Free - $0/month" },
23 { value: "basic", label: "Basic - $9/month" },
24 { value: "pro", label: "Pro - $29/month" },
25 ];
26
27 function handleSubmit(event: Event) {
28 event.preventDefault();
29 errors = {};
30 if (!username) errors.username = "Username is required";
31 if (!email) errors.email = "Email is required";
32 if (Object.keys(errors).length === 0) alert("Form submitted!");
33 }
34</script>
35
36<form onsubmit={handleSubmit} class="rounded-lg border bg-card p-6">
37 <FieldPrimitives.Set>
38 <FieldPrimitives.Legend>Account Information</FieldPrimitives.Legend>
39 <FieldPrimitives.Description>
40 Create your account by filling out the information below.
41 </FieldPrimitives.Description>
42 <FieldPrimitives.Separator />
43
44 <FieldPrimitives.Group class="gap-6">
45 <Field label="Username" description="Choose a unique username." required error={errors.username}>
46 <Input id="username" error={!!errors.username} bind:value={username} placeholder="johndoe" required />
47 </Field>
48
49 <Field label="Email" description="We'll never share your email." required error={errors.email}>
50 <Input id="email" error={!!errors.email} type="email" bind:value={email} placeholder="you@example.com" />
51 </Field>
52
53 <Field label="Bio" description="Tell us about yourself.">
54 <Textarea id="bio" bind:value={bio} placeholder="I am a..." rows={4} />
55 </Field>
56
57 <Field label="Country" description="Select your country." required>
58 <Select bind:value={country} placeholder="Select a country..." options={countries} />
59 </Field>
60 </FieldPrimitives.Group>
61 </FieldPrimitives.Set>
62
63 <FieldPrimitives.Set>
64 <FieldPrimitives.Legend>Preferences</FieldPrimitives.Legend>
65 <FieldPrimitives.Group class="gap-4">
66 <Field label="Newsletter subscription" description="Receive weekly updates." orientation="horizontal">
67 <Checkbox id="newsletter" bind:checked={newsletter} />
68 </Field>
69 <Field label="Enable notifications" description="Get notified about updates." orientation="horizontal">
70 <Switch id="notifications" bind:checked={notifications} />
71 </Field>
72 </FieldPrimitives.Group>
73 </FieldPrimitives.Set>
74
75 <div class="flex gap-4">
76 <Button type="submit">Submit</Button>
77 <Button type="button" variant="outline">Reset</Button>
78 </div>
79</form>Input Variants
Different visual styles for input fields
1
2<script lang="ts">
3 import { Input, Card, Field } from "@kareyes/aether";
4</script>
5
6<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
7 <Card>
8 <Field label="Default Variant">
9 <Input placeholder="Default style" variant="default" />
10 </Field>
11 </Card>
12
13 <Card>
14 <Field label="Outline Variant">
15 <Input placeholder="Outline style" variant="outline" />
16 </Field>
17 </Card>
18
19 <Card>
20 <Field label="Filled Variant">
21 <Input placeholder="Filled style" variant="filled" />
22 </Field>
23 </Card>
24
25 <Card>
26 <Field label="Ghost Variant">
27 <Input placeholder="Ghost style" variant="ghost" />
28 </Field>
29 </Card>
30
31 <Card>
32 <Field label="Underline Variant">
33 <Input placeholder="Underline style" variant="underline" />
34 </Field>
35 </Card>
36</div>Input Sizes
Different size options for input fields
1
2<script lang="ts">
3 import { Input, Card, Field } from "@kareyes/aether";
4</script>
5
6<div class="grid gap-6 md:grid-cols-3">
7 <Card>
8 <Field label="Small Size">
9 <Input placeholder="Small input" size="sm" />
10 </Field>
11 </Card>
12
13 <Card>
14 <Field label="Default Size">
15 <Input placeholder="Default input" size="default" />
16 </Field>
17 </Card>
18
19 <Card>
20 <Field label="Large Size">
21 <Input placeholder="Large input" size="lg" />
22 </Field>
23 </Card>
24</div>Input Masks
Formatted input fields with automatic masking
1
2<script lang="ts">
3 import { Input, Card, Field } from "@kareyes/aether";
4
5 let phone = $state("");
6 let ssn = $state("");
7</script>
8
9<div class="grid gap-6 md:grid-cols-2">
10 <Card>
11 <Field label="Phone Number">
12 <Input bind:value={phone} placeholder="(555) 555-5555" mask="phone" />
13 </Field>
14 </Card>
15
16 <Card>
17 <Field label="Social Security Number">
18 <Input bind:value={ssn} placeholder="***-**-****" mask="ssn" />
19 </Field>
20 </Card>
21
22 <Card>
23 <Field label="Credit Card">
24 <Input placeholder="**** **** **** ****" mask="creditCard" />
25 </Field>
26 </Card>
27
28 <Card>
29 <Field label="Date">
30 <Input placeholder="MM/DD/YYYY" mask="date" />
31 </Field>
32 </Card>
33</div>Textarea Variants & Features
Different styles and features for textarea fields
Maximum 200 characters
Automatically grows with content
1
2<script lang="ts">
3 import { Textarea, Card, Field } from "@kareyes/aether";
4
5 let description = $state("");
6</script>
7
8<div class="grid gap-6 md:grid-cols-2">
9 <Card>
10 <Field label="Default Textarea">
11 <Textarea placeholder="Enter your text..." variant="default" />
12 </Field>
13 </Card>
14
15 <Card>
16 <Field label="Outline Textarea">
17 <Textarea placeholder="Enter your text..." variant="outline" />
18 </Field>
19 </Card>
20
21 <Card>
22 <Field label="With Character Counter" description="Maximum 200 characters">
23 <Textarea bind:value={description} placeholder="Enter your text..." maxLength={200} showCount />
24 </Field>
25 </Card>
26
27 <Card>
28 <Field label="Auto-Resize" description="Automatically grows with content">
29 <Textarea placeholder="Start typing..." autoResize minRows={2} maxRows={6} />
30 </Field>
31 </Card>
32</div>Checkbox Variants & Sizes
Different styles and sizes for checkbox fields
1
2<script lang="ts">
3 import { Checkbox, Card, Field } from "@kareyes/aether";
4</script>
5
6<!-- Variants -->
7<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
8 <Card>
9 <Field label="Default Variant" orientation="horizontal">
10 <Checkbox variant="default" />
11 </Field>
12 </Card>
13
14 <Card>
15 <Field label="Destructive Variant" orientation="horizontal">
16 <Checkbox variant="destructive" />
17 </Field>
18 </Card>
19
20 <Card>
21 <Field label="Success Variant" orientation="horizontal">
22 <Checkbox variant="success" />
23 </Field>
24 </Card>
25
26 <Card>
27 <Field label="Warning Variant" orientation="horizontal">
28 <Checkbox variant="warning" />
29 </Field>
30 </Card>
31</div>
32
33<!-- Sizes -->
34<div class="grid gap-6 md:grid-cols-4">
35 <Card>
36 <Field label="Small Size" orientation="horizontal">
37 <Checkbox size="sm" />
38 </Field>
39 </Card>
40
41 <Card>
42 <Field label="Default Size" orientation="horizontal">
43 <Checkbox size="default" />
44 </Field>
45 </Card>
46
47 <Card>
48 <Field label="Large Size" orientation="horizontal">
49 <Checkbox size="lg" />
50 </Field>
51 </Card>
52
53 <Card>
54 <Field label="Extra Large" orientation="horizontal">
55 <Checkbox size="xl" />
56 </Field>
57 </Card>
58</div>CheckboxGroup with Field Component
Multiple checkbox selection with Field wrapper for labels, descriptions, and error handling
Choose the features you want to enable for your account.
Selected: None
Default vertical orientation
Horizontal orientation for inline display
Selected: None
1
2<script lang="ts">
3 import { CheckboxGroup, Card, Field, FieldPrimitives } from "@kareyes/aether";
4 import type { CheckboxPrimitives } from "@kareyes/aether";
5
6 type CheckboxGroupOption = CheckboxPrimitives.CheckboxGroupOption;
7
8 let selectedFeatures = $state<string[]>([]);
9
10 const featureOptions: CheckboxGroupOption[] = [
11 { id: "feature-api", label: "API Access", value: "api", description: "Access to REST API" },
12 { id: "feature-analytics", label: "Analytics Dashboard", value: "analytics", description: "View detailed analytics" },
13 { id: "feature-export", label: "Data Export", value: "export", description: "Export data to CSV/JSON" },
14 ];
15</script>
16
17 <div class="space-y-8">
18 <!-- Basic CheckboxGroup -->
19 <Card>
20 <Field
21 label="Select Features"
22 description="Choose the features you want to enable for your account."
23 >
24 <CheckboxGroup
25 options={featureOptions}
26 bind:values={selectedFeatures}
27 />
28 </Field>
29 <div class="mt-4 rounded-md bg-muted p-3">
30 <p class="text-sm font-medium">
31 Selected: {selectedFeatures.length > 0
32 ? selectedFeatures.join(", ")
33 : "None"}
34 </p>
35 </div>
36 </Card>
37
38 <!-- CheckboxGroup Orientations -->
39 <div class="grid gap-6 md:grid-cols-2">
40 <Card>
41 <Field
42 label="Vertical Layout"
43 description="Default vertical orientation"
44 >
45 <CheckboxGroup
46 options={interestOptions}
47 values={[]}
48 orientation="vertical"
49 />
50 </Field>
51 </Card>
52
53 <Card>
54 <Field
55 label="Horizontal Layout"
56 description="Horizontal orientation for inline display"
57 >
58 <CheckboxGroup
59 options={interestOptions}
60 values={[]}
61 orientation="horizontal"
62 />
63 </Field>
64 </Card>
65 </div>
66
67 <!-- CheckboxGroup with Error State -->
68 <Card>
69 <Field
70 label="User Permissions"
71 description="Select at least one permission to continue."
72 required
73 error={selectedPermissions.length === 0
74 ? "Please select at least one permission"
75 : undefined}
76 >
77 <CheckboxGroup
78 options={permissionOptions}
79 bind:values={selectedPermissions}
80 error={selectedPermissions.length === 0}
81 required
82 />
83 </Field>
84 <div class="mt-4 rounded-md bg-muted p-3">
85 <p class="text-sm font-medium">
86 Selected: {selectedPermissions.length > 0
87 ? selectedPermissions.join(", ")
88 : "None"}
89 </p>
90 </div>
91 </Card>
92
93 <!-- CheckboxGroup in Form Context -->
94 <Card class="bg-muted/50">
95 <FieldPrimitives.Set>
96 <FieldPrimitives.Legend>Account Setup</FieldPrimitives.Legend>
97 <FieldPrimitives.Description
98 >Configure your account features and preferences</FieldPrimitives.Description
99 >
100
101 <FieldPrimitives.Separator />
102
103 <FieldPrimitives.Group class="gap-6">
104 <Field
105 label="Enable Features"
106 description="Select the features you want to use"
107 >
108 <CheckboxGroup
109 options={featureOptions}
110 bind:values={selectedFeatures}
111 size="default"
112 />
113 </Field>
114
115 <Field
116 label="Communication Preferences"
117 description="How should we contact you?"
118 >
119 <CheckboxGroup
120 options={preferenceOptions}
121 bind:values={selectedPreferences}
122 variant="default"
123 />
124 </Field>
125 </FieldPrimitives.Group>
126 </FieldPrimitives.Set>
127 </Card>
128 </div>RadioGroup with Field Component
Single selection radio groups with Field wrapper for labels, descriptions, and error handling
Select the subscription plan that best fits your needs.
Basic features for personal use - $0/month
Essential features for small teams - $9/month
Advanced features for professionals - $29/month
Full features for large organizations - $99/month
Selected: basic
Choose your preferred color theme.
Bright and clear appearance
Easy on the eyes at night
Matches your system preferences
Selected: light
Default vertical orientation
Bright and clear appearance
Easy on the eyes at night
Matches your system preferences
Horizontal orientation for inline display
Visa, Mastercard, Amex
Secure PayPal payment
Direct bank transfer
Bitcoin, Ethereum
Selected: None
Configure your product preferences
Choose your preferred size
How would you like to be notified?
Choose how you want to run your GPU workloads.
Selected: kubernetes
1
2<script lang="ts">
3 import { RadioGroup, Card, Field, FieldPrimitives } from "@kareyes/aether";
4 import type { RadioGroupPrimitives } from "@kareyes/aether";
5
6 type RadioGroupOption = RadioGroupPrimitives.RadioGroupOption;
7
8 let selectedPlan = $state("basic");
9 let selectedPayment = $state("");
10
11 const planOptions: RadioGroupOption[] = [
12 { id: "plan-free", label: "Free Plan", value: "free", description: "Basic features - $0/month" },
13 { id: "plan-basic", label: "Basic Plan", value: "basic", description: "Essential features - $9/month" },
14 { id: "plan-pro", label: "Pro Plan", value: "pro", description: "Advanced features - $29/month" },
15 ];
16
17 const paymentOptions: RadioGroupOption[] = [
18 { id: "pay-credit", label: "Credit Card", value: "credit", description: "Visa, Mastercard, Amex" },
19 { id: "pay-paypal", label: "PayPal", value: "paypal", description: "Secure PayPal payment" },
20 { id: "pay-bank", label: "Bank Transfer", value: "bank", description: "Direct bank transfer", disabled: true },
21 ];
22</script>
23
24<!-- Basic RadioGroup -->
25<Card>
26 <Field label="Choose Your Plan" description="Select the subscription plan that best fits your needs.">
27 <RadioGroup options={planOptions} bind:value={selectedPlan} />
28 </Field>
29 <div class="mt-4 rounded-md bg-muted p-3">
30 <p class="text-sm font-medium">Selected: {selectedPlan || "None"}</p>
31 </div>
32</Card>
33
34<!-- RadioGroup with Error State -->
35<Card>
36 <Field
37 label="Payment Method"
38 description="Select your preferred payment method."
39 required
40 error={!selectedPayment ? "Please select a payment method" : undefined}
41 >
42 <RadioGroup options={paymentOptions} bind:value={selectedPayment} error={!selectedPayment} required />
43 </Field>
44</Card>
45
46<!-- RadioGroup in Form Context -->
47<Card class="bg-muted/50">
48 <FieldPrimitives.Set>
49 <FieldPrimitives.Legend>Product Configuration</FieldPrimitives.Legend>
50 <FieldPrimitives.Description>Configure your product preferences</FieldPrimitives.Description>
51 <FieldPrimitives.Separator />
52 <FieldPrimitives.Group class="gap-6">
53 <Field label="Select Size" description="Choose your preferred size">
54 <RadioGroup options={sizeRadioOptions} bind:value={selectedSize} />
55 </Field>
56 </FieldPrimitives.Group>
57 </FieldPrimitives.Set>
58</Card>Switch Variants & Sizes
Different styles and sizes for switch fields
Standard switch style
Success-themed switch
1
2<script lang="ts">
3 import { Switch, Card, Field } from "@kareyes/aether";
4</script>
5
6<!-- Variants -->
7<div class="grid gap-6 md:grid-cols-2">
8 <Card>
9 <Field label="Default Variant" description="Standard switch style" orientation="horizontal">
10 <Switch variant="default" />
11 </Field>
12 </Card>
13
14 <Card>
15 <Field label="Success Variant" description="Success-themed switch" orientation="horizontal">
16 <Switch variant="success" />
17 </Field>
18 </Card>
19</div>
20
21<!-- Sizes -->
22<div class="grid gap-6 md:grid-cols-3">
23 <Card>
24 <Field label="Small Size" orientation="horizontal">
25 <Switch size="sm" />
26 </Field>
27 </Card>
28
29 <Card>
30 <Field label="Default Size" orientation="horizontal">
31 <Switch size="default" />
32 </Field>
33 </Card>
34
35 <Card>
36 <Field label="Large Size" orientation="horizontal">
37 <Switch size="lg" />
38 </Field>
39 </Card>
40</div>Horizontal Layouts
Fields with horizontal orientation
Receive updates via email
You must agree to continue
Toggle dark mode theme
1
2<script lang="ts">
3 import { Checkbox, Switch, Card, Field } from "@kareyes/aether";
4
5 let agreeToTerms = $state(false);
6 let darkMode = $state(false);
7</script>
8
9<div class="grid gap-6">
10 <Card>
11 <Field label="Email notifications" description="Receive updates via email" orientation="horizontal">
12 <Switch />
13 </Field>
14 </Card>
15
16 <Card>
17 <Field label="I agree to terms" description="You must agree to continue" orientation="horizontal">
18 <Checkbox bind:checked={agreeToTerms} />
19 </Field>
20 </Card>
21
22 <Card>
23 <Field label="Dark mode" description="Toggle dark mode theme" orientation="horizontal">
24 <Switch bind:checked={darkMode} variant="success" />
25 </Field>
26 </Card>
27</div>Error States
Fields displaying validation errors
1
2<script lang="ts">
3 import { Input, Card, Field } from "@kareyes/aether";
4</script>
5
6<div class="grid gap-6 md:grid-cols-2">
7 <Card>
8 <Field
9 label="Username"
10 description="Enter a unique username"
11 error="This username is already taken"
12 required
13 >
14 <Input placeholder="username" />
15 </Field>
16 </Card>
17
18 <Card>
19 <Field label="Email" error="Please enter a valid email address" required>
20 <Input type="email" placeholder="email@example.com" />
21 </Field>
22 </Card>
23</div>Card Component Variants
Different visual styles and configurations for the Card component
Cards with Actions
Manage your profile settings
Configure how you receive alerts
Cards with Footers
This action cannot be undone
Deleting your account will permanently remove all your data from our servers.
Update your preferences
Complex Card Example
Manage your team plan and billing
Next billing date: Dec 1, 2025
1
2<script lang="ts">
3 import { Button, Input, Switch, Select, Card, Field, FieldPrimitives } from "@kareyes/aether";
4
5 let plan = $state("");
6 const plans = [
7 { value: "free", label: "Free - $0/month" },
8 { value: "pro", label: "Pro - $29/month" },
9 ];
10</script>
11
12<!-- Card with Header Actions -->
13<Card title="User Profile" description="Manage your profile settings">
14 {#snippet headerAction()}
15 <Button variant="ghost" size="sm">Edit</Button>
16 {/snippet}
17 <Field label="Display Name">
18 <Input value="John Doe" />
19 </Field>
20</Card>
21
22<!-- Card with Footer -->
23<Card title="Delete Account" description="This action cannot be undone" variant="outline">
24 <p class="text-sm text-muted-foreground">
25 Deleting your account will permanently remove all your data.
26 </p>
27 {#snippet footer()}
28 <Button variant="outline" size="sm" class="mr-2">Cancel</Button>
29 <Button variant="destructive" size="sm">Delete Account</Button>
30 {/snippet}
31</Card>
32
33<!-- Complex Card Example -->
34<Card title="Team Subscription" description="Manage your team plan" variant="elevated" hover>
35 {#snippet headerAction()}
36 <Button variant="outline" size="sm">Upgrade</Button>
37 {/snippet}
38 <div class="space-y-4">
39 <Field label="Plan Type">
40 <Select bind:value={plan} options={plans} placeholder="Select a plan..." />
41 </Field>
42 <Field label="Auto-renew subscription" orientation="horizontal">
43 <Switch checked variant="success" />
44 </Field>
45 </div>
46 {#snippet footer()}
47 <div class="flex-1">
48 <p class="text-xs text-muted-foreground">Next billing date: Dec 1, 2025</p>
49 </div>
50 <Button size="sm">Update Plan</Button>
51 {/snippet}
52</Card>Slider with Field Component
Range slider controls with Field wrapper
Current volume: 50%
$100 - $500
1
2<script lang="ts">
3 import { Slider, Card, Field } from "@kareyes/aether";
4
5 let volume = $state(50);
6 let priceRange = $state([100, 500]);
7</script>
8
9<div class="grid gap-6 md:grid-cols-2">
10 <Card>
11 <Field label="Volume" description={`Current volume: ${volume}%`}>
12 <Slider type="single" bind:value={volume} min={0} max={100} step={1} />
13 </Field>
14 </Card>
15
16 <Card>
17 <Field label="Price Range" description={`$${priceRange[0]} - $${priceRange[1]}`}>
18 <Slider type="multiple" bind:value={priceRange} min={0} max={1000} step={10} />
19 </Field>
20 </Card>
21</div>Input OTP with Field Component
One-time password inputs with Field wrapper
Enter the 6-digit code sent to your email
Enter your 4-digit PIN
1
2<script lang="ts">
3 import { InputOTP, Card, Field } from "@kareyes/aether";
4
5 let otpCode = $state("");
6</script>
7
8<div class="grid gap-6 md:grid-cols-2">
9 <Card>
10 <Field label="Verification Code" description="Enter the 6-digit code sent to your email">
11 <InputOTP maxlength={6} groups={2} bind:value={otpCode} />
12 </Field>
13 {#if otpCode}
14 <div class="mt-4 rounded-md bg-muted p-3">
15 <p class="text-sm font-medium">Code: {otpCode}</p>
16 </div>
17 {/if}
18 </Card>
19
20 <Card>
21 <Field label="PIN Code" description="Enter your 4-digit PIN" required>
22 <InputOTP maxlength={4} groups={2} />
23 </Field>
24 </Card>
25</div>Number Spinner with Field Component
Numeric spinners with Field wrapper
Select the number of items
Selected: 1
Increments of $10
Your age in years
1
2<script lang="ts">
3 import { NumberSpinner, Card, Field } from "@kareyes/aether";
4
5 let quantity = $state(1);
6</script>
7
8<div class="grid gap-6 md:grid-cols-3">
9 <Card>
10 <Field label="Quantity" description="Select the number of items">
11 <NumberSpinner bind:value={quantity} min={1} max={100} />
12 </Field>
13 <div class="mt-4 rounded-md bg-muted p-3">
14 <p class="text-sm font-medium">Selected: {quantity}</p>
15 </div>
16 </Card>
17
18 <Card>
19 <Field label="Amount" description="Increments of $10">
20 <NumberSpinner value={50} min={0} max={1000} step={10} />
21 </Field>
22 </Card>
23
24 <Card>
25 <Field label="Age" description="Your age in years" required>
26 <NumberSpinner value={25} min={18} max={120} />
27 </Field>
28 </Card>
29</div>File Input with Field Component
File upload controls with Field wrapper
PDF or Word document (max 5MB)
Drag and drop or click to browse
Drop images or PDFs here
Max 10MB per file
1
2<script lang="ts">
3 import { FileInput, Card, Field } from "@kareyes/aether";
4</script>
5
6<div class="grid gap-6 md:grid-cols-2">
7 <Card>
8 <Field label="Upload Document" description="PDF or Word document (max 5MB)">
9 <FileInput
10 mode="regular"
11 validation={{ maxFiles: 3, acceptedTypes: [".doc", ".docx", ".txt"] }}
12 />
13 </Field>
14 </Card>
15
16 <Card>
17 <Field label="Upload Images" description="Drag and drop or click to browse">
18 <FileInput
19 mode="drag-drop"
20 validation={{ maxSize: 10 * 1024 * 1024, acceptedTypes: ["image/*", ".pdf"] }}
21 dragDropProps={{ label: "Drop images or PDFs here", description: "Max 10MB per file", showFileList: true }}
22 />
23 </Field>
24 </Card>
25</div>
26
27<Card>
28 <Field label="Upload document" error="File size exceeds 5MB limit">
29 <FileInput
30 mode="regular"
31 validation={{ maxSize: 5 * 1024 * 1024, acceptedTypes: ["application/pdf"] }}
32 />
33 </Field>
34</Card>Date Picker with Field Component
Date selection with Field wrapper
Select your birth date
Choose your preferred delivery date
1
2<script lang="ts">
3 import type { DateValue } from "@internationalized/date";
4 import { DatePicker, Card, Field } from "@kareyes/aether";
5
6 let deliveryDate = $state<DateValue | undefined>(undefined);
7</script>
8
9<div class="grid gap-6 md:grid-cols-2">
10 <Card>
11 <Field label="Date of Birth" description="Select your birth date">
12 <DatePicker />
13 </Field>
14 </Card>
15
16 <Card>
17 <Field label="Delivery Date" description="Choose your preferred delivery date" required>
18 <DatePicker bind:value={deliveryDate} />
19 </Field>
20 </Card>
21</div>Label Position
Control label placement with labelPosition prop - useful for Checkbox and Switch components
Checkbox - Label After
Control appears before label (common pattern for checkboxes)
You must accept to continue
Switch - Label After
Toggle appears before label
Receive email updates
Multiple Checkboxes
Group of checkboxes with labels after
Receive weekly updates and news
Promotional offers and discounts
Before vs After Comparison
Visual comparison of label positions
Label Before (Default)
Turn on this feature
Label After
Turn on this feature
Vertical Orientation with Label After
Label after also works with vertical layouts
Please read and accept our terms of service
1
2<script lang="ts">
3 import { Checkbox, Switch, Card, Field } from "@kareyes/aether";
4
5 let acceptTerms = $state(false);
6 let enableNotifications = $state(false);
7</script>
8
9<div class="grid gap-6 md:grid-cols-2">
10 <!-- Checkbox with label after -->
11 <Card>
12 <Field
13 label="Accept terms and conditions"
14 description="You must accept to continue"
15 labelPosition="after"
16 orientation="horizontal"
17 >
18 <Checkbox bind:checked={acceptTerms} />
19 </Field>
20 </Card>
21
22 <!-- Switch with label after -->
23 <Card>
24 <Field
25 label="Enable notifications"
26 description="Receive email updates"
27 labelPosition="after"
28 orientation="horizontal"
29 >
30 <Switch bind:checked={enableNotifications} />
31 </Field>
32 </Card>
33</div>
34
35<!-- Before vs After Comparison -->
36<Card>
37 <div class="space-y-6">
38 <div>
39 <p class="text-xs font-medium text-muted-foreground mb-2">Label Before (Default)</p>
40 <Field label="Enable feature" description="Turn on this feature" labelPosition="before" orientation="horizontal">
41 <Switch />
42 </Field>
43 </div>
44 <div>
45 <p class="text-xs font-medium text-muted-foreground mb-2">Label After</p>
46 <Field label="Enable feature" description="Turn on this feature" labelPosition="after" orientation="horizontal">
47 <Switch />
48 </Field>
49 </div>
50 </div>
51</Card>Complete Order Form
Comprehensive form with all advanced components
Fill out the order form with all the necessary details
Number of items to order
Set notification volume: 50%
Enter the code from your email
Upload proof of payment (PDF, max 5MB)
Drag and drop product images
Drop images here
Max 10MB per image
Get your order within 24 hours
1
2<script lang="ts">
3 import { Button, Slider, InputOTP, NumberSpinner, FileInput, Switch, Field, FieldPrimitives, Card } from "@kareyes/aether";
4
5 let quantity = $state(1);
6 let volume = $state(50);
7 let otpCode = $state("");
8</script>
9
10<Card class="bg-muted/50">
11 <FieldPrimitives.Set>
12 <FieldPrimitives.Legend>Place Your Order</FieldPrimitives.Legend>
13 <FieldPrimitives.Description>
14 Fill out the order form with all the necessary details
15 </FieldPrimitives.Description>
16 <FieldPrimitives.Separator />
17
18 <FieldPrimitives.Group class="gap-6">
19 <Field label="Quantity" description="Number of items to order" required>
20 <NumberSpinner bind:value={quantity} min={1} max={100} />
21 </Field>
22
23 <Field label="Volume Level" description={`Set notification volume: ${volume}%`}>
24 <Slider type="single" bind:value={volume} min={0} max={100} step={1} />
25 </Field>
26
27 <Field label="Verification Code" description="Enter the code from your email" required>
28 <InputOTP maxlength={6} groups={2} bind:value={otpCode} />
29 </Field>
30
31 <Field label="Upload Receipt" description="Upload proof of payment (PDF, max 5MB)">
32 <FileInput
33 mode="regular"
34 validation={{ maxSize: 5 * 1024 * 1024, acceptedTypes: ["application/pdf"] }}
35 />
36 </Field>
37
38 <Field label="Enable Rush Delivery" description="Get your order within 24 hours" orientation="horizontal">
39 <Switch />
40 </Field>
41 </FieldPrimitives.Group>
42
43 <div class="flex gap-4 pt-4">
44 <Button>Place Order</Button>
45 <Button variant="outline">Cancel</Button>
46 </div>
47 </FieldPrimitives.Set>
48</Card>Features
- 🎯 Unified API: Consistent interface for all form components
- 🔄 Auto-Layout: Vertical, horizontal, and responsive orientations
- ✅ Validation: Built-in error state handling with visual feedback
- ♿ Accessible: Automatic ARIA attributes and semantic HTML
- 🎨 Flexible: Works with Input, Textarea, Checkbox, Switch, Select, RadioGroup, Slider, and more
- 🎭 Type-Safe: Full TypeScript support
- 🔀 Label Positioning: Control label placement (before/after) for optimal UX
Simple Field (Recommended)
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { Input } from "@kareyes/aether";
let email = $state('');
</script>
<Field label="Email" description="Enter your email address">
<Input type="email" bind:value={email} placeholder="you@example.com" />
</Field>
Composable Field (Advanced)
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { Input } from "@kareyes/aether";
let email = $state('');
</script>
<FieldPrimitives.Root>
<FieldPrimitives.Label>Email</FieldPrimitives.Label>
<FieldPrimitives.Description>Enter your email address</FieldPrimitives.Description>
<Input type="email" bind:value={email} placeholder="you@example.com" />
</FieldPrimitives.Root>
API Reference
Field Props
Simplified component that wraps all field functionality:
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string |
required | Field label text |
description |
string? |
- | Helper text displayed below the field |
error |
string? |
- | Error message (replaces description when present) |
required |
boolean |
false |
Shows required asterisk (*) next to label |
disabled |
boolean |
false |
Disables the field and applies disabled styling |
orientation |
"vertical" | "horizontal" | "responsive" |
"vertical" |
Layout orientation |
labelPosition |
"before" | "after" |
"before" |
Position of label relative to control (useful for Checkbox, Switch) |
class |
string? |
- | Additional CSS classes for the root element |
children |
Snippet |
required | The form control to render |
beforeLabel |
Snippet? |
- | Optional content before the label (for horizontal layouts) |
Component Exports
The Field component provides both a simplified API and composable parts:
import { FieldPrimitives } from "@kareyes/aether";
// Simplified wrapper component
Field
// Composable parts
FieldPrimitives.Root // Container with orientation support
FieldPrimitives.Set // Fieldset wrapper for grouped fields
FieldPrimitives.Legend // Legend for fieldset
FieldPrimitives.Group // Groups multiple fields together
FieldPrimitives.Content // Content wrapper for horizontal layouts
FieldPrimitives.Label // Label element
FieldPrimitives.Title // Title element (alternative to Label)
FieldPrimitives.Description // Description/helper text
FieldPrimitives.Separator // Visual separator
FieldPrimitives.Error // Error message display
Complete Implementation Guide
Input Components
Text Input
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { Input } from "@kareyes/aether";
let username = $state('');
let errors = $state<Record<string, string>>({});
</script>
<!-- Basic Input -->
<Field
label="Username"
description="Choose a unique username for your account."
required
error={errors.username}
>
<Input
bind:value={username}
placeholder="johndoe"
error={!!errors.username}
/>
</Field>
<!-- Input with Error State -->
<Field
label="Email"
error="Please enter a valid email address"
required
>
<Input type="email" error={true} placeholder="you@example.com" />
</Field>
<!-- Disabled Input -->
<Field
label="Email"
description="Email cannot be changed"
disabled
>
<Input type="email" value="locked@example.com" disabled />
</Field>
Textarea
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { Textarea } from "@kareyes/aether";
let bio = $state('');
</script>
<!-- Basic Textarea -->
<Field
label="Bio"
description="Tell us a little about yourself."
>
<Textarea
bind:value={bio}
placeholder="I am a..."
rows={4}
/>
</Field>
<!-- Textarea with Character Counter -->
<Field
label="Description"
description="Maximum 200 characters"
>
<Textarea
bind:value={bio}
placeholder="Enter your text..."
maxLength={200}
showCount
/>
</Field>
<!-- Auto-Resize Textarea -->
<Field
label="Notes"
description="Automatically grows with content"
>
<Textarea
placeholder="Start typing..."
autoResize
minRows={2}
maxRows={6}
/>
</Field>
Selection Components
Select
Important: Use the new simplified Select component API:
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { Select } from "@kareyes/aether";
let country = $state('');
const countries = [
{ value: 'us', label: 'United States' },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'ca', label: 'Canada' },
{ value: 'au', label: 'Australia' },
];
</script>
<!-- Basic Select -->
<Field
label="Country"
description="Select your country of residence."
required
>
<Select
bind:value={country}
placeholder="Select a country..."
options={countries}
/>
</Field>
<!-- Select with Error -->
<Field
label="Country"
error="Please select a country"
required
>
<Select
bind:value={country}
placeholder="Select a country..."
options={countries}
error={true}
/>
</Field>
CheckboxGroup
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { CheckboxGroup } from "@kareyes/aether";
import type { CheckboxGroupOption } from "@kareyes/aether";
let selectedFeatures = $state<string[]>([]);
const featureOptions: CheckboxGroupOption[] = [
{
id: 'feature-api',
label: 'API Access',
value: 'api',
description: 'Access to REST API'
},
{
id: 'feature-analytics',
label: 'Analytics Dashboard',
value: 'analytics',
description: 'View detailed analytics'
},
{
id: 'feature-export',
label: 'Data Export',
value: 'export',
description: 'Export data to CSV/JSON'
},
];
</script>
<!-- Basic CheckboxGroup -->
<Field
label="Select Features"
description="Choose the features you want to enable for your account."
>
<CheckboxGroup
options={featureOptions}
bind:values={selectedFeatures}
/>
</Field>
<!-- CheckboxGroup with Horizontal Layout -->
<Field
label="Interests"
description="Select your areas of interest"
>
<CheckboxGroup
options={featureOptions}
bind:values={selectedFeatures}
orientation="horizontal"
/>
</Field>
<!-- CheckboxGroup with Error -->
<Field
label="User Permissions"
description="Select at least one permission to continue."
required
error={selectedFeatures.length === 0 ? "Please select at least one permission" : undefined}
>
<CheckboxGroup
options={featureOptions}
bind:values={selectedFeatures}
error={selectedFeatures.length === 0}
required
/>
</Field>
RadioGroup
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { RadioGroup } from "@kareyes/aether";
import type { RadioGroupOption } from "@kareyes/aether";
let selectedPlan = $state('basic');
const planOptions: RadioGroupOption[] = [
{
id: 'plan-free',
label: 'Free Plan',
value: 'free',
description: 'Basic features for personal use - $0/month'
},
{
id: 'plan-basic',
label: 'Basic Plan',
value: 'basic',
description: 'Essential features for small teams - $9/month'
},
{
id: 'plan-pro',
label: 'Pro Plan',
value: 'pro',
description: 'Advanced features for professionals - $29/month'
},
];
</script>
<!-- Basic RadioGroup -->
<Field
label="Choose Your Plan"
description="Select the subscription plan that best fits your needs."
>
<RadioGroup
options={planOptions}
bind:value={selectedPlan}
/>
</Field>
<!-- RadioGroup with Horizontal Layout -->
<Field
label="Notification Method"
description="How would you like to be notified?"
>
<RadioGroup
options={planOptions}
bind:value={selectedPlan}
orientation="horizontal"
/>
</Field>
<!-- RadioGroup with Card Style -->
<Field
label="Select Cluster Type"
description="Choose how you want to run your GPU workloads."
>
<RadioGroup
options={planOptions}
bind:value={selectedPlan}
isCard={true}
/>
</Field>
<!-- RadioGroup with Error -->
<Field
label="Payment Method"
description="Select your preferred payment method."
required
error={!selectedPlan ? "Please select a payment method" : undefined}
>
<RadioGroup
options={planOptions}
bind:value={selectedPlan}
error={!selectedPlan}
required
/>
</Field>
Toggle Components
Checkbox
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { Checkbox } from "@kareyes/aether";
let acceptTerms = $state(false);
</script>
<!-- Horizontal Checkbox (Recommended) -->
<Field
label="Accept terms and conditions"
description="You must accept to continue"
orientation="horizontal"
>
<Checkbox bind:checked={acceptTerms} />
</Field>
<!-- Checkbox with Label After -->
<Field
label="Subscribe to newsletter"
description="Receive weekly updates and news"
labelPosition="after"
orientation="horizontal"
>
<Checkbox bind:checked={acceptTerms} />
</Field>
<!-- Vertical Checkbox -->
<Field
label="Marketing emails"
description="Receive promotional emails and updates"
orientation="vertical"
>
<Checkbox bind:checked={acceptTerms} />
</Field>
Switch
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { Switch } from "@kareyes/aether";
let notifications = $state(true);
</script>
<!-- Horizontal Switch (Recommended) -->
<Field
label="Enable notifications"
description="Get notified about important updates."
orientation="horizontal"
>
<Switch bind:checked={notifications} />
</Field>
<!-- Switch with Label After -->
<Field
label="Dark mode"
description="Toggle dark mode theme"
labelPosition="after"
orientation="horizontal"
>
<Switch bind:checked={notifications} />
</Field>
<!-- Disabled Switch -->
<Field
label="Beta features"
description="Not available in your plan"
disabled
orientation="horizontal"
>
<Switch checked={false} disabled />
</Field>
Specialized Input Components
InputOTP
Important: Use the new simplified InputOTP component API:
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { InputOTP } from "@kareyes/aether";
let otpCode = $state('');
</script>
<!-- Basic InputOTP -->
<Field
label="Verification Code"
description="Enter the 6-digit code sent to your email"
>
<InputOTP maxlength={6} groups={2} bind:value={otpCode} />
</Field>
<!-- InputOTP for PIN -->
<Field
label="PIN Code"
description="Enter your 4-digit PIN"
required
>
<InputOTP maxlength={4} groups={2} />
</Field>
NumberSpinner
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { NumberSpinner } from "@kareyes/aether";
let quantity = $state(1);
</script>
<!-- Basic NumberSpinner -->
<Field
label="Quantity"
description="Select the number of items"
>
<NumberSpinner bind:value={quantity} min={1} max={100} />
</Field>
<!-- NumberSpinner with Step -->
<Field
label="Amount"
description="Increments of $10"
>
<NumberSpinner bind:value={quantity} min={0} max={1000} step={10} />
</Field>
<!-- Required NumberSpinner -->
<Field
label="Age"
description="Your age in years"
required
>
<NumberSpinner value={25} min={18} max={120} />
</Field>
Slider
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { Slider } from "@kareyes/aether";
let volume = $state(50);
let priceRange = $state([100, 500]);
</script>
<!-- Single Value Slider -->
<Field
label="Volume"
description={`Current volume: ${volume}%`}
>
<Slider type="single" bind:value={volume} min={0} max={100} step={1} />
</Field>
<!-- Range Slider -->
<Field
label="Price Range"
description={`$${priceRange[0]} - $${priceRange[1]}`}
>
<Slider type="multiple" bind:value={priceRange} min={0} max={1000} step={10} />
</Field>
FileInput
Important: Use the new simplified FileInput component API:
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { FileInput } from "@kareyes/aether";
let uploadedFiles = $state<File[]>([]);
</script>
<!-- Regular File Input -->
<Field
label="Upload Document"
description="PDF or Word document (max 5MB)"
>
<FileInput
mode="regular"
validation={{
maxFiles: 3,
acceptedTypes: ['.doc', '.docx', '.txt']
}}
/>
</Field>
<!-- Drag & Drop File Input -->
<Field
label="Upload Images"
description="Drag and drop or click to browse"
>
<FileInput
mode="drag-drop"
validation={{
maxSize: 10 * 1024 * 1024,
acceptedTypes: ['image/*', '.pdf'],
}}
dragDropProps={{
label: "Drop images or PDFs here",
description: "Max 10MB per file",
showFileList: true
}}
/>
</Field>
<!-- File Input with Error -->
<Field
label="Upload document"
error="File size exceeds 5MB limit"
>
<FileInput
mode="regular"
validation={{
maxSize: 5 * 1024 * 1024,
acceptedTypes: ['application/pdf']
}}
/>
</Field>
DatePicker
<script lang="ts">
import { Field } from "@kareyes/aether";
import { DatePicker } from "@kareyes/aether";
import type { DateValue } from '@internationalized/date';
let deliveryDate = $state<DateValue | undefined>(undefined);
</script>
<!-- Basic DatePicker -->
<Field
label="Date of Birth"
description="Select your birth date"
>
<DatePicker />
</Field>
<!-- DatePicker with Binding -->
<Field
label="Delivery Date"
description="Choose your preferred delivery date"
required
>
<DatePicker bind:value={deliveryDate} />
</Field>
Field Orientations
The orientation prop controls the layout of the field components.
Vertical Layout (Default)
Best for most form fields. Stacks label, control, and description vertically.
<Field
label="Full Name"
description="Enter your full legal name"
orientation="vertical"
>
<Input placeholder="John Doe" />
</Field>
Horizontal Layout
Places label and control side by side. Ideal for Checkbox, Switch, and compact forms.
<Field
label="Enable notifications"
description="Receive email updates"
orientation="horizontal"
>
<Switch bind:checked={notifications} />
</Field>
Responsive Layout
Automatically switches between vertical (mobile) and horizontal (desktop) based on container width. Requires a container query container.
<div class="@container">
<Field
label="Name"
description="Enter your full name"
orientation="responsive"
>
<Input placeholder="John Doe" />
</Field>
</div>
Label Position
The labelPosition prop controls whether the label appears before or after the form control. This is particularly useful for Checkbox and Switch components where the control typically appears before the label.
Label Before (Default)
Default behavior where the label appears above or to the left of the control.
<Field
label="Email"
description="Enter your email address"
labelPosition="before"
>
<Input type="email" placeholder="you@example.com" />
</Field>
Label After
Position the label after the form control. Best practice for Checkbox, Switch, and Radio components.
<script lang="ts">
import { Field } from "@kareyes/aether";
import { Checkbox } from "@kareyes/aether";
import { Switch } from "@kareyes/aether";
let acceptTerms = $state(false);
let enableNotifications = $state(false);
</script>
<!-- Checkbox with label after -->
<Field
label="Accept terms and conditions"
description="You must accept to continue"
labelPosition="after"
orientation="horizontal"
>
<Checkbox bind:checked={acceptTerms} />
</Field>
<!-- Switch with label after -->
<Field
label="Enable notifications"
description="Receive email updates"
labelPosition="after"
orientation="horizontal"
>
<Switch bind:checked={enableNotifications} />
</Field>
When to Use Label After
Use labelPosition="after" for:
- ✅ Checkbox controls
- ✅ Switch/Toggle controls
- ✅ Radio buttons (in some designs)
- ✅ Any control where the interactive element should appear first
Use labelPosition="before" (default) for:
- ✅ Text inputs
- ✅ Textareas
- ✅ Select dropdowns
- ✅ Date pickers
- ✅ Number inputs
- ✅ File uploads
- ✅ Most standard form controls
Label Position Examples
<script lang="ts">
import { Field } from "@kareyes/aether";
import { Checkbox } from "@kareyes/aether";
import { Switch } from "@kareyes/aether";
let newsletter = $state(false);
let marketing = $state(false);
let darkMode = $state(false);
</script>
<div class="space-y-4">
<!-- Multiple checkboxes with label after -->
<Field
label="Subscribe to newsletter"
description="Receive weekly updates and news"
labelPosition="after"
orientation="horizontal"
>
<Checkbox bind:checked={newsletter} />
</Field>
<Field
label="Receive marketing emails"
description="Promotional offers and discounts"
labelPosition="after"
orientation="horizontal"
>
<Checkbox bind:checked={marketing} />
</Field>
<!-- Switch with label after -->
<Field
label="Dark mode"
description="Toggle dark mode theme"
labelPosition="after"
orientation="horizontal"
>
<Switch bind:checked={darkMode} />
</Field>
</div>
Form Structure Components
FieldPrimitives.Set and FieldPrimitives.Group
Use FieldPrimitives.Set and FieldPrimitives.Group to create well-organized, semantically correct forms.
<script lang="ts">
import { FieldPrimitives ,Field} from "@kareyes/aether";
import { Input } from "@kareyes/aether";
import { Checkbox } from "@kareyes/aether";
import { Select } from "@kareyes/aether";
let formData = $state({
username: '',
email: '',
country: '',
newsletter: false,
});
const countries = [
{ value: 'us', label: 'United States' },
{ value: 'uk', label: 'United Kingdom' },
];
</script>
<form>
<!-- Fieldset for grouping related fields -->
<FieldPrimitives.Set>
<FieldPrimitives.Legend>Account Information</FieldPrimitives.Legend>
<FieldPrimitives.Description>
Create your account by filling out the information below.
</FieldPrimitives.Description>
<!-- Visual separator -->
<FieldPrimitives.Separator />
<!-- Group of fields with consistent spacing -->
<FieldPrimitives.Group class="gap-6">
<Field
label="Username"
description="Choose a unique username for your account."
required
>
<Input bind:value={formData.username} placeholder="johndoe" />
</Field>
<Field
label="Email"
description="We'll never share your email with anyone."
required
>
<Input type="email" bind:value={formData.email} placeholder="you@example.com" />
</Field>
<Field
label="Country"
description="Select your country of residence."
required
>
<Select bind:value={formData.country} placeholder="Select a country..." options={countries} />
</Field>
</FieldPrimitives.Group>
</FieldPrimitives.Set>
<FieldPrimitives.Set>
<FieldPrimitives.Legend>Preferences</FieldPrimitives.Legend>
<FieldPrimitives.Group class="gap-4">
<Field
label="Newsletter subscription"
description="Receive weekly updates about new features."
orientation="horizontal"
>
<Checkbox bind:checked={formData.newsletter} />
</Field>
</FieldPrimitives.Group>
</FieldPrimitives.Set>
</form>
Complete Form Examples
Account Creation Form
<script lang="ts">
import { FieldPrimitives,Field } from "@kareyes/aether";
import { Input } from "@kareyes/aether";
import { Textarea } from "@kareyes/aether";
import { Select } from "@kareyes/aether";
import { Checkbox } from "@kareyes/aether";
import { Switch } from "@kareyes/aether";
import { Button } from "@kareyes/aether";
let formData = $state({
username: '',
email: '',
password: '',
bio: '',
country: '',
newsletter: false,
notifications: true,
});
let errors = $state<Record<string, string>>({});
const countries = [
{ value: 'us', label: 'United States' },
{ value: 'uk', label: 'United Kingdom' },
{ value: 'ca', label: 'Canada' },
];
function handleSubmit(event: Event) {
event.preventDefault();
errors = {};
// Validation
if (!formData.username) {
errors.username = "Username is required";
} else if (formData.username.length < 3) {
errors.username = "Username must be at least 3 characters";
}
if (!formData.email) {
errors.email = "Email is required";
} else if (!formData.email.includes("@")) {
errors.email = "Please enter a valid email address";
}
if (!formData.password) {
errors.password = "Password is required";
} else if (formData.password.length < 8) {
errors.password = "Password must be at least 8 characters";
}
if (!formData.country) {
errors.country = "Please select a country";
}
// Submit if no errors
if (Object.keys(errors).length === 0) {
console.log('Form submitted:', formData);
}
}
</script>
<form onsubmit={handleSubmit} class="rounded-lg border bg-card p-6">
<FieldPrimitives.Set>
<FieldPrimitives.Legend>Account Information</FieldPrimitives.Legend>
<FieldPrimitives.Description>
Create your account by filling out the information below.
</FieldPrimitives.Description>
<FieldPrimitives.Separator />
<FieldPrimitives.Group class="gap-6">
<Field
label="Username"
description="Choose a unique username for your account."
required
error={errors.username}
>
<Input
bind:value={formData.username}
placeholder="johndoe"
error={!!errors.username}
required
/>
</Field>
<Field
label="Email"
description="We'll never share your email with anyone."
required
error={errors.email}
>
<Input
type="email"
bind:value={formData.email}
placeholder="you@example.com"
error={!!errors.email}
required
/>
</Field>
<Field
label="Password"
description="Must be at least 8 characters long."
required
error={errors.password}
>
<Input
type="password"
bind:value={formData.password}
placeholder="••••••••"
error={!!errors.password}
required
/>
</Field>
<Field
label="Bio"
description="Tell us a little about yourself."
>
<Textarea
bind:value={formData.bio}
placeholder="I am a..."
rows={4}
/>
</Field>
<Field
label="Country"
description="Select your country of residence."
required
error={errors.country}
>
<Select
bind:value={formData.country}
placeholder="Select a country..."
options={countries}
error={!!errors.country}
/>
</Field>
</FieldPrimitives.Group>
</FieldPrimitives.Set>
<FieldPrimitives.Set>
<FieldPrimitives.Legend>Preferences</FieldPrimitives.Legend>
<FieldPrimitives.Group class="gap-4">
<Field
label="Newsletter subscription"
description="Receive weekly updates about new features."
orientation="horizontal"
>
<Checkbox bind:checked={formData.newsletter} />
</Field>
<Field
label="Enable notifications"
description="Get notified about important updates."
orientation="horizontal"
>
<Switch bind:checked={formData.notifications} />
</Field>
</FieldPrimitives.Group>
</FieldPrimitives.Set>
<div class="flex gap-4 pt-4">
<Button type="submit">Submit</Button>
<Button type="button" variant="outline">Reset</Button>
</div>
</form>
Order Form with Advanced Components
<script lang="ts">
import { FieldPrimitives, Field } from "@kareyes/aether";
import { NumberSpinner } from "@kareyes/aether";
import { DatePicker } from "@kareyes/aether";
import { Slider } from "@kareyes/aether";
import { InputOTP } from "@kareyes/aether";
import { FileInput } from "@kareyes/aether";
import { Switch } from "@kareyes/aether";
import { Button } from "@kareyes/aether";
import type { DateValue } from '@internationalized/date';
let orderData = $state({
quantity: 1,
deliveryDate: undefined as DateValue | undefined,
volume: 50,
otpCode: '',
rushDelivery: false,
});
</script>
<form class="rounded-lg border bg-card p-6">
<FieldPrimitives.Set>
<FieldPrimitives.Legend>Place Your Order</FieldPrimitives.Legend>
<FieldPrimitives.Description>
Fill out the order form with all the necessary details
</FieldPrimitives.Description>
<FieldPrimitives.Separator />
<FieldPrimitives.Group class="gap-6">
<div class="grid gap-6 md:grid-cols-2">
<Field
label="Quantity"
description="Number of items to order"
required
>
<NumberSpinner bind:value={orderData.quantity} min={1} max={100} />
</Field>
<Field
label="Delivery Date"
description="When should we deliver?"
required
>
<DatePicker bind:value={orderData.deliveryDate} />
</Field>
</div>
<Field
label="Volume Level"
description={`Set notification volume: ${orderData.volume}%`}
>
<Slider type="single" bind:value={orderData.volume} min={0} max={100} step={1} />
</Field>
<Field
label="Verification Code"
description="Enter the code from your email"
required
>
<InputOTP maxlength={6} groups={2} bind:value={orderData.otpCode} />
</Field>
<Field
label="Upload Receipt"
description="Upload proof of payment (PDF, max 5MB)"
>
<FileInput
mode="regular"
validation={{
maxSize: 5 * 1024 * 1024,
acceptedTypes: ['application/pdf']
}}
/>
</Field>
<Field
label="Product Images"
description="Drag and drop product images"
>
<FileInput
mode="drag-drop"
validation={{
maxSize: 10 * 1024 * 1024,
acceptedTypes: ['image/*']
}}
dragDropProps={{
label: "Drop images here",
description: "Max 10MB per image",
showFileList: true
}}
/>
</Field>
<Field
label="Enable Rush Delivery"
description="Get your order within 24 hours"
orientation="horizontal"
>
<Switch bind:checked={orderData.rushDelivery} />
</Field>
</FieldPrimitives.Group>
<div class="flex gap-4 pt-4">
<Button type="submit">Place Order</Button>
<Button type="button" variant="outline">Cancel</Button>
</div>
</FieldPrimitives.Set>
</form>
Validation Patterns
Real-time Validation
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { Input } from "@kareyes/aether";
let email = $state('');
let emailError = $derived(() => {
if (!email) return '';
if (!email.includes('@')) return 'Invalid email address';
if (email.length < 5) return 'Email is too short';
return '';
});
</script>
<Field
label="Email"
description="Enter your email address"
error={emailError()}
required
>
<Input
type="email"
bind:value={email}
error={!!emailError()}
/>
</Field>
Submit Validation
<script lang="ts">
import { Field } from "@kareyes/aether";
import { Input } from "@kareyes/aether";
import { Button } from "@kareyes/aether";
let formData = $state({
email: '',
password: ''
});
let errors = $state<Record<string, string>>({});
function validate() {
const newErrors: Record<string, string> = {};
if (!formData.email) {
newErrors.email = 'Email is required';
} else if (!formData.email.includes('@')) {
newErrors.email = 'Invalid email address';
}
if (!formData.password) {
newErrors.password = 'Password is required';
} else if (formData.password.length < 8) {
newErrors.password = 'Password must be at least 8 characters';
}
return newErrors;
}
function handleSubmit(e: Event) {
e.preventDefault();
errors = validate();
if (Object.keys(errors).length === 0) {
console.log('Form submitted:', formData);
}
}
</script>
<form onsubmit={handleSubmit} class="space-y-4">
<Field
label="Email"
error={errors.email}
required
>
<Input
type="email"
bind:value={formData.email}
error={!!errors.email}
/>
</Field>
<Field
label="Password"
error={errors.password}
required
>
<Input
type="password"
bind:value={formData.password}
error={!!errors.password}
/>
</Field>
<Button type="submit">Submit</Button>
</form>
Advanced Composable Usage
For cases requiring custom layouts, use the composable parts:
<script lang="ts">
import { FieldPrimitives } from "@kareyes/aether";
import { Input } from "@kareyes/aether";
import { Badge } from "@kareyes/aether";
</script>
<!-- Custom field with badge -->
<FieldPrimitives.Root>
<div class="flex items-center justify-between">
<FieldPrimitives.Label>API Key</FieldPrimitives.Label>
<Badge variant="secondary">Optional</Badge>
</div>
<FieldPrimitives.Description>
Your secret API key for authentication
</FieldPrimitives.Description>
<Input type="password" value="sk_live_..." readonly />
</FieldPrimitives.Root>
<!-- Horizontal layout with custom spacing -->
<FieldPrimitives.Root orientation="horizontal">
<FieldPrimitives.Content>
<FieldPrimitives.Title>Enable Feature</FieldPrimitives.Title>
<FieldPrimitives.Description>
This will enable the new experimental feature
</FieldPrimitives.Description>
</FieldPrimitives.Content>
<Switch />
</FieldPrimitives.Root>
Accessibility
The Field component automatically handles:
- Label Association: Proper
forandidattributes connecting labels to form controls - ARIA Attributes: Automatically applies
aria-invalid,aria-describedby, andaria-required - Error Announcements: Screen reader announcements for validation errors
- Required Indicators: Visual asterisk (*) and semantic
requiredattributes - Disabled State: Proper propagation of disabled state to all child elements
- Focus Management: Full keyboard navigation support for all form controls
- Semantic HTML: Uses proper
<fieldset>,<legend>, and grouping elements
Best Practices
Component-Specific Recommendations
InputOTP: Always use
Fieldwrapper with clear description of expected format<Field label="Verification Code" description="Enter the 6-digit code sent to your email" > <InputOTP maxlength={6} groups={2} bind:value={code} /> </Field>Select: Use the simplified API with
optionsarray anderrorprop<Field label="Country" error={errors.country}> <Select bind:value={country} options={countries} error={!!errors.country} /> </Field>FileInput: Always specify validation rules and use appropriate mode
<Field label="Upload Document" error={errors.file}> <FileInput mode="drag-drop" validation={{ maxSize: 5 * 1024 * 1024, acceptedTypes: ['.pdf'] }} /> </Field>CheckboxGroup & RadioGroup: Use with proper type definitions
<Field label="Features" error={errors.features}> <CheckboxGroup options={featureOptions} bind:values={selected} error={!!errors.features} /> </Field>
General Guidelines
- Always Use Labels: Every form control should have a descriptive label
- Provide Helpful Descriptions: Guide users on what input is expected
- Clear Error Messages: Be specific about validation requirements
- Mark Required Fields: Consistently use the
requiredprop - Appropriate Orientations:
- Vertical for text inputs, selects, textareas
- Horizontal for checkboxes, switches
- Label Position:
labelPosition="after"for Checkbox, SwitchlabelPosition="before"(default) for all other inputs
- Group Related Fields: Use
FieldPrimitives.SetandFieldPrimitives.Groupfor logical organization - Validation Strategy: Choose between real-time and submit validation based on UX needs
Component Compatibility Matrix
| Component | Recommended Orientation | Label Position | Error Support | Notes |
|---|---|---|---|---|
| Input | vertical |
before |
✅ | Supports all variants and masks |
| Textarea | vertical |
before |
✅ | Use showCount for character limits |
| Select | vertical |
before |
✅ | Use simplified API with options |
| Checkbox | horizontal |
after |
✅ | Set both orientation and labelPosition |
| Switch | horizontal |
after |
✅ | Set both orientation and labelPosition |
| CheckboxGroup | vertical |
before |
✅ | Has internal orientation option |
| RadioGroup | vertical |
before |
✅ | Has internal orientation and isCard |
| Slider | vertical |
before |
❌ | Use description for value display |
| InputOTP | vertical |
before |
✅ | Always specify maxlength and groups |
| NumberSpinner | vertical |
before |
✅ | Set min, max, and step |
| FileInput | vertical |
before |
✅ | Specify mode and validation |
| DatePicker | vertical |
before |
✅ | Use with DateValue type |
Migration from Old API
If you're using the old Field component API, here's how to migrate:
Old API (Deprecated)
<Field label="Email" description="Enter email">
<Input type="email" bind:value={email} />
</Field>
New API (Recommended)
<Field label="Email" description="Enter email">
<Input type="email" bind:value={email} />
</Field>
Composable API (Advanced)
<FieldPrimitives.Root>
<FieldPrimitives.Label>Email</FieldPrimitives.Label>
<FieldPrimitives.Description>Enter email</FieldPrimitives.Description>
<Input type="email" bind:value={email} />
</FieldPrimitives.Root>
Related Components
- Input: Text input with variants, sizes, masks, and addons
- Textarea: Multi-line text input with auto-resize and character counter
- Checkbox: Single checkbox with variants and sizes
- CheckboxGroup: Multiple checkbox selection with options array
- Switch: Toggle switch with variants and sizes
- Select: Simplified dropdown with options array (new API)
- RadioGroup: Single selection radio group with options array
- Slider: Single and range sliders
- FileInput: File upload with regular and drag-drop modes
- DatePicker: Date selection with calendar
- InputOTP: One-time password / PIN input
- NumberSpinner: Numeric input with increment/decrement buttons
Troubleshooting
Error not showing
Make sure you're passing both the error prop to Field and setting error={true} on the input component:
<Field error={errors.email}>
<Input error={!!errors.email} />
</Field>
Select not working
Use the new simplified Select API with options array:
<Select bind:value={country} options={countries} />
InputOTP not grouping
Specify both maxlength and groups:
<InputOTP maxlength={6} groups={2} />
FileInput validation not working
Make sure to pass validation as an object:
<FileInput
mode="regular"
validation={{ maxSize: 5 * 1024 * 1024, acceptedTypes: ['.pdf'] }}
/>
Checkbox/Switch label on wrong side
Use both orientation="horizontal" and labelPosition="after":
<Field
orientation="horizontal"
labelPosition="after"
label="Accept terms"
>
<Checkbox />
</Field>
Summary
The Field component provides a unified, accessible way to build forms in your application. Key takeaways:
- ✅ Use
Fieldfor simplified, consistent form fields - ✅ Use composable parts (
FieldPrimitives.Root,FieldPrimitives.Label, etc.) for custom layouts - ✅ Always set appropriate
orientation(vertical/horizontal) - ✅ Use
labelPosition="after"for Checkbox and Switch - ✅ Provide clear labels, descriptions, and error messages
- ✅ Use
FieldPrimitives.SetandFieldPrimitives.Groupfor organized forms - ✅ Follow component-specific API patterns (Select, InputOTP, FileInput)
- ✅ Implement proper validation with helpful error messages