Input

A labeled text field with built-in states for validation, icons, and helper text. Extends the native input element so all HTML attributes pass through.

Preview

Sizes

Three sizes to fit any density — compact toolbars, standard forms, or prominent search bars.

Icons

Left icons set context; right icons signal supplementary actions or status. Both are pointer-events-none by default.

States

Error replaces helperText and switches the ring to red. Disabled removes all interaction and dims to 50%.

3–24 characters, no spaces.

That email is already taken.

Implementation

Built with forwardRef so refs work transparently. All standard HTML input attributes are forwarded through.

Input.tsx
tsx
import { Input } from "@/components/ui/Input";
import { Search, Mail, Lock } from "lucide-react";

// Basic
<Input label="Email" placeholder="you@example.com" type="email" />

// With helper text
<Input
  label="Username"
  helperText="Must be 3–24 characters."
  placeholder="sitka_user"
/>

// Error state
<Input
  label="Password"
  error="Must be at least 8 characters."
  type="password"
  defaultValue="abc"
/>

// With icons
<Input
  label="Search"
  leftIcon={<Search className="w-4 h-4" />}
  placeholder="Search components…"
/>

<Input
  label="Email"
  leftIcon={<Mail className="w-4 h-4" />}
  rightIcon={<Lock className="w-3.5 h-3.5" />}
  placeholder="you@example.com"
  type="email"
/>

// Sizes
<Input inputSize="sm" placeholder="Small" />
<Input inputSize="md" placeholder="Medium" />
<Input inputSize="lg" placeholder="Large" />

// Disabled
<Input label="Read-only field" disabled defaultValue="Cannot edit this" />

Props

PropTypeDefaultDescription
labelstringRenders a <label> element associated with the input via id.
helperTextstringSubtle hint text shown below the input when there is no error.
errorstringError message. Switches border and ring to red and replaces helperText.
inputSize"sm" | "md" | "lg""md"Controls height, padding, and font size.
leftIconReactNodeIcon node rendered inside the left edge of the input.
rightIconReactNodeIcon node rendered inside the right edge of the input.
disabledbooleanfalseDims the input and removes interaction.

Mobile

Inputs on mobile trigger the virtual keyboard and interact directly with the OS input system. Getting these details right prevents zoom jank and missed taps.

9:41

Sign in

Welcome back

Enter email
Enter password
Search components…
ScenarioGuidance
Prevent zoom on focusKeep font-size ≥ 16px on the <input> element. iOS zooms in when the font is smaller. The lg size meets this; md (13px) does not — add font-size: 16px in mobile CSS.
inputmode attributePass inputMode="email" for emails, "numeric" or "decimal" for numbers, "tel" for phone numbers, and "url" for URLs. This surfaces the right virtual keyboard without changing validation type.
Full-width on mobileInputs should be full-width (w-full) on small screens. Avoid fixed widths like w-72 in mobile layouts.
returnKeyTypeUse type="search" for search fields — mobile browsers show a Search/Go key. For form fields followed by another input, iOS advances focus on Next automatically.
AutofillAdd autocomplete attributes ("email", "current-password", "given-name", etc.) so iOS/Android Autofill and password managers work correctly.
Touch target heightUse inputSize="md" (h-10) or larger. The sm size (h-8 / 32px) is below the 44px guideline — only use it in dense UI where labels confirm context.

Accessibility

  • The label prop auto-generates an id and wires the <label> htmlFor — never skip it for form inputs.
  • Error strings are rendered in a <p> below the input; screen readers announce them on focus.
  • Disabled inputs are excluded from tab order natively — no aria-disabled workaround needed.
  • Icons are aria-hidden via pointer-events-none spans; they carry no semantic meaning.
  • Focus ring uses focus-visible so it only appears on keyboard navigation.