Input

Form input component used across PyColors UI. Includes labels, helper text, validation, icons, and accessibility best practices.

The Input component provides a consistent, accessible, and theme-aware text field for forms and UI surfaces.

It supports labels, helper text, validation errors, and optional icons on the left or right — making it suitable for dashboards, settings pages, authentication flows, and complex forms built with React Hook Form + Zod.


Import

import { Input } from "@/components/ui/input";

Basic usage

<Input placeholder="Type something…" />
import { Input } from "@/components/ui/input";

export default function Demo() {
  return <Input placeholder="Type something…" />;
}

The default Input uses the PyColors theme tokens, adapts to light/dark mode, and respects the global typography scale.


With label

<Input label="Email" placeholder="you@domain.com" />
import { Input } from "@/components/ui/input";

export default function Example() {
  return <Input label="Email" placeholder="you@domain.com" />;
}

Labels use the correct accessible patterns (htmlFor + id) and inherit typography rules from the design system.


Helper text

Use helperText to provide optional context or hints below the field.

Visible on your public profile

<Input
  label="Username"
  placeholder="your-username"
  helperText="Visible on your public profile"
/>
import { Input } from "@/components/ui/input";

export default function HelperExample() {
  return (
    <Input
      label="Username"
      placeholder="your-username"
      helperText="Visible on your public profile"
    />
  );
}

Helper text is rendered with a smaller, muted style aligned to the typography scale.


Validation error

The error prop:

  • Toggles the error visual state.
  • Sets aria-invalid on the input.
  • Updates aria-describedby to point to the error message.
  • Displays the error message below the field.

Invalid email address

<Input
  label="Email"
  placeholder="you@domain.com"
  error="Invalid email address"
/>
import { Input } from "@/components/ui/input";

export default function ErrorExample() {
  return (
    <Input
      label="Email"
      placeholder="you@domain.com"
      error="Invalid email address"
    />
  );
}

Use this pattern with React Hook Form + Zod to map schema or server validation errors directly to fields.


With icons

Icons can be rendered on either side of the input using leftIcon and rightIcon.

Left icon

import { Search } from "lucide-react";

<Input placeholder="Search…" leftIcon={<Search className="size-4" aria-hidden />} />
import { Input } from "@/components/ui/input";
import { Search } from "lucide-react";

export default function LeftIconExample() {
  return (
    <Input
      placeholder="Search…"
      leftIcon={<Search className="size-4" aria-hidden="true" />}
    />
  );
}

Right icon

Often used for password reveal/hide toggles, loading states, or inline validation.

import { Eye } from "lucide-react";

<Input
  type="password"
  label="Password"
  placeholder="••••••••"
  rightIcon={<Eye className="size-4 text-muted-foreground" aria-hidden />}
/>
"use client";

import * as React from "react";
import { Input } from "@/components/ui/input";
import { Eye, EyeOff } from "lucide-react";

export default function PasswordInput() {
  const [visible, setVisible] = React.useState(false);

  return (
    <Input
      type={visible ? "text" : "password"}
      label="Password"
      placeholder="••••••••"
      rightIcon={
        <button
          type="button"
          onClick={() => setVisible((v) => !v)}
          className="inline-flex items-center justify-center text-muted-foreground hover:text-foreground"
          aria-label={visible ? "Hide password" : "Show password"}
        >
          {visible ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
        </button>
      }
    />
  );
}

Static icons should be decorative (aria-hidden). For interactive controls (like the password toggle), render a semantic element (button/link) so keyboard + screen readers work correctly.


Sizes

Use the size prop to adapt the Input to different densities.

<Input size="sm" placeholder="Small input" />
<Input size="md" placeholder="Medium input" />
<Input size="lg" placeholder="Large input" />
import { Input } from "@/components/ui/input";

export default function SizesExample() {
  return (
    <div className="flex flex-col gap-4 w-80">
      <Input size="sm" placeholder="Small input" />
      <Input size="md" placeholder="Medium input" />
      <Input size="lg" placeholder="Large input" />
    </div>
  );
}
  • Use "md" as the default size for most forms.
  • Reserve "sm" for dense UIs such as tables, filters, or toolbars.
  • Use "lg" for marketing pages, onboarding flows, or high-focus fields.

API

Props

PropTypeDefaultDescription
labelstringAccessible label displayed above the input
helperTextstringOptional helper text shown below the field
errorstringValidation error message
leftIconReact.ReactNodeIcon rendered inside the field, on the left
rightIconReact.ReactNodeIcon/control rendered inside the field, right
size"sm" | "md" | "lg""md"Controls input height and padding
classNamestringAdditional Tailwind classes

The Input also accepts native input props (type, placeholder, autoComplete, etc.).


TypeScript types

These types define the public API of the Input component.

export type InputSize = "sm" | "md" | "lg";

export interface InputProps
  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
  label?: string;
  helperText?: string;
  error?: string;
  leftIcon?: React.ReactNode;
  rightIcon?: React.ReactNode;
  size?: InputSize;
}

Accessibility

  • Uses aria-invalid when error is present.
  • Automatically wires aria-describedby to helper/error text when available.
  • Static icons should be decorative (aria-hidden).
  • Always pair Input with a visible label in forms for best accessibility.
  • Preserve focus rings and keyboard navigation (Tab, Shift+Tab).

Design guidelines

  • Use clear, concise labels (1–3 words when possible).
  • Prefer placeholder text as an example, not the only label.
  • Keep helper text short; move complex explanations to adjacent content.
  • Use icons with className="size-4" to stay consistent with the design system.