Skip to main content
Gremorie

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>

PropTypeDefaultDescription
typestring"text"Any native HTML input type. Drives keyboard, validation, and platform UI (e.g. date picker, file dialog).
disabledbooleanfalseDisables interaction. Applies pointer-events-none and opacity-50.
aria-invalidboolean-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

  1. <Input> is a leaf primitive. Pair it with a <Label> (linked via htmlFor / id) so clicks and screen reader interactions focus the field.
  2. For prefix/suffix icons or buttons, wrap it with <InputGroup> and use <InputGroupInput> instead of <Input> so the group can drive shared focus states.
  3. 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> (via htmlFor matching the input id) or an explicit aria-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" with aria-describedby pointing at the error message so screen readers announce the failure when focus enters the field.
  • Disabled: disabled removes 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.
  • 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

On this page