Input
Single-line text field that adopts every native HTML `type` and exposes token-driven focus, error, and disabled states.
Overview
Input is a thin styled wrapper around the native <input> element. Pass any HTML type (text, email, password, number, search, tel, url, file, date...) and the browser semantics, validation, and virtual keyboard all come along for free.
All visual states are token-driven: border-input for default, focus-visible:ring-ring/50 for keyboard focus, and aria-invalid:border-destructive for error. There are no variants - inputs should look the same everywhere so users can predict their behavior.
Preview
'use client';import { Input, Label } from '@gremorie/rx-forms';export function InputPreview() { return ( <div className="flex max-w-xs flex-col gap-2"> <Label htmlFor="email-input">Email</Label> <Input id="email-input" type="email" placeholder="you@example.com" /> </div> );}Anatomy
Input single styled native <input> element (leaf primitive)Installation
bash npx gremorie@latest add rx-input bash pnpm dlx gremorie@latest add rx-input bash yarn dlx gremorie@latest add rx-input bash bunx --bun gremorie@latest add rx-input Usage
import { Input, Label } from "@gremorie/rx-forms";
export function Example() {
return (
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="you@example.com" />
</div>
);
}The Angular edition of this component ships from source today (see the workbench for the side-by-side); its registry entry is coming next.
API
<Input>
| Prop | Type | Default | Description |
|---|---|---|---|
type | string | "text" | Any native HTML input type. Drives keyboard, validation, and platform UI (e.g. date picker, file dialog). |
disabled | boolean | false | Disables interaction. Applies pointer-events-none and opacity-50. |
aria-invalid | boolean | - | When true, switches border and focus ring to the destructive token. |
Extends all React.ComponentProps<"input">. Renders with data-slot="input" so the parent InputGroup can drive group-wide focus and error states.
Composition
<Input>is a leaf primitive. Pair it with a<Label>(linked viahtmlFor/id) so clicks and screen reader interactions focus the field.- For prefix/suffix icons or buttons, wrap it with
<InputGroup>and use<InputGroupInput>instead of<Input>so the group can drive shared focus states. - For form-bound usage, wrap it in a
<FormField>+<FormControl>so labels, descriptions, and error messages auto-wire ARIA relationships.
Variations
With label
The canonical pattern. type="email" triggers the email keyboard on mobile and enables built-in browser validation. Pair every input with a <Label> linked via htmlFor.
'use client';import { Input, Label } from '@gremorie/rx-forms';export function InputWithLabelPreview() { return ( <div className="grid w-full max-w-sm gap-2"> <Label htmlFor="email">Email</Label> <Input id="email" type="email" placeholder="you@example.com" /> </div> );}Number input with min/max
type="number" shows numeric keyboard on mobile and accepts min, max, step for browser validation.
<div className="grid gap-2">
<Label htmlFor="quantity">Quantity</Label>
<Input id="quantity" type="number" min={1} max={99} defaultValue={1} />
</div>Invalid state
Drive aria-invalid from your validation library and point aria-describedby at the error text. The destructive ring and border come for free.
Enter a valid email address.
'use client';import { Input, Label } from '@gremorie/rx-forms';export function InputInvalidPreview() { return ( <div className="grid w-full max-w-sm gap-2"> <Label htmlFor="invalid-email">Email</Label> <Input id="invalid-email" type="email" defaultValue="not-an-email" aria-invalid="true" aria-describedby="invalid-email-error" /> <p id="invalid-email-error" className="text-sm text-destructive"> Enter a valid email address. </p> </div> );}File input
type="file" renders the native picker. The internal file: selectors style the button-like trigger.
'use client';import { Input, Label } from '@gremorie/rx-forms';export function InputFilePreview() { return ( <div className="grid w-full max-w-sm gap-2"> <Label htmlFor="attachment">Attachment</Label> <Input id="attachment" type="file" /> </div> );}Disabled
disabled removes the input from the tab order, blocks pointer events, and drops opacity to 50%.
'use client';import { Input } from '@gremorie/rx-forms';export function InputDisabledPreview() { return ( <div className="w-full max-w-sm"> <Input disabled placeholder="Disabled input" defaultValue="Read only" /> </div> );}Accessibility
- Labels: every input needs an associated
<Label>(viahtmlFormatching the inputid) or an explicitaria-label. - Focus: 3px focus-visible ring driven by
focus-visible:ring-ring/50, so it only appears for keyboard users. - Validation: combine
aria-invalid="true"witharia-describedbypointing at the error message so screen readers announce the failure when focus enters the field. - Disabled:
disabledremoves the input from the tab order and prevents activation, including from JavaScript-issued focus. - Autocomplete: respect platform conventions by setting
autoComplete="email","current-password","one-time-code", etc.
Related
- Label - the accessible label primitive
- Input Group - wrap Input with icons, buttons, or kbd hints
- Textarea - multi-line counterpart
- Form - wires Input to react-hook-form with ARIA helpers
- Input OTP - segmented input for one-time codes