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-invalidon the input. - Updates
aria-describedbyto 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>
);
}Recommended usage
- 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
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Accessible label displayed above the input |
helperText | string | — | Optional helper text shown below the field |
error | string | — | Validation error message |
leftIcon | React.ReactNode | — | Icon rendered inside the field, on the left |
rightIcon | React.ReactNode | — | Icon/control rendered inside the field, right |
size | "sm" | "md" | "lg" | "md" | Controls input height and padding |
className | string | — | Additional 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-invalidwhenerroris present. - Automatically wires
aria-describedbyto helper/error text when available. - Static icons should be decorative (
aria-hidden). - Always pair
Inputwith 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.