Text Input
A native <input> or <textarea> styled via a single class. Same .x-text-input covers three usages:
- Bare input —
<input class="unset x-text-input" /> - Bare textarea —
<textarea class="unset x-text-input" /> - Wrapper —
<div class="x-text-input">with one input/textarea and any number of affix elements
Mode is selected by what the element is (:is(input, textarea)) and what it contains (:has(> input) → horizontal row, :has(> textarea) → vertical column). No marker class on affixes, no data-side. Source order is render order.
Install
import "elements-kit/ui/styles/theme.css";import "elements-kit/ui/styles/scaling.css";import "elements-kit/ui/styles/radius.css";import "elements-kit/ui/styles/space.css";import "elements-kit/ui/styles/typography.css";import "elements-kit/ui/styles/cursor.css";import "elements-kit/ui/styles/unset.css";import "elements-kit/ui/styles/palette/gray.css";import "elements-kit/ui/styles/neutral/gray.css";import "elements-kit/ui/styles/palette/black-alpha.css";import "elements-kit/ui/styles/palette/mint.css";import "elements-kit/ui/styles/accent/mint.css";
import "elements-kit/ui/text-input/text-input.css";API
<!-- Bare input --><input class="unset x-text-input" data-size="2" data-variant="surface" />
<!-- Bare textarea --><textarea class="unset x-text-input" data-size="2" data-variant="surface" />
<!-- Input wrapper — horizontal: leading/trailing affixes --><div class="x-text-input" data-size="2" data-variant="surface"> <SearchIcon /> <input class="unset" placeholder="Search…" /> <kbd>⌘K</kbd></div>
<!-- Textarea wrapper — vertical: top/bottom affixes --><div class="x-text-input" data-size="2" data-variant="surface"> <div>Markdown</div> <textarea class="unset" /> <div>0 / 280</div></div>| Attribute | Values |
|---|---|
data-variant | surface (default), soft |
data-size | 1, 2 (default), 3 |
data-accent (on a parent or the element) | any imported color scale |
disabled / readonly | native — dims background, switches cursor |
Sizing
data-size | input height | textarea min-height | radius |
|---|---|---|---|
1 | --space-5 | --space-8 | max(--radius-2, --radius-pill) |
2 (default) | --space-6 | --space-9 | max(--radius-2, --radius-pill) |
3 | --space-7 | 80px | max(--radius-3, --radius-pill) |
Variants
surface(default) —--color-surfacebackground, 1px--neutral-a7border,--focus-8focus ring.soft—--accent-a3background, no border,--accent-8focus ring.
Wrapper anatomy
Inside the wrapper, document order chooses where an affix lands.
<div class="x-text-input"> <span>A</span> <!-- before input → leading (left for input, top for textarea) --> <input class="unset" /> <span>B</span> <!-- after input → trailing (right for input, bottom for textarea) --></div>- The wrapped
<input>/<textarea>has its background, border, padding, and outline stripped — the wrapper owns the chrome. - Affixes get
display: flex; align-items: center; cursor: textand a negative margin that extends them flush with the wrapper edge. - Focus-within outline wraps the whole container, so a focus ring lands once on the row/column rather than on the inner control.
Clicking an affix focuses the input because the affix carries cursor: text and the input remains the only focusable child in the row/column.
States
- Disabled / read-only —
var(--neutral-a11)text, dimmed background. When the input is empty (:placeholder-shown), cursor becomesvar(--cursor-disabled)and propagates to affixes inside the wrapper. - Autofill — Chrome’s yellow autofill background is suppressed via
background-clip: textwhile preserving the colored text.
Accessibility
- Pair with a
<label>(wrapping orfor=). The wrapper does not own the label. - The wrapper is a presentational container; the
<input>/<textarea>keeps all native semantics, form participation, and ARIA. aria-invalid="true"on the input/textarea is yours to apply for error states.