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 .xoji-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
13 props, straight from the manifest.
| Prop | Type | Default | 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, with their selectors.
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 .xoji-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 xoji-default register
(derive(xojiDefault, { 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-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-semibold
Slots
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.
<xoji-field label="Email" name="email" type="email" placeholder="you@example.com" required></xoji-field>
<xoji-field
label="Password"
name="password"
type="password"
description="At least 12 characters."
required
></xoji-field>
<xoji-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>
</xoji-field>
<xoji-field label="Amount" name="amount" type="number" invalid error="Must be greater than zero.">
<span slot="prefix">$</span>
</xoji-field>
<script lang="ts">
import { Field } from "@xoji/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 "@xoji/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>