Input
Wraps a native input and forwards its attributes, so type, required, pattern, and autocomplete work as usual. Choose the outline or secondary variant.
Installation
Copy the command below and run it in your terminal.
bunx @sivir-ui/svelte add inputpnpm dlx @sivir-ui/svelte add inputnpx @sivir-ui/svelte add inputyarn dlx @sivir-ui/svelte add inputUsage
Bind value to read the text. Setting label wraps the field in a <label>, so you don't need a
separate one.
import { Input } from '@sivir-ui/svelte/components/input';
let email = $state('');
<Input bind:value={email} type="email" label="Email" placeholder="you@example.com" />Examples
Adornments
<script lang="ts">
import GitBranch from '@lucide/svelte/icons/git-branch';
import { Input } from '@sivir-ui/svelte/components/input';
</script>
<div class="w-full max-w-xs">
<Input label="Repository" placeholder="sivir-ui">
{#snippet leading()}
<GitBranch />
{/snippet}
{#snippet trailing()}
<span>.git</span>
{/snippet}
</Input>
</div>
The leading and trailing snippets render inside the field border and ignore pointer events. Use them for icons,
units, or a fixed suffix. They are ignored for checkbox, file, range, and other non-text
types.
Validation
<script lang="ts">
import { Button } from '@sivir-ui/svelte/components/button';
import { Input } from '@sivir-ui/svelte/components/input';
let email = $state('');
let workspaceSlug = $state('');
let emailInput = $state<HTMLInputElement>();
let workspaceSlugInput = $state<HTMLInputElement>();
let emailTouched = $state(false);
let workspaceSlugTouched = $state(false);
let submitted = $state(false);
const errorInputClass =
'border-[color-mix(in_srgb,var(--color-error)_70%,transparent)] shadow-[0_0_0_calc(var(--border-size)*2)_color-mix(in_srgb,var(--color-error)_25%,transparent)]';
function emailValidationMessage(
value: string,
input: HTMLInputElement | undefined,
isTouched: boolean
): string {
if (!isTouched) {
return '';
}
if (value.trim() === '') {
return 'Enter an email address.';
}
if (!input?.validity.valid) {
return 'Enter a valid email address.';
}
return '';
}
function workspaceSlugValidationMessage(
value: string,
input: HTMLInputElement | undefined,
isTouched: boolean
): string {
if (!isTouched) {
return '';
}
if (value.trim() === '') {
return 'Enter a workspace slug.';
}
if (!input?.validity.valid) {
return 'Use lowercase letters, numbers, and hyphens only.';
}
return '';
}
function validate(event: SubmitEvent) {
event.preventDefault();
emailTouched = true;
workspaceSlugTouched = true;
submitted = true;
}
let emailError = $derived(emailValidationMessage(email, emailInput, emailTouched));
let workspaceSlugError = $derived(
workspaceSlugValidationMessage(workspaceSlug, workspaceSlugInput, workspaceSlugTouched)
);
let isValid = $derived(submitted && !emailError && !workspaceSlugError);
</script>
<form class="w-full max-w-md space-y-4" novalidate onsubmit={validate}>
<div class="space-y-1.5">
<Input
label="Email"
type="email"
placeholder="you@example.com"
required
bind:value={email}
bind:element={emailInput}
onblur={() => {
emailTouched = true;
}}
aria-invalid={emailError ? 'true' : undefined}
aria-describedby={emailError ? 'email-error email-description' : 'email-description'}
aria-errormessage={emailError ? 'email-error' : undefined}
class={emailError ? errorInputClass : undefined}
/>
<div
data-state={emailError ? 'error' : undefined}
class="-mt-1.5 max-h-0 overflow-hidden opacity-0 transition-[margin,max-height,opacity] [transition-duration:220ms] ease-[var(--ease-out)] motion-reduce:transition-none data-[state=error]:mt-0 data-[state=error]:max-h-24 data-[state=error]:opacity-100"
>
<p
id="email-error"
class="text-sm font-medium text-error"
role={emailError ? 'alert' : undefined}
aria-hidden={emailError ? undefined : 'true'}
>
{emailError}
</p>
</div>
<p
id="email-description"
class="text-foreground-muted [font-size:var(--font-size-body,16px)]"
>
We'll use this to send workspace invites.
</p>
</div>
<div class="space-y-1.5">
<Input
label="Workspace slug"
placeholder="design-team"
required
pattern="[a-z0-9]+(?:-[a-z0-9]+)*"
bind:value={workspaceSlug}
bind:element={workspaceSlugInput}
onblur={() => {
workspaceSlugTouched = true;
}}
aria-invalid={workspaceSlugError ? 'true' : undefined}
aria-describedby={workspaceSlugError
? 'workspace-slug-error workspace-slug-description'
: 'workspace-slug-description'}
aria-errormessage={workspaceSlugError ? 'workspace-slug-error' : undefined}
class={workspaceSlugError ? errorInputClass : undefined}
/>
<div
data-state={workspaceSlugError ? 'error' : undefined}
class="-mt-1.5 max-h-0 overflow-hidden opacity-0 transition-[margin,max-height,opacity] [transition-duration:220ms] ease-[var(--ease-out)] motion-reduce:transition-none data-[state=error]:mt-0 data-[state=error]:max-h-24 data-[state=error]:opacity-100"
>
<p
id="workspace-slug-error"
class="text-sm font-medium text-error"
role={workspaceSlugError ? 'alert' : undefined}
aria-hidden={workspaceSlugError ? undefined : 'true'}
>
{workspaceSlugError}
</p>
</div>
<p
id="workspace-slug-description"
class="text-foreground-muted [font-size:var(--font-size-body,16px)]"
>
Lowercase letters, numbers, and hyphens only.
</p>
</div>
<div class="flex items-center gap-3">
<Button type="submit" size="md">Validate fields</Button>
{#if isValid}
<p class="text-sm text-success" role="status">Both fields are valid.</p>
{/if}
</div>
</form>
Input has no error prop. This example sets native constraints, reads validity through bind:element, and shows messages after
blur or submit.
Anatomy
Input - Accepts a single-line value.
<script lang="ts"> import { Input } from '@sivir-ui/svelte/components/input'; let email = $state(''); </script> <Input bind:value={email} label="Email" placeholder="you@example.com" />