Skip to content

Input

import { Input } from '@delightstack/components';

Types are also available:

import type { InputType, InputOption } from '@delightstack/components';
View code
<Input label="Name" placeholder="Enter your name" required />

The type prop supports 13 input types:

TypeDescription
'text'Standard text input (default)
'email'Email with validation
'password'Hidden text with optional reveal toggle
'url'URL validation
'tel'Phone number
'search'Search with clear button
'number'Numeric with increment/decrement buttons
'textarea'Multi-line text
'date'Date picker
'time'Time picker
'datetime-local'Date and time picker
'color'Color picker
'file'File selection
View code
<Input type="text" label="Username" placeholder="Enter username" />
<Input type="email" label="Email" placeholder="you@example.com" />
<Input type="url" label="Website" placeholder="https://example.com" />
<Input type="tel" label="Phone" placeholder="(555) 555-5555" />
View code
<Input type="number" label="Quantity" min={0} max={100} step={5} />

Use date, time, and datetime-local for temporal values. The label always stays floated since these controls render their own value UI. Constrain the range with min/max.

View code
<Input type="date" label="Date" bind:value={date} />
<Input type="time" label="Time" bind:value={time} />
<Input type="datetime-local" label="Date & time" bind:value={when} />

The color type pairs a live swatch with the selected hex value. Bind value to read the chosen colour.

View code
<Input type="color" label="Brand color" bind:value={color} />

Use file for uploads. Add accept to restrict file types and multiple to allow several at once. Selected files appear as a removable preview list, and image files show a thumbnail.

View code
<Input type="file" label="Attachment" />
<Input type="file" label="Photos" accept="image/*" multiple />
View code
<script>
import { Input } from '@delightstack/components';
let bio = $state('');
</script>
<Input
type="textarea"
label="Bio"
bind:value={bio}
rows={4}
auto_resize
maxlength={500}
show_counter
/>
View code
<Input
type="password"
label="Password"
bind:value={password}
show_toggle
strength_indicator
/>

Use mask to auto-format user input. # = digit, A = letter, * = any character.

View code
<Input label="Phone" mask="(###) ###-####" bind:value={phone} />
<Input label="Card Number" mask="#### #### #### ####" bind:value={card} />
<Input label="Expiry" mask="##/##" bind:value={expiry} />

Provide options for dropdown suggestions. Use onfilter for async loading.

View code
<script>
import { Input } from '@delightstack/components';
const cities = [
{ value: 'nyc', label: 'New York' },
{ value: 'la', label: 'Los Angeles' },
{ value: 'chi', label: 'Chicago' },
];
let selectedCity = $state('');
</script>
<Input label="City" options={cities} bind:value={selectedCity} />
View code
<Input
label="Search users"
onfilter={async (query) => {
const results = await searchUsers(query);
return results.map(u => ({ value: u.id, label: u.name }));
}}
bind:value={selectedUser}
/>

Enable multiple to collect an array of values as chips/tags.

View code
<script>
import { Input } from '@delightstack/components';
let tags = $state<string[]>([]);
</script>
<Input label="Tags" multiple bind:value={tags} placeholder="Add tags..." />
View code
<Input label="Price" prefix="$" suffix="USD" type="number" />
<Input label="Website" prefix="https://" placeholder="example.com" />
View code
<Input label="Disabled input" placeholder="Cannot edit" disabled />

Inputs are transparent (outlined) by default so they sit cleanly on any surface. Set filled to paint a solid surface background behind the field.

View code
<Input label="Outlined (default)" placeholder="Transparent surface" />
<Input label="Filled" placeholder="Solid surface" filled />
View code
<Input size="0" label="Small (42px)" placeholder="Small" />
<Input size="1" label="Default (48px)" placeholder="Default" />
<Input size="2" label="Medium (54px)" placeholder="Medium" />
<Input size="3" label="Large (60px)" placeholder="Large" />
View code
<Input
label="Email"
type="email"
error="Please enter a valid email address"
description="We'll never share your email"
/>
View code
<Input label="Name" skeleton={loading} bind:value={name} />

Pass a parse function to validate the value — standalone, it runs when the input is blurred, and the thrown error’s message shows below the field. While errored, the input re-validates on every keystroke so the message clears the moment the value is fixed.

Database tables expose a ready-made parse (plus name, type, label, and constraint props) for every field, so a single spread wires it all up:

<script>
import { Input } from '@delightstack/components';
import { personTable } from '$lib/schema';
let email = $state('');
</script>
<Input {...personTable.form.field.email} bind:value={email} />

Inside a <Form>, the parse function is registered with the form instead, which runs it alongside the form-level schema (the schema wins when both error). See the Working with Forms guide.

PropTypeDefaultDescription
typeInputType'text'Input type
valuestring | number | boolean | string[] | File | File[] | null-Current value, bindable
labelstring-Floating label text
placeholderstring-Placeholder text
disabledbooleanfalseDisable input
readonlybooleanfalseRead-only mode
requiredbooleanfalseMark as required
namestring-Form field name. Inside a Form, registers the field; with no value prop the input becomes context-driven (reads/writes the form data — no bind:value needed)
skeletonbooleanfalseShow skeleton loading state
tooltipstring-Tooltip text on hover
PropTypeDefaultDescription
errorstring | boolean-Error message or boolean error state
parse(value: unknown) => unknown-Validator that throws a user-showable error message. Standalone it runs on blur (re-running on change while errored); inside a Form it is registered with the form instead
patternstring-Regex pattern
minlengthnumber-Minimum length
maxlengthnumber-Maximum length
minnumber | string-Minimum value (number/date)
maxnumber | string-Maximum value (number/date)
stepnumber-Step value for number inputs
PropTypeDefaultDescription
size'0' | '1' | '2' | '3''1'Input size
prefixstring-Text before input
suffixstring-Text after input
iconComponent-Leading icon component
clearablebooleanfalseShow clear button
show_counterbooleanfalseShow character count
descriptionstring-Description text below input
densebooleanfalseTighter internal spacing
comfortablebooleanfalseMore internal spacing
filledbooleanfalsePaint a solid surface background behind the control (vs the default transparent/outlined look)
idstringautoElement ID
classstring''Additional CSS classes
PropTypeDefaultDescription
optionsInputOption[]-Suggestion options for autocomplete dropdown
onfilter(query: string) => Promise<InputOption[]>-Async filter callback
PropTypeDefaultDescription
multiplebooleanfalseEnable chips/tags mode (value becomes string[])
PropTypeDefaultDescription
rowsnumber3Initial rows for textarea
auto_resizebooleanfalseAuto-grow textarea to fit content
PropTypeDefaultDescription
show_togglebooleanfalseShow password visibility toggle
strength_indicatorbooleanfalseShow password strength meter
PropTypeDefaultDescription
maskstring-Input mask pattern (#=digit, A=letter, *=any)
PropTypeDefaultDescription
acceptstring-Accepted file types
EventDetailDescription
oninput{ value }Value is changing (during typing)
onchange{ value }Value is committed (on blur or selection)
onfocus-Input focused
onblur-Input blurred
type InputType =
| 'text' | 'email' | 'password' | 'url' | 'tel' | 'search'
| 'number' | 'textarea'
| 'date' | 'time' | 'datetime-local'
| 'color' | 'file';
interface InputOption {
value: string;
label: string;
disabled?: boolean;
description?: string;
}
  • Proper <label> association via for/id
  • Error linked with aria-describedby
  • aria-required when required
  • aria-invalid on error state
  • Full keyboard support for all modes
  • Autocomplete: screen reader announces result count, keyboard navigation with arrow keys
  • Focus ring visible on :focus-visible