sveltelean Primitives
Versionv0.2.0 GitHub

Example

Email, password and notes
<label for="input-email">Email</label>
<input id="input-email" type="email" name="email" autocomplete="email"
	placeholder="name@example.com" required data-slean="input" />

<label for="input-password">Password</label>
<input id="input-password" type="password" name="password"
	autocomplete="current-password" data-slean="input" />

<label for="input-notes">Notes</label>
<textarea id="input-notes" name="notes" data-slean="input"></textarea>
Tier 0: the two sources differ by the stylesheet import and the bindings. Type a partial address and leave the field: the red border appears only after an edit. The notes grow with their text where field-sizing is supported.

Why this implementation exists

A text field is the control every form has, and the platform implements all of it: editing and selection in every script, autofill and password managers keyed by autocomplete, the on-screen keyboard chosen by type, constraint validation on submit and :user-invalid after an edit. A component that wraps <input> to provide these adds code the element does not need.

Svelte Lean ships the element as it is, in three sizes, with the states the platform reports (:read-only, :disabled, :user-invalid, ::placeholder) and aria-invalid for an error the application found. The textarea grows with its content through field-sizing: content where the browser supports it.

The browser owns

  • editing, selection, autofill and spell checking
  • constraint validation (required, pattern, minlength, the email and url formats) and :user-invalid after an edit
  • the platform keyboard chosen by type and autocomplete
  • implicit form submission with Enter, and the form value
  • a textarea that grows with its content (field-sizing)

Svelte Lean owns

  • input.css: three sizes, border, colors, read-only, invalid and disabled states, a three-line textarea minimum
  • the contract
  • documentation

Usage

The markup needs no package. Install @svelte-lean/styles for the stylesheet; @svelte-lean/primitives adds the typed contract and nothing at runtime.

npm install @svelte-lean/styles
stylesheets
import '@svelte-lean/styles/input.css';

Put data-slean="input" on an <input> of type text, email, password, search, url or tel, or on a <textarea>. Give it a label and the autocomplete token that matches the value.

data-size="sm" or data-size="lg" changes the height and padding. Bind the value with bind:value; it is always a string.

For a description and an error message under the input, put it in a Field. Other input types have their own primitives: Number field, Date field, Color field, File field, Slider.

Anatomy

PartElementdata-slean-partRequiredNotes
root<input type="text|email|password|search|url|tel" data-slean="input">–yesA label through <label for>; autocomplete with the right token; data-size="sm" or "lg".
root (multi-line)<textarea data-slean="input">–noFull width of its container, three lines at least, growing with its content where field-sizing is supported.

Runtime profile

Tier 0: the input has no behavior module, the Vite plugin maps input to no module, and the native-only fixture proves the path ships no Svelte Lean JavaScript.

Accessibility contract

  • A visible label through <label for>. A placeholder is a hint about the format, not a label: it disappears when the user types.
  • autocomplete tokens let the browser and password managers fill the field; type chooses the on-screen keyboard.
  • The browser exposes required and constraint failures itself. An error the application found is aria-invalid="true" on the input and a message listed in aria-describedby.
  • :user-invalid colors a field only after the user edited it, so an untouched required field is not red.
  • Under a coarse pointer the text takes the base size, which keeps iOS Safari from zooming the page when a field takes focus.

Keyboard

KeyWhenResult
charactersfocus in the fieldTypes (native)
ArrowLeft/ArrowRight/Home/Endfocus in the fieldMoves the caret; Shift extends the selection (native)
Enterfocus in an <input>Submits the form (implicit submission, native)
Enterfocus in a <textarea>Inserts a line break (native)
Escapefocus in type="search"Clears the value in Chrome and Safari (native)

Platform features

FeatureBaselineOutside the target
Text-like inputs, <textarea>, ::placeholder, :read-onlyWidely availableNot applicable
:user-invalidBaseline 2023 (Chrome 119, Firefox 88, Safari 16.5)No invalid border after an edit
field-sizing: contentNewly available since June 2026 (Chrome 123, Safari 26.2, Firefox 152)The textarea keeps the height of its rows (three lines at least) and scrolls
lh unitBaseline 2023 (Chrome 109, Safari 16.4, Firefox 120)No three-line minimum; rows sets the height

Without JavaScript

Fully functional, including validation on submit and the invalid border after an edit.

Server rendering

The value attribute, or the textarea’s text, renders the initial value; required, pattern and aria-invalid render as attributes.

Before hydration

Nothing is attached; the field works before and after hydration alike.

Styling

input.css sets the height from the control-height tokens, the border, background and text colors, the muted background of :read-only, the danger border of :user-invalid and aria-invalid, half opacity for :disabled, and a three-line minimum for the textarea (in lh, so it follows the line height). The tokens it reads:

TokenDefault (light)Applies to
--slean-control-height-md2.25remmin-block-size
--slean-space-10.25rempadding-block
--slean-space-30.75rempadding-inline
--slean-control-bordervar(--slean-border-strong)border
--slean-radius-md0.625remborder-radius
--slean-control-bgvar(--slean-surface)background
--slean-fgvar(--slean-neutral-12)color
--slean-accentoklch(54% 0.19 258)caret-color
--slean-text-sm0.875remfont-size
--slean-leading1.5line-height
--slean-duration-fast100mstransition
--slean-easecubic-bezier(0.2, 0, 0, 1)transition
--slean-fg-mutedvar(--slean-neutral-11)color
--slean-control-height-sm2remmin-block-size
--slean-space-20.5rempadding-inline, min-block-size, padding-block
--slean-radius-sm0.375remborder-radius
--slean-control-height-lg2.75remmin-block-size
--slean-space-41rempadding-inline
--slean-text-md1remfont-size
--slean-control-border-hovervar(--slean-accent)border-color
--slean-control-border-focusvar(--slean-accent)border-color
--slean-control-ring0 0 0 3px color-mix(in oklch, var(--slean-accent) 22%, transparent)box-shadow
--slean-warningoklch(76% 0.16 80)border-color
--slean-control-ring-warning0 0 0 3px color-mix(in oklch, var(--slean-warning) 28%, transparent)box-shadow
--slean-dangeroklch(55% 0.2 25)border-color
--slean-control-ring-danger0 0 0 3px color-mix(in oklch, var(--slean-danger) 22%, transparent)box-shadow
--slean-control-border-disabledvar(--slean-border)border-color
--slean-control-bg-disabledvar(--slean-muted)background

State selectors the stylesheet targets, all from the platform or ARIA: ::placeholder, :disabled, :focus-visible, :hover, :user-invalid, [aria-invalid="true"].

Variant attributes: data-size (sm, lg); data-status (warning, error).

Compatibility notes

Text-like inputs and the textarea are widely available; :user-invalid is Baseline 2023. field-sizing is newly available since June 2026 (Chrome 123, Safari 26.2, Firefox 152): where it is missing, the textarea keeps the height of its rows, three lines at least, and scrolls.

Examples

Sizes

data-size="sm" and data-size="lg" change the height, the inline padding and, for the large size, the text; without the attribute the size is medium. Under a coarse pointer the medium size takes the large height.

sizes
<input data-slean="input" data-size="sm" value="Small" aria-label="Small" />
<input data-slean="input" value="Medium" aria-label="Medium" />
<input data-slean="input" data-size="lg" value="Large" aria-label="Large" />

States

Every state is the platform’s or an ARIA attribute: readonly keeps the value focusable and submitted, disabled removes it from the tab order and the form data, and aria-invalid="true" marks an error the application found. The error text is the application’s; the Field page shows where it goes.

Use letters, digits and hyphens only.

states
<div data-slean="field">
	<label for="input-plan" data-slean-part="label">Plan</label>
	<input id="input-plan" value="Team" readonly data-slean="input" />
</div>
<div data-slean="field">
	<label for="input-seats" data-slean-part="label">Seats</label>
	<input id="input-seats" value="12" disabled data-slean="input" />
</div>
<div data-slean="field">
	<label for="input-handle" data-slean-part="label">Handle</label>
	<input id="input-handle" value="ada lovelace" aria-invalid="true"
		aria-describedby="input-handle-error" data-slean="input" />
	<p id="input-handle-error" data-slean-part="error">Use letters, digits and hyphens only.</p>
</div>

Testing

Source