Enter text, add a clear button, and connect the value to your form or state.

An input is a native text field for short values like a name, an email address or a search. It works inside a field and a form, and adds a clear button, text around the input, and change reasons.

Console
<script lang="ts">
  import { XIcon } from "@lucide/svelte"
  import { Field, FieldDescription, FieldLabel } from "@ui-sdk/svelte/field"
  import { Input, InputClear, InputGroup } from "@ui-sdk/svelte/input"
  import { useLog } from "./log"

  const log = useLog()
</script>

<Field name="website">
  <FieldLabel>Website</FieldLabel>
  <InputGroup>
    <span>https://</span>
    <Input
      placeholder="example.com"
      defaultValue="hollisgrant.com"
      onValueChange={(value, details) =>
        log(`${value ? `“${value}”` : "Empty"} (${details.reason})`)}
    />
    <InputClear aria-label="Clear website">
      <XIcon aria-hidden="true" />
    </InputClear>
  </InputGroup>
  <FieldDescription>Shown on your public profile.</FieldDescription>
</Field>
Edit the website, then press Clear website. The text clears, focus returns to the input and the console logs the reason. Pressing https:// focuses the input too.

Installation

npm install @ui-sdk/svelte @ui-sdk/core

Composition

Use the following composition to build an Input or an InputGroup:

Input

InputGroup
├── Input
└── InputClear

Input renders a native <input>. Pass its type, placeholder, autocomplete and other native attributes directly. InputGroup holds text or buttons around it, and InputClear empties it.

Usage

Import the parts and put them together:

<script lang="ts">
  import { Input, InputClear, InputGroup } from "@ui-sdk/svelte/input"
</script>

<InputGroup>
  <Input type="search" aria-label="Search" />
  <InputClear aria-label="Clear search" />
</InputGroup>

Fields

Wrap the input in a Field to give it a visible label, a description and errors. The field also supplies its name when the input has none.

<Field name="username">
  <FieldLabel>Username</FieldLabel>
  <Input autocomplete="username" />
  <FieldDescription>Choose a unique username for your account.</FieldDescription
  >
</Field>

Disabled and Read Only

Set disabled on the input or its field to prevent editing and leave it out of the tab order and the submitted values. A readOnly input can still be focused, selected and submitted.

<Field disabled>
  <FieldLabel>Email</FieldLabel>
  <Input type="email" defaultValue="hollis@example.com" />
</Field>

<Field>
  <FieldLabel>Profile URL</FieldLabel>
  <Input readOnly value="example.com/hollis" />
</Field>

Validation

Use native constraints such as required, type="email" and pattern. The field shows its error after a submit attempt and updates it as the value changes. Set invalid on the field for an error your app decides; the input gets aria-invalid. Field covers the error parts and their matches.

<Field name="email" invalid={emailTaken ? true : undefined}>
  <FieldLabel>Email</FieldLabel>
  <Input type="email" required />
  <FieldError match="valueMissing">Enter your email address.</FieldError>
  <FieldError match>That email is already in use.</FieldError>
</Field>

Input Group

Place text or buttons around an input with InputGroup. Pressing the group's text focuses the input. The text is display content, not part of the submitted value.

<InputGroup>
  <span>https://</span>
  <Input aria-label="Website" placeholder="example" />
  <span>.com</span>
</InputGroup>

Clear Button

InputClear shows while the input has text it can clear. Pressing it empties the value, reports clear-press and returns focus to the input. Controlled state and native input listeners receive the change too.

<InputGroup>
  <Input type="search" aria-label="Search people" />
  <InputClear aria-label="Clear search">
    <XIcon aria-hidden="true" />
  </InputClear>
</InputGroup>

Controlled

Bind value with bind:value to control the input. onValueChange also receives the change details, and native oninput still works.

<script lang="ts">
  let name = $state("Hollis Grant")
</script>

<Input bind:value={name} />

See State and Events for the shared controlled-state conventions. Input's reasons tell typing, clearing, resetting and controller changes apart.

Filtering What's Typed

Call details.cancel() in onValueChange to reject a change. The previous text and caret position are restored.

<Input
  inputmode="numeric"
  maxlength={6}
  onValueChange={(value, details) => {
    if (!/^\d*$/.test(value)) details.cancel()
  }}
/>

Controller

Use createInputController() for actions outside the input. Its methods update the input through the same change callbacks as typing.

<script lang="ts">
  const message = createInputController()
</script>

<Input controller={message} aria-label="Message" />
<button onclick={() => message.setValue("On my way!")}>Quick Reply</button>
<button onclick={() => message.clear()}>Clear</button>
<button onclick={() => message.focus()}>Focus</button>

Controllers covers creating and sharing a controller.

Passwords and Files

Use Password Input to show and hide a password. A native file input works with type="file"; use File Input for dropping, previews and validation.

Styling

Each part has a data-slot, such as input-group, and shows its state through the attributes listed under Data Attributes. Use data-filled, data-disabled, data-readonly and data-required on the input, and the field's validation and focus attributes inside a field. For a group's focus ring, style :has(input:focus-visible), so a focused button inside it keeps its own ring.

Data Attributes

Input

[data-slot="input"]

AttributeWhat it means
data-filledPresent while the field holds any text.
data-disabledPresent when the field is disabled, by its own prop or its Field.
data-readonlyPresent when the field is read-only.
data-requiredPresent when the field is required.

InputGroup

[data-slot="input-group"]

AttributeWhat it means
data-filledPresent while the field in the group holds any text.
data-disabledPresent while the field in the group is disabled, by itself or a <fieldset>.
data-readonlyPresent while the field in the group is read-only.

InputClear

[data-slot="input-clear"]

AttributeWhat it means
data-filledPresent while the field in the group holds any text.
data-disabledPresent while the field in the group is disabled, by itself or a <fieldset>.
data-readonlyPresent while the field in the group is read-only.

API Reference

Props and callbacks are the same as below, as Svelte props, plus every native input attribute. value can be bound with bind:value.

Input

value?string
The value, for a controlled field.
defaultValue?string
The value it starts with. A form reset returns here.
onValueChange?((value: string, details: ChangeDetails<InputValueChangeReason>) => void)
Called when the user changes the value: typing ("input-change"), a Clear part ("clear-press"), a form reset ("reset") or a controller ("imperative"). details.cancel() keeps the value as it was.
controller?object
Reads and writes the field from anywhere. Create one with createInputController().
debug?boolean
Logs every state change with its reason to the console, in development builds.
disabled?boolean
Disables the field.
readOnly?boolean
Makes the field read-only.
required?boolean
Makes the field required.

InputGroup

debug?boolean
Logs every state change with its reason to the console, in development builds.

InputClear

A <button> that empties the input in its group and focuses it. It renders only while there's text to clear.

State

filledboolean
Whether the field holds any text.
disabledboolean
Whether the field is disabled.
readOnlyboolean
Whether the field is read-only.
requiredboolean
Whether the field is required.

InputGroup and InputClear receive the state of the input inside the group.

filledboolean
Whether the field in the group holds any text.
disabledboolean
Whether the field in the group is disabled, by itself or by a <fieldset>.
readOnlyboolean
Whether the field in the group is read-only.

Reasons

input-changeEvent
Typing, pasting or dropping changed a text field's value; in a combobox it can also open or close it, or Enter keeps the typed text as the value.
clear-pressMouseEvent | KeyboardEvent
A Clear part was pressed, like on an input, a select or a combobox.
resetnone
A form reset.
imperativenone
A controller or api call, or a property set on a web component.

Controller

Create one with createInputController() and pass it as controller.

getValue()() => string
The current value.
setValue(value, options?)(value: string, options?: ChangeOptions<InputValueChangeReason> | undefined) => boolean
Sets the value as if the user typed it, so input fires. Returns false when the change was canceled or nothing changed.
clear(options?)(options?: ChangeOptions<InputValueChangeReason> | undefined) => boolean
Empties the field, as if the user deleted the text, so input fires. Returns false when the change was canceled or nothing changed.
focus()() => void
Moves focus to the field.

Accessibility

Give the input a visible field label or an aria-label, and give the clear button an accessible name. The native input keeps standard text editing, selection, autocomplete and form behavior.

  • The label names the input and the description describes it.
  • Clicking the label focuses the input.
  • An invalid input is marked once the form tries to submit.
  • Data-filled follows the text.
  • An input group's clear button shows only while there's text.
  • <ui-input>'s value property writes the field quietly, and its api reports. Web components only.
Enhancements
  • Changes report their reason: typing, then a reset.
  • A canceled change keeps the text as it was.
  • A canceled change keeps a controlled input's text as it was.
  • A canceled change waits for an IME to finish before the text goes back.
  • A controlled input keeps every keystroke.
  • A required input marks its label required.
Input group
  • The clear button empties the field, reports why, and returns focus.
  • Pressing the group's text focuses its field.
  • The clear button hides while its field is disabled.

Runs in React, Vue and web components.