Input
A single-line text field — with optional in-field leading/trailing slots and built-in clear, password-reveal, and loading affordances.
@garn/ui/inputDefault
Default text input.
Appearance
The shared field-surface fill axis (see lib/field-variants.ts): outline = a semi-transparent neutral border on a transparent fill (the default); soft = a subtle neutral fill with no edge that clears to the full outline on focus; ghost = seamless (no fill / edge) until focus — for inline / table-cell fields.
Outline
outlineSoft
softGhost
ghostSizing
Control height rung off the locked --garn-control-* ladder (24·28·32·40·44px at default density).
xssmmdlgxlSpacing
The same control rendered across garn's density modes — compact, default, and spacious.
States
Interactive states, toggled live.
<Input />State
Labeled
Input with an associated label.
Slots
Leading icon, trailing unit, and an interactive trailing control.
Search
Leading search icon + clearable.
Clearable
A clear button (Escape clears, focus retained).
Password
A password field with a reveal toggle.
Loading
A spinner, and a cancellable loading field.
Invalid
aria-invalid — danger edge + auto error glyph.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add input- Registry
input- Source
- input.tsx
- Deps
class-variance-authoritylucide-react- Registry deps
utilsfield-variantsfield-surfacespinner
Import
import { Input } from "@garn/ui/input";Props
Generated from the component's TypeScript types.
appearance- Description
- No description.
- Type
"ghost" | "outline" | "soft"- Default
outline
size- Description
- No description.
- Type
"lg" | "md" | "sm" | "xl" | "xs"- Default
md
cancelLabel- Description
- Accessible name for the cancel button.
- Type
string
clearable- Description
- Show a clear button when the field is non-empty; Escape also clears.
- Type
boolean
clearLabel- Description
- Accessible name for the clear button.
- Type
string
hidePasswordLabel- Description
- Accessible name for the reveal toggle when the password is visible.
- Type
string
leading- Description
- Decorative or interactive content rendered inside the field, before the text.
- Type
React.ReactNode
loading- Description
- Show a trailing spinner and mark the field aria-busy.
- Type
boolean
onCancel- Description
- When loading, make the spinner a cancel button (spinner → ✕ on hover/focus) that calls this.
- Type
() => void
onClear- Description
- Fires after the field is cleared (button or Escape).
- Type
() => void
onRevealedChange- Description
- Fires when the reveal toggle flips (controlled or uncontrolled).
- Type
(revealed: boolean) => void
revealable- Description
- For a password field, render a show/hide toggle that swaps the input type.
- Type
boolean
revealed- Description
- Controlled reveal state (pair with `onRevealedChange`); omit for uncontrolled.
- Type
boolean
showPasswordLabel- Description
- Accessible name for the reveal toggle when the password is hidden.
- Type
string
trailing- Description
- Decorative or interactive content rendered inside the field, at the trailing edge (after the built-in affordances).
- Type
React.ReactNode
Plus 309 inherited native <input> attributes.
Styling
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="input"][data-slot="input-root"][data-slot="input-leading"][data-slot="input-trailing"][data-slot="input-error-icon"][data-slot="input-reveal"][data-slot="input-clear"][data-slot="input-loading"][data-slot="input-cancel"]| Property | Token | Tier |
|---|---|---|
height | --garn-control-h-md | semantic · air |
paddingInline | --garn-control-px-md | semantic · air |
borderColor | --garn-foreground | semantic |
iconInset | --garn-control-icon-inset | semantic |
Private instance vars (never externalize): --in-px
Overview
Input is the single-line text field — the most-used control in any form. In garn it's not a bare <input>: it renders the shared field surface (the same appearance × size system as Select, Textarea, and Combobox), so every text control in a form lines up to the same height, radius, and focus ring at a given density. The appearance axis sets how much the field asserts itself — outline for the default bordered field, soft for a filled rest state, ghost for a borderless field that only reveals its edge on interaction.
Beyond the bare field, Input ships the affordances you'd otherwise re-build every time: leading/trailing slots for an icon or unit, a built-in clear button, a password reveal toggle, a loading spinner, and an error treatment that reddens the border and ring when aria-invalid is set. Reach past Input only when the data isn't a single line of text — a long message wants Textarea, a fixed-length code wants InputOTP, a choice wants Select or Combobox.
Guidelines
Always pair an Input with a Label. A placeholder is not a label — it vanishes on focus and fails screen readers. Use the label component (or Form's wiring) so clicking the text focuses the field and the accessible name is announced. Reserve the placeholder for an example of the expected value, not the field's name.
Let the field surface carry sizing — don't hand-pad. Pick a size rung and the control-height tokens handle the rest; adding px-*/h-* utilities breaks alignment with the sibling controls and the density modes. If a field looks too tall or short next to its neighbors, it's almost always a stray height utility, not the token.
Signal validity through state, not color alone. Set aria-invalid on error so the built-in red border + ring and the error-icon slot engage together, and wire the message via aria-describedby (Form does this for you). Use leading/trailing for units and adornments ($, %, a search icon) rather than baking them into the value.
Best practices
- Pair every input with a label (visible <label htmlFor> preferred).
- Set the right
type(email, password, search, number…) so the keyboard and validation fit; pairrevealablewith type=password and the right autoComplete. - Use
leading/trailingfor in-field icons/units; reach for the built-inclearable/loadingrather than hand-building affordances. - Reflect validation with aria-invalid + a described error message.
- Don't rely on the placeholder as the label — it disappears on focus.
- Don't hand-set height/padding; the size variant + --garn-control-* tokens own the grid.
- Don't use leading/trailing for outside-attached addons with merged borders (a https:// cell, a flanking Select) — that's a future InputGroup.
Content guidelines
- Label the field with a short noun phrase in sentence case (“Email address”).
- Use the placeholder only for a format hint or example (“name@company.com”), never as the label.
- Keep helper text to one line, and write errors as specific, fixable instructions (“Enter a valid email”).
Accessibility
- Role
textbox- Focus
- Border darkens (foreground/40) on any focus; keyboard focus adds a 2px neutral ring (foreground/15). Affordance buttons carry their own focus ring.
Enforceable contracts
Give every input an accessible label (a <label htmlFor>, or aria-label/aria-labelledby).
when no associated <label htmlFor>, aria-label, or aria-labelledby
- • Associate a visible <label> (htmlFor) or provide aria-label / aria-labelledby — the placeholder is not a label.
- • Reflect validation with aria-invalid (+ an aria-describedby error message); the auto error glyph is decorative (aria-hidden).
- • The clear button is a real <button> with an accessible name ('Clear', via clearLabel); it is keyboard-reachable, clears + keeps focus in the field, and Escape clears too.
- • The password reveal toggle is a <button aria-controls> with a Show/Hide label swap (not aria-pressed); a visually-hidden aria-live=polite region announces the state, and the type is forced back to password on form submit.
- • Loading sets aria-busy on the field; the spinner is aria-hidden. With onCancel the cancel button is named (cancelLabel) and keyboard-operable.
- • Leading/trailing slots keep the field a single click target — clicking empty padding or a decorative icon focuses the input, while a real button/link inside a slot still works.
- Status
- Stable
- Version
v0.3.0- Introduced
v0.1.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.