Input
A basic text input component for user data entry
Basic Input
Standard input without addons
Code Svelte
1
2<script lang="ts">
3 import { Input, Label } from "@kareyes/aether";
4</script>
5
6<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
7 <div class="space-y-2">
8 <Label for="basic-text">Text Input</Label>
9 <Input id="basic-text" placeholder="Enter text..." />
10 </div>
11 <div class="space-y-2">
12 <Label for="basic-email">Email Input</Label>
13 <Input id="basic-email" type="email" placeholder="Enter email..." />
14 </div>
15 <div class="space-y-2">
16 <Label for="basic-password">Password Input</Label>
17 <Input id="basic-password" type="password" placeholder="Enter password..." />
18 </div>
19 <div class="space-y-2">
20 <Label for="basic-number">Number Input</Label>
21 <Input id="basic-number" type="number" placeholder="Enter number..." />
22 </div>
23</div>Icon Addons
Add icons to enhance visual clarity
Code Svelte
1
2<script lang="ts">
3 import { Input, Label, InputGroupPrimitives } from "@kareyes/aether";
4 import { Search, Mail, DollarSign, Lock, Eye, EyeOff } from "@kareyes/aether/icons";
5
6 const { InputGroupButton } = InputGroupPrimitives;
7
8 let searchValue = $state("");
9 let emailValue = $state("");
10 let priceValue = $state("");
11 let passwordValue = $state("");
12 let showPassword = $state(false);
13</script>
14
15<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
16 <!-- Start Icon -->
17 <Input bind:value={searchValue} placeholder="Search...">
18 {#snippet startIcon()}<Search class="size-4" />{/snippet}
19 </Input>
20
21 <!-- End Icon -->
22 <Input bind:value={emailValue} type="email" placeholder="Enter your email">
23 {#snippet endIcon()}<Mail class="size-4" />{/snippet}
24 </Input>
25
26 <!-- Both Icons -->
27 <Input bind:value={priceValue} type="number" placeholder="0.00">
28 {#snippet startIcon()}<DollarSign class="size-4" />{/snippet}
29 {#snippet endIcon()}<span class="text-xs text-muted-foreground">USD</span>{/snippet}
30 </Input>
31
32 <!-- Password with Toggle -->
33 <Input bind:value={passwordValue} type={showPassword ? "text" : "password"} placeholder="Enter password">
34 {#snippet startIcon()}<Lock class="size-4" />{/snippet}
35 {#snippet endButton()}
36 <InputGroupButton size="icon-xs" variant="ghost" onclick={() => (showPassword = !showPassword)}>
37 {#if showPassword}<EyeOff class="size-4" />{:else}<Eye class="size-4" />{/if}
38 </InputGroupButton>
39 {/snippet}
40 </Input>
41</div>Text Addons
Display additional text context
Code Svelte
1
2<script lang="ts">
3 import { Input, Label } from "@kareyes/aether";
4 import { User } from "@kareyes/aether/icons";
5
6 let usernameValue = $state("");
7</script>
8
9<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
10 <!-- URL Prefix -->
11 <Input placeholder="example.com" startText="https://" endText=".com" />
12
13 <!-- Currency -->
14 <Input type="number" placeholder="0.00" startText="$" endText="USD" />
15
16 <!-- Email Domain -->
17 <Input bind:value={usernameValue} placeholder="username" endText="@company.com">
18 {#snippet startIcon()}<User class="size-4" />{/snippet}
19 </Input>
20
21 <!-- Unit Suffix -->
22 <Input type="number" placeholder="0" endText="kg" />
23</div>Button Addons
Add interactive buttons for actions
Code Svelte
1
2<script lang="ts">
3 import { Input, Label, InputGroupPrimitives } from "@kareyes/aether";
4 import { Search, Copy, Check, Send } from "@kareyes/aether/icons";
5
6 const { InputGroupButton } = InputGroupPrimitives;
7
8 let urlValue = $state("https://example.com");
9 let copiedUrl = $state(false);
10 let messageValue = $state("");
11 let searchValue = $state("");
12
13 function handleCopy() {
14 navigator.clipboard.writeText(urlValue);
15 copiedUrl = true;
16 setTimeout(() => (copiedUrl = false), 2000);
17 }
18</script>
19
20<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
21 <!-- Copy Button -->
22 <Input bind:value={urlValue} readonly>
23 {#snippet endButton()}
24 <InputGroupButton size="icon-xs" onclick={handleCopy}>
25 {#if copiedUrl}<Check class="size-4 text-green-500" />{:else}<Copy class="size-4" />{/if}
26 </InputGroupButton>
27 {/snippet}
28 </Input>
29
30 <!-- Search Button -->
31 <Input placeholder="Type to search...">
32 {#snippet startIcon()}<Search class="size-4" />{/snippet}
33 {#snippet endButton()}
34 <InputGroupButton size="xs" variant="default">Search</InputGroupButton>
35 {/snippet}
36 </Input>
37
38 <!-- Send Message -->
39 <Input bind:value={messageValue} placeholder="Type your message...">
40 {#snippet endButton()}
41 <InputGroupButton size="icon-xs" variant="default" disabled={!messageValue}>
42 <Send class="size-4" />
43 </InputGroupButton>
44 {/snippet}
45 </Input>
46
47 <!-- Clear Button -->
48 <Input bind:value={searchValue} placeholder="Type something...">
49 {#if searchValue}
50 {#snippet endButton()}
51 <InputGroupButton size="icon-xs" variant="ghost" onclick={() => (searchValue = "")}>
52 <span class="text-lg">x</span>
53 </InputGroupButton>
54 {/snippet}
55 {/if}
56 </Input>
57</div>Input Masks
Automatic formatting with input masks
Code Svelte
1
2<script lang="ts">
3 import { Input, Label } from "@kareyes/aether";
4 import { Phone, CreditCard } from "@kareyes/aether/icons";
5</script>
6
7<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
8 <!-- Phone -->
9 <Input mask="phone" placeholder="(555) 555-5555">
10 {#snippet startIcon()}<Phone class="size-4" />{/snippet}
11 </Input>
12
13 <!-- Credit Card -->
14 <Input mask="creditCard" placeholder="1234 5678 9012 3456">
15 {#snippet startIcon()}<CreditCard class="size-4" />{/snippet}
16 </Input>
17
18 <!-- SSN -->
19 <Input mask="ssn" placeholder="123-45-6789" />
20
21 <!-- Date -->
22 <Input mask="date" placeholder="MM/DD/YYYY" />
23</div>Combined Features
Mix addons with masks and other features
Code Svelte
1
2<script lang="ts">
3 import { Input, Label, InputGroupPrimitives } from "@kareyes/aether";
4 import { Phone, Lock, Copy } from "@kareyes/aether/icons";
5
6 const { InputGroupButton } = InputGroupPrimitives;
7</script>
8
9<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
10 <!-- Phone with Icon and Mask -->
11 <Input mask="phone" placeholder="(555) 555-5555">
12 {#snippet startIcon()}<Phone class="size-4" />{/snippet}
13 {#snippet endButton()}
14 <InputGroupButton size="icon-xs" variant="ghost">
15 <Copy class="size-4" />
16 </InputGroupButton>
17 {/snippet}
18 </Input>
19
20 <!-- Price with Icons and Text -->
21 <Input type="number" placeholder="0.00" startText="$">
22 {#snippet endIcon()}<Lock class="size-4 text-green-600" />{/snippet}
23 {#snippet endText()}<span class="text-xs text-muted-foreground">Secure</span>{/snippet}
24 </Input>
25</div>Input States
Different input states with addons
Please enter a valid email address
Code Svelte
1
2<script lang="ts">
3 import { Input, Label } from "@kareyes/aether";
4 import { Mail, Lock, Eye } from "@kareyes/aether/icons";
5</script>
6
7<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
8 <!-- Error State -->
9 <Input value="invalid@" error={true} placeholder="Enter email">
10 {#snippet startIcon()}<Mail class="size-4" />{/snippet}
11 </Input>
12
13 <!-- Disabled State -->
14 <Input value="Disabled input" disabled>
15 {#snippet startIcon()}<Lock class="size-4" />{/snippet}
16 </Input>
17
18 <!-- Readonly State -->
19 <Input value="Read-only value" readonly>
20 {#snippet endIcon()}<Eye class="size-4" />{/snippet}
21 </Input>
22
23 <!-- Loading State -->
24 <Input value="Loading..." loading />
25
26</div>Custom Addons
Use custom snippets for complete control
Code Svelte
1
2<script lang="ts">
3 import { Input, InputGroupPrimitives } from "@kareyes/aether";
4 import { Send } from "@kareyes/aether/icons";
5
6 const { InputGroupButton } = InputGroupPrimitives;
7</script>
8
9<div class="grid grid-cols-1 gap-6 max-w-2xl">
10 <!-- Custom Start Addon -->
11 <Input placeholder="Enter value">
12 {#snippet startAddon()}
13 <div class="flex items-center gap-2">
14 <div class="size-2 rounded-full bg-green-500 animate-pulse"></div>
15 <span class="text-xs font-medium">Live</span>
16 </div>
17 {/snippet}
18 </Input>
19
20 <!-- Custom End Addon -->
21 <Input placeholder="Type your message...">
22 {#snippet endAddon()}
23 <div class="flex items-center gap-1">
24 <InputGroupButton size="icon-xs" variant="ghost"><span>😀</span></InputGroupButton>
25 <InputGroupButton size="icon-xs" variant="default"><Send class="size-4" /></InputGroupButton>
26 </div>
27 {/snippet}
28 </Input>
29</div>Using Input with Field Component
Recommended pattern for forms with labels, descriptions, and error handling
We'll never share your email
Choose a unique username
Must be at least 8 characters
Enter your phone number
Enter the product price
Your secret API key (read-only)
Code Svelte
1
2<script lang="ts">
3 import { Input, Field, Button, InputGroupPrimitives } from "@kareyes/aether";
4 import { Mail, User, Lock, Eye, EyeOff, Phone, Copy } from "@kareyes/aether/icons";
5
6 const { InputGroupButton } = InputGroupPrimitives;
7
8 let formData = $state({
9 email: "", password: "", username: "",
10 phone: "", price: "", apiKey: "sk_live_1234567890abcdef",
11 });
12 let errors = $state<Record<string, string>>({});
13 let showFormPassword = $state(false);
14
15 function validateForm() {
16 errors = {};
17 if (!formData.email) errors.email = "Email is required";
18 if (!formData.password) errors.password = "Password is required";
19 if (!formData.username) errors.username = "Username is required";
20 }
21</script>
22
23<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
24 <Field label="Email" description="We'll never share your email" required error={errors.email}>
25 <Input type="email" bind:value={formData.email} placeholder="you@example.com" error={!!errors.email}>
26 {#snippet startIcon()}<Mail class="size-4" />{/snippet}
27 </Input>
28 </Field>
29
30 <Field label="Username" description="Choose a unique username" required error={errors.username}>
31 <Input bind:value={formData.username} placeholder="johndoe" error={!!errors.username}>
32 {#snippet startIcon()}<User class="size-4" />{/snippet}
33 </Input>
34 </Field>
35
36 <Field label="Password" description="Must be at least 8 characters" required error={errors.password}>
37 <Input type={showFormPassword ? "text" : "password"} bind:value={formData.password} placeholder="••••••••" error={!!errors.password}>
38 {#snippet startIcon()}<Lock class="size-4" />{/snippet}
39 {#snippet endButton()}
40 <InputGroupButton size="icon-xs" variant="ghost" onclick={() => (showFormPassword = !showFormPassword)}>
41 {#if showFormPassword}<EyeOff class="size-4" />{:else}<Eye class="size-4" />{/if}
42 </InputGroupButton>
43 {/snippet}
44 </Input>
45 </Field>
46
47 <Field label="Phone Number" description="Enter your phone number">
48 <Input bind:value={formData.phone} mask="phone" placeholder="(555) 555-5555">
49 {#snippet startIcon()}<Phone class="size-4" />{/snippet}
50 </Input>
51 </Field>
52</div>
53<Button onclick={validateForm}>Validate Form</Button>Complete Form with Field Component
Full form using Field.Set and Field.Group for proper structure
Code Svelte
1
2<script lang="ts">
3 import { Input, Field, FieldPrimitives, Button, InputGroupPrimitives } from "@kareyes/aether";
4 import { Mail, User, Lock, Eye, EyeOff, Phone, CreditCard } from "@kareyes/aether/icons";
5
6 const { InputGroupButton } = InputGroupPrimitives;
7
8 let formData = $state({ email: "", password: "", username: "", phone: "" });
9 let errors = $state<Record<string, string>>({});
10 let showFormPassword = $state(false);
11</script>
12
13<form onsubmit={(e) => { e.preventDefault(); }}>
14 <FieldPrimitives.Set>
15 <FieldPrimitives.Legend>Create Account</FieldPrimitives.Legend>
16 <FieldPrimitives.Description>Enter your details to create a new account</FieldPrimitives.Description>
17 <FieldPrimitives.Separator />
18 <FieldPrimitives.Group class="gap-6">
19 <Field label="Email Address" required error={errors.email}>
20 <Input type="email" bind:value={formData.email} placeholder="you@example.com" error={!!errors.email}>
21 {#snippet startIcon()}<Mail class="size-4" />{/snippet}
22 </Input>
23 </Field>
24
25 <Field label="Username" required error={errors.username}>
26 <Input bind:value={formData.username} placeholder="johndoe" error={!!errors.username}>
27 {#snippet startIcon()}<User class="size-4" />{/snippet}
28 </Input>
29 </Field>
30
31 <Field label="Password" required error={errors.password}>
32 <Input type={showFormPassword ? "text" : "password"} bind:value={formData.password} placeholder="••••••••">
33 {#snippet startIcon()}<Lock class="size-4" />{/snippet}
34 {#snippet endButton()}
35 <InputGroupButton size="icon-xs" variant="ghost" onclick={() => (showFormPassword = !showFormPassword)}>
36 {#if showFormPassword}<EyeOff class="size-4" />{:else}<Eye class="size-4" />{/if}
37 </InputGroupButton>
38 {/snippet}
39 </Input>
40 </Field>
41
42 <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
43 <Field label="Phone Number">
44 <Input bind:value={formData.phone} mask="phone" placeholder="(555) 555-5555">
45 {#snippet startIcon()}<Phone class="size-4" />{/snippet}
46 </Input>
47 </Field>
48 <Field label="Credit Card">
49 <Input mask="creditCard" placeholder="1234 5678 9012 3456">
50 {#snippet startIcon()}<CreditCard class="size-4" />{/snippet}
51 </Input>
52 </Field>
53 </div>
54 </FieldPrimitives.Group>
55
56 <div class="flex gap-4 pt-4">
57 <Button type="submit">Create Account</Button>
58 <Button type="button" variant="outline">Cancel</Button>
59 </div>
60 </FieldPrimitives.Set>
61</form>Basic Usage
<script lang="ts">
import { Input } from "@kareyes/aether";
let value = $state("");
</script>
<Input bind:value placeholder="Enter text..." />
With Primitives Import
<script lang="ts">
import { InputPrimitives } from "@kareyes/aether";
</script>
<InputPrimitives.Root>
<InputPrimitives.Input placeholder="Enter text..." />
</InputPrimitives.Root>
Props Reference
Base Props
| Prop | Type | Default | Description |
|---|---|---|---|
value |
string |
- | Bindable input value |
type |
InputType |
"text" |
Input type |
variant |
InputVariant |
"default" |
Visual variant |
size |
InputSize |
"default" |
Input size |
mask |
MaskType | MaskPattern |
- | Input mask configuration |
error |
boolean |
false |
Error state |
disabled |
boolean |
false |
Disabled state |
readonly |
boolean |
false |
Readonly state |
placeholder |
string |
- | Placeholder text |
class |
string |
- | Additional CSS classes |
Addon Props
| Prop | Type | Default | Description |
|---|---|---|---|
startIcon |
Snippet |
- | Icon snippet at the start |
endIcon |
Snippet |
- | Icon snippet at the end |
startText |
string | Snippet |
- | Text at the start |
endText |
string | Snippet |
- | Text at the end |
startButton |
Snippet |
- | Button snippet at the start |
endButton |
Snippet |
- | Button snippet at the end |
startAddon |
Snippet |
- | Custom start addon |
endAddon |
Snippet |
- | Custom end addon |
groupClassName |
string |
- | Classes for InputGroup wrapper |
Variants
<Input variant="default" placeholder="Default" />
<Input variant="outline" placeholder="Outline" />
<Input variant="filled" placeholder="Filled" />
<Input variant="ghost" placeholder="Ghost" />
<Input variant="underline" placeholder="Underline" />
Examples
Icon Addons
<script lang="ts">
import { Input } from "@kareyes/aether";
import SearchIcon from "@lucide/svelte/icons/search";
import MailIcon from "@lucide/svelte/icons/mail";
</script>
<!-- Start Icon -->
<Input placeholder="Search...">
{#snippet startIcon()}
<SearchIcon class="size-4" />
{/snippet}
</Input>
<!-- End Icon -->
<Input type="email" placeholder="Enter email">
{#snippet endIcon()}
<MailIcon class="size-4" />
{/snippet}
</Input>
<!-- Both Icons -->
<Input placeholder="Secure input">
{#snippet startIcon()}
<LockIcon class="size-4" />
{/snippet}
{#snippet endIcon()}
<span class="text-xs text-muted-foreground">Encrypted</span>
{/snippet}
</Input>
Text Addons
<!-- String Text -->
<Input
placeholder="example.com"
startText="https://"
endText=".com"
/>
<!-- Currency -->
<Input
type="number"
placeholder="0.00"
startText="$"
endText="USD"
/>
<!-- Email Domain -->
<Input
placeholder="username"
endText="@company.com"
/>
Button Addons
<script lang="ts">
import { Input, InputGroupPrimitives } from "@kareyes/aether";
import CopyIcon from "@lucide/svelte/icons/copy";
import EyeIcon from "@lucide/svelte/icons/eye";
import EyeOffIcon from "@lucide/svelte/icons/eye-off";
let showPassword = $state(false);
</script>
<!-- Copy Button -->
<Input readonly value="https://example.com">
{#snippet endButton()}
<InputGroupPrimitives.Button size="icon-xs" onclick={() => navigator.clipboard.writeText('https://example.com')}>
<CopyIcon class="size-4" />
</InputGroupPrimitives.Button>
{/snippet}
</Input>
<!-- Password Toggle -->
<Input
type={showPassword ? "text" : "password"}
placeholder="Enter password"
>
{#snippet endButton()}
<InputGroupPrimitives.Button
size="icon-xs"
variant="ghost"
onclick={() => showPassword = !showPassword}
>
{#if showPassword}
<EyeOffIcon class="size-4" />
{:else}
<EyeIcon class="size-4" />
{/if}
</InputGroupPrimitives.Button>
{/snippet}
</Input>
With Input Masks
<script lang="ts">
import { Input } from "@kareyes/aether";
import PhoneIcon from "@lucide/svelte/icons/phone";
import CreditCardIcon from "@lucide/svelte/icons/credit-card";
</script>
<!-- Phone Number -->
<Input
mask="phone"
placeholder="(555) 555-5555"
>
{#snippet startIcon()}
<PhoneIcon class="size-4" />
{/snippet}
</Input>
<!-- Credit Card -->
<Input
mask="creditCard"
placeholder="1234 5678 9012 3456"
>
{#snippet startIcon()}
<CreditCardIcon class="size-4" />
{/snippet}
</Input>
States
<!-- Error State -->
<Input
value="invalid@"
error={true}
placeholder="Enter email"
>
{#snippet startIcon()}
<MailIcon class="size-4" />
{/snippet}
</Input>
<!-- Disabled -->
<Input
value="Disabled"
disabled
startText="$"
/>
<!-- Readonly -->
<Input
value="Read-only"
readonly
>
{#snippet endIcon()}
<LockIcon class="size-4" />
{/snippet}
</Input>
With Field Component
<script lang="ts">
import { Field, Input } from "@kareyes/aether";
import MailIcon from "@lucide/svelte/icons/mail";
import LockIcon from "@lucide/svelte/icons/lock";
let email = $state('');
let password = $state('');
</script>
<!-- Email with Icon -->
<Field
label="Email"
description="We'll never share your email"
required
>
<Input type="email" bind:value={email} placeholder="you@example.com">
{#snippet startIcon()}
<MailIcon class="size-4" />
{/snippet}
</Input>
</Field>
<!-- Password with Icon -->
<Field
label="Password"
description="Must be at least 8 characters"
required
>
<Input type="password" bind:value={password} placeholder="••••••••">
{#snippet startIcon()}
<LockIcon class="size-4" />
{/snippet}
</Input>
</Field>
With Validation
<script lang="ts">
import { Field, Input } from "@kareyes/aether";
let email = $state('');
let errors = $state<Record<string, string>>({});
function validateEmail() {
if (!email) {
errors.email = 'Email is required';
} else if (!email.includes('@')) {
errors.email = 'Please enter a valid email address';
} else {
delete errors.email;
}
}
</script>
<Field
label="Email"
required
error={errors.email}
>
<Input
type="email"
bind:value={email}
placeholder="you@example.com"
error={!!errors.email}
onblur={validateEmail}
/>
</Field>
Complete Form Example
<script lang="ts">
import { Field, Input, Button } from "@kareyes/aether";
import MailIcon from "@lucide/svelte/icons/mail";
import LockIcon from "@lucide/svelte/icons/lock";
import UserIcon from "@lucide/svelte/icons/user";
import PhoneIcon from "@lucide/svelte/icons/phone";
let formData = $state({
username: '',
email: '',
password: '',
phone: '',
});
let errors = $state<Record<string, string>>({});
function handleSubmit(e: Event) {
e.preventDefault();
errors = {};
if (!formData.username) errors.username = 'Username is required';
if (!formData.email) errors.email = 'Email is required';
if (!formData.password) errors.password = 'Password is required';
if (!formData.phone) errors.phone = 'Phone is required';
if (Object.keys(errors).length === 0) {
console.log('Form submitted:', formData);
}
}
</script>
<form onsubmit={handleSubmit} class="space-y-6">
<Field
label="Username"
required
error={errors.username}
>
<Input
bind:value={formData.username}
placeholder="johndoe"
error={!!errors.username}
>
{#snippet startIcon()}
<UserIcon class="size-4" />
{/snippet}
</Input>
</Field>
<Field
label="Email"
required
error={errors.email}
>
<Input
type="email"
bind:value={formData.email}
placeholder="you@example.com"
error={!!errors.email}
>
{#snippet startIcon()}
<MailIcon class="size-4" />
{/snippet}
</Input>
</Field>
<Field
label="Password"
required
error={errors.password}
>
<Input
type="password"
bind:value={formData.password}
placeholder="••••••••"
error={!!errors.password}
>
{#snippet startIcon()}
<LockIcon class="size-4" />
{/snippet}
</Input>
</Field>
<Field
label="Phone Number"
required
error={errors.phone}
>
<Input
bind:value={formData.phone}
mask="phone"
placeholder="(555) 555-5555"
error={!!errors.phone}
>
{#snippet startIcon()}
<PhoneIcon class="size-4" />
{/snippet}
</Input>
</Field>
<Button type="submit">Create Account</Button>
</form>
Accessibility
The Input component follows accessibility best practices:
ARIA Attributes
aria-invalidset when error state is truearia-disabledset when disabled- Proper label association through Field component
Best Practices
- Use Field component: Always wrap inputs with Field for labels and descriptions
- Consistent icon sizing: Use
class="size-4"for icons - Button accessibility: Include
aria-labelon icon buttons - Error handling: Use
errorprop combined with Field error messages - Placeholder text: Provide descriptive placeholder text