Field
A labelled text input with helper text, validation, adornments, and built-in clear and reveal actions.
Live demo
Field is the complete single-line text input: a label, an input, a persistent description, and an error message wired together with the right accessibility relationships out of the box. The input inherits the shared .xtyle-control chrome and adds field-specific layout: a unified control box that holds leading and trailing adornment slots (icons, currency prefixes, unit suffixes) alongside the input.
It generates a stable id and links it to the label, builds aria-describedby from both the description and the error, and ships two optional built-in actions: a clear button and a password reveal toggle. In the HTML and Svelte bindings the element is form-associated, participating in native form submission and constraint validation. Sizes (sm / md / lg) match the Button padding scale, and readonly, required, disabled, and invalid are all first-class.
When to use
How this component composes with the rest of the set.
Props
22 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
aria-label from the placeholder. | ||||
bind:value). | ||||
password enables the reveal toggle. | ||||
aria-describedby. | ||||
aria-describedby) only while invalid. | ||||
aria-invalid, and a custom validity in form-associated mode. | ||||
* indicator, required + aria-required, and a valueMissing validity. | ||||
required is unmet. The platform localizes its own constraint messages and this one is xtyle's, so an app that is not in English should set it. | ||||
<datalist> it owns inside the input's own root and wires the input's list to it, so suggestions work even though the input lives in a shadow root (where a page-level <datalist> could never reach it). Set the JS property in html / svelte; the Astro binding takes a JSON array attribute. Needs the runtime. (html / svelte / astro on hydrate.) | ||||
--font-mono) for code-shaped values: identifiers, paths, hex colors, expressions, env values. A presentational swap on an existing token, so it bakes into the zero-JS Astro markup too. | ||||
spellcheck="false" — a bare spellcheck is the HTML spelling of on. | ||||
email, street-address, or off. | ||||
spellcheck is. | ||||
Events
What the component emits, and what rides along on event.detail. The name in the
first column is the one addEventListener takes.
| Event | Detail | Bindings | Description |
|---|---|---|---|
Appearance
Variants
default
The standard field: neutral border, accent focus ring.
invalid
Error treatment: danger border, a danger-tinted focus ring, and the error message shown.
Sizes
sm
Compact.
md
Default.
lg
Large.
States
focus-visible
Keyboard focus inside the control: accent border and a token-colored ring via :focus-within.
invalid
Invalid: danger border; the focus-within ring shifts to a danger tint.
disabled
Non-interactive: muted ink and a disabled-tinted control.
readonly
Read-only: a subtly recessed background; the value still submits.
required
Required: a danger-colored * indicator on the label.
Anatomy
The named parts that make up the component. A ::part() handle is reachable from your own stylesheet; the class beside it is the component's internal selector, which a shadow boundary keeps to itself.
field
The outer column wrapper carrying the size and state modifiers.
label
The field label, linked to the input via for; carries the required indicator.
control
The bordered box that wraps the input and its adornments; paints the focus-within ring.
input
The native input, styled by .xtyle-control with its own chrome neutralized so the control box owns the border.
adornment
A leading (prefix) or trailing (suffix) slot for icons, currency symbols, or units.
description
Persistent helper text below the control, linked via aria-describedby.
error
The validation message shown when invalid, linked via aria-describedby.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xtyle-default register
(derive(xtyleDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent--bg-1--border-normal--border-thin--danger--danger-bg--danger-text--duration-fast--ease-standard--fg-0--fg-1--fg-2--fg-disabled--field-bg--field-border--font-mono--font-sans--leading-normal--leading-tight--radius-md--radius-sm--ring--space-1--space-2--space-3--space-4--state-disabled--state-hover--state-press--text-lg--text-sm--weight-medium--weight-semiboldSlots
A leading adornment: icon, currency symbol, or unit.
A trailing adornment: icon, unit, or status marker.
Overrides the built-in clear-button glyph.
Overrides the built-in password-reveal glyph.
Accessibility
Code
Labels, validation, and adornments
A required email, a password with helper text, a clearable search with a leading icon, and an invalid amount with a currency prefix.
<xtyle-field label="Email" name="email" type="email" placeholder="you@example.com" required></xtyle-field>
<xtyle-field
label="Password"
name="password"
type="password"
description="At least 12 characters."
required
></xtyle-field>
<xtyle-field
label="Search"
name="q"
placeholder="Search…"
clearable
>
<svg slot="prefix" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path fill="currentColor" d="M10 4a6 6 0 1 0 3.5 10.9l4.3 4.3 1.4-1.4-4.3-4.3A6 6 0 0 0 10 4Zm0 2a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z" />
</svg>
</xtyle-field>
<xtyle-field label="Amount" name="amount" type="number" invalid error="Must be greater than zero.">
<span slot="prefix">$</span>
</xtyle-field><script lang="ts">
import { Field } from "@xtyle/svelte";
let email = $state("");
</script>
<Field label="Email" name="email" type="email" placeholder="you@example.com" required bind:value={email} />
<Field
label="Password"
name="password"
type="password"
description="At least 12 characters."
required
/>
<Field label="Search" name="q" placeholder="Search…" clearable>
{#snippet prefix()}
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path fill="currentColor" d="M10 4a6 6 0 1 0 3.5 10.9l4.3 4.3 1.4-1.4-4.3-4.3A6 6 0 0 0 10 4Zm0 2a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z" />
</svg>
{/snippet}
</Field>
<Field label="Amount" name="amount" type="number" invalid error="Must be greater than zero.">
{#snippet prefix()}<span>$</span>{/snippet}
</Field>---
import { Field } from "@xtyle/astro";
---
<Field label="Email" name="email" type="email" placeholder="you@example.com" required />
<Field
label="Password"
name="password"
type="password"
description="At least 12 characters."
required
/>
<Field label="Search" name="q" placeholder="Search…">
<svg slot="prefix" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path fill="currentColor" d="M10 4a6 6 0 1 0 3.5 10.9l4.3 4.3 1.4-1.4-4.3-4.3A6 6 0 0 0 10 4Zm0 2a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z" />
</svg>
</Field>
<Field label="Amount" name="amount" type="number" invalid error="Must be greater than zero.">
<span slot="prefix">$</span>
</Field>Type-ahead suggestions
Pass options and Field renders a <datalist> in the input's own root, so suggestions work across the shadow boundary where a page-level datalist can't reach.
<xtyle-field id="branch" label="Branch" name="branch" placeholder="Type to filter…"></xtyle-field>
<script>
document.getElementById("branch").options = ["main", "develop", "release/1.0", "hotfix"];
</script><script lang="ts">
import { Field } from "@xtyle/svelte";
const branches = ["main", "develop", "release/1.0", "hotfix"];
</script>
<Field label="Branch" name="branch" placeholder="Type to filter…" options={branches} />---
import { Field } from "@xtyle/astro";
const branches = ["main", "develop", "release/1.0", "hotfix"];
---
<Field label="Branch" name="branch" placeholder="Type to filter…" options={branches} />