Input
A native input element. Built with Base UI and Tailwind CSS. Copy-paste ready.
import { Input } from "@/components/ui/input";
export default function Particle() {
return <Input aria-label="Enter text" placeholder="Enter text" type="text" />;
}
Installation
pnpm dlx shadcn@latest add @cnippet/input
Usage
import { Input } from "@/components/ui/input"<Input />Examples
For accessible labelling and validation, prefer using the Field component to wrap inputs, or the FieldControl component. See some related examples.
With Label
Pair the input with a visible FieldLabel using Field for accessible name association.
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="w-full max-w-xs">
<FieldLabel htmlFor="input-demo-email">Email</FieldLabel>
<Input
id="input-demo-email"
placeholder="name@example.com"
type="email"
/>
</Field>
);
}
With Description
Adds a FieldDescription below the input to provide additional guidance or format hints.
Choose a unique username for your account.
import {
Field,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="input-demo-username">Username</FieldLabel>
<Input
id="input-demo-username"
placeholder="Enter your username"
type="text"
/>
<FieldDescription>
Choose a unique username for your account.
</FieldDescription>
</Field>
);
}
With Error Message
Shows a FieldError below the input after native constraint validation fails on form submission.
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export default function FieldWithErrorDemo() {
return (
<Field>
<FieldLabel>Email</FieldLabel>
<Input placeholder="Enter your email" type="email" />
<FieldError>Please enter a valid email address.</FieldError>
</Field>
);
}
With Character Counter
Displays a live character count below the input that updates as the user types, indicating the remaining budget.
"use client";
import { useState } from "react";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
const [value, setValue] = useState("");
const maxLength = 50;
return (
<Field className="max-w-sm">
<div className="flex w-full items-center justify-between">
<FieldLabel htmlFor="bio">Description</FieldLabel>
<span className="text-muted-foreground text-xs">
{value.length}/{maxLength}
</span>
</div>
<Input
id="bio"
maxLength={maxLength}
onChange={(e) => setValue(e.target.value)}
placeholder="Description of your project"
value={value}
/>
</Field>
);
}
Password
A type="password" input with a visibility toggle button to reveal or mask the entered value.
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="input-demo-password">Password</FieldLabel>
<Input id="input-demo-password" placeholder="Password" type="password" />
</Field>
);
}
Phone
A type="tel" input styled for phone number entry, optionally combined with a country-code prefix.
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="input-demo-tel">Phone</FieldLabel>
<Input id="input-demo-tel" placeholder="+1 (555) 123-4567" type="tel" />
</Field>
);
}
URL
A type="url" input that validates the format on blur and shows a browser-native or custom error message.
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="input-demo-url">URL</FieldLabel>
<Input id="input-demo-url" placeholder="https://example.com" type="url" />
</Field>
);
}
Number
A type="number" input with min, max, and step constraints applied for numeric-only entry.
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="input-demo-number">Number</FieldLabel>
<Input id="input-demo-number" placeholder="123" type="number" />
</Field>
);
}
Date
A type="date" input that renders the browser's native date picker widget.
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="input-demo-date">Date</FieldLabel>
<Input id="input-demo-date" type="date" />
</Field>
);
}
File
A type="file" input styled to match the design system, with a custom label trigger hiding the default file button.
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="input-demo-file">File</FieldLabel>
<Input id="input-demo-file" type="file" />
</Field>
);
}
With Required
Displays a red asterisk on the label to signal that the field must be filled before submitting.
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="company">
Company <span className="text-destructive">*</span>
</FieldLabel>
<Input id="company" placeholder="Wotso Inc." />
</Field>
);
}
Time
A type="time" input that renders the browser's native time picker for hour and minute selection.
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="input-demo-time">Time</FieldLabel>
<Input id="input-demo-time" type="time" />
</Field>
);
}
With Multiple Fields
A group of related inputs (e.g. first name and last name) laid out side by side in a single row.
import { Button } from "@/components/ui/button";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export function Pattern() {
return (
<form className="max-w-lg">
<div className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel htmlFor="form-phone">Phone</FieldLabel>
<Input id="form-phone" placeholder="+1 (555) 123-4567" type="tel" />
</Field>
<Field>
<FieldLabel htmlFor="form-country">Country</FieldLabel>
<Select defaultValue="us">
<SelectTrigger id="form-country">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="us">United States</SelectItem>
<SelectItem value="uk">United Kingdom</SelectItem>
<SelectItem value="ca">Canada</SelectItem>
</SelectContent>
</Select>
</Field>
</div>
<Field>
<FieldLabel htmlFor="form-address">Address</FieldLabel>
<Input id="form-address" placeholder="123 Main St" type="text" />
</Field>
<div className="flex justify-end gap-3 pt-4">
<Button type="button" variant="outline">
Cancel
</Button>
<Button type="submit">Submit</Button>
</div>
</form>
);
}
Label With Tooltip
The label carries an info icon button that opens a Tooltip with additional context about the field.
Your unique identifier on the platform.
import { HelpCircleIcon } from "lucide-react";
import {
Field,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
export function Pattern() {
return (
<Field className="max-w-xs">
<div className="flex items-center gap-2">
<FieldLabel htmlFor="username">Username</FieldLabel>
<Tooltip>
<TooltipTrigger className="inline-flex items-center">
<HelpCircleIcon className="size-3.5 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
Your unique identifier on the platform.
</TooltipContent>
</Tooltip>
</div>
<Input id="username" placeholder="johndoe" />
<FieldDescription>
Your unique identifier on the platform.
</FieldDescription>
</Field>
);
}
With Badge
An inline badge appended to the label — used for plan-restricted or beta fields to indicate access requirements.
import { Badge } from "@/components/ui/badge";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="w-full max-w-xs">
<div className="flex w-full items-center gap-2">
<FieldLabel htmlFor="api-key">API Key</FieldLabel>
<Badge variant="success">New</Badge>
</div>
<Input id="api-key" placeholder="9f82a1c4b7e243d8a6c9f1e2a3b4c5d6" />
</Field>
);
}
With Optional
An "Optional" badge appended to the label to distinguish non-required fields from required ones at a glance.
import { Badge } from "@/components/ui/badge";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<div className="flex w-full items-center justify-between gap-2">
<FieldLabel htmlFor="middle-name">Middle Name</FieldLabel>
<Badge size="sm" variant="warning">
Optional
</Badge>
</div>
<Input id="middle-name" placeholder="Alexander" />
</Field>
);
}
Password With Link
A password-style input with a "Forgot password?" link next to the label and a show/hide toggle inside the field.
"use client";
import { EyeIcon, EyeOffIcon } from "lucide-react";
import { useState } from "react";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
const [isVisible, setIsVisible] = useState<boolean>(false);
const toggleVisibility = () => setIsVisible((prevState) => !prevState);
return (
<Field className="w-full max-w-xs">
<div className="flex items-center justify-between">
<FieldLabel htmlFor="password-link">Password</FieldLabel>
<a
className="font-medium text-primary text-xs hover:underline"
href="#"
>
Forgot password?
</a>
</div>
<div className="relative">
<Input
className="pe-9"
id="password-link"
type={isVisible ? "text" : "password"}
/>
<button
aria-label={isVisible ? "Hide password" : "Show password"}
aria-pressed={isVisible}
className="absolute inset-e-0 inset-y-0 flex h-full w-9 items-center justify-center rounded-e-md text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus:z-10 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50"
onClick={toggleVisibility}
type="button"
>
{isVisible ? (
<EyeOffIcon aria-hidden="true" />
) : (
<EyeIcon aria-hidden="true" />
)}
</button>
</div>
</Field>
);
}
Multiple Errors
Displays a bulleted list of validation errors below the field — useful for fields with multiple constraints.
import { CircleAlertIcon } from "lucide-react";
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="security-code">Security Code</FieldLabel>
<Input
aria-invalid="true"
id="security-code"
placeholder="Enter your security code"
/>
<FieldError className="mt-2 space-y-1.5 text-xs">
<div className="flex items-center gap-1.5">
<CircleAlertIcon className="size-3.5" />
<span>Code must be at least 12 characters long</span>
</div>
<div className="flex items-center gap-1.5">
<CircleAlertIcon className="size-3.5" />
<span>Code must contain at least one uppercase letter</span>
</div>
<div className="flex items-center gap-1.5">
<CircleAlertIcon className="size-3.5" />
<span>Code cannot include common words or patterns</span>
</div>
</FieldError>
</Field>
);
}
Password Strength
A password input with a live strength indicator that evaluates the entered value and shows a color-coded hint message.
Use 8+ characters with a number and a special character.
"use client";
import { AlertTriangleIcon, CircleCheckIcon, InfoIcon } from "lucide-react";
import { useId, useState } from "react";
import { cn } from "@/lib/utils";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
const id = useId();
const [password, setPassword] = useState("");
const getStrength = (pass: string) => {
const requirements = [
{ met: pass.length >= 8, text: "8+ characters" },
{ met: /[0-9]/.test(pass), text: "a number" },
{ met: /[!@#$%^&*]/.test(pass), text: "a special character" },
];
const metCount = requirements.filter((r) => r.met).length;
return { metCount, requirements };
};
const { metCount } = getStrength(password);
const getHint = () => {
if (!password) {
return "Use 8+ characters with a number and a special character.";
}
if (metCount === 3) {
return "Strong password. You're all set!";
}
if (metCount === 2) {
return "Almost there! Add the missing requirement.";
}
return "Weak password. Include 8+ characters, a number, and a special character.";
};
const getStatus = () => {
if (!password)
return {
color: "text-muted-foreground",
icon: <InfoIcon className="size-3.5 shrink-0" />,
};
if (metCount === 3)
return {
color: "text-emerald-500",
icon: <CircleCheckIcon className="size-3.5 shrink-0" />,
};
if (metCount === 2)
return {
color: "text-amber-500",
icon: <AlertTriangleIcon className="size-3.5 shrink-0" />,
};
return {
color: "text-destructive",
icon: <AlertTriangleIcon className="size-3.5 shrink-0" />,
};
};
const { color, icon: StatusIcon } = getStatus();
return (
<Field className="w-full max-w-xs">
<FieldLabel htmlFor={id}>Password</FieldLabel>
<Input
id={id}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter password"
type="password"
value={password}
/>
<div
className={cn(
"flex items-center gap-2 text-xs transition-colors duration-200",
color,
)}
>
{StatusIcon}
<p>{getHint()}</p>
</div>
</Field>
);
}
Disabled
A disabled input showing a masked value — used for read-only secrets like API tokens with a description pointing to where to manage them.
Your token is masked. Regenerate it from your security settings.
import {
Field,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="input-disabled">API token</FieldLabel>
<Input
defaultValue="sk-••••••••••••••••••••••••••••••••"
disabled
id="input-disabled"
type="text"
/>
<FieldDescription>
Your token is masked. Regenerate it from your security settings.
</FieldDescription>
</Field>
);
}
Readonly With Copy
A readOnly input for shareable links or tokens, paired with a copy-to-clipboard icon button that toggles to a checkmark on success.
Share this link to invite friends.
"use client";
import { CheckIcon, CopyIcon } from "lucide-react";
import { useRef } from "react";
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
import { Button } from "@/components/ui/button";
import {
Field,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
const { copyToClipboard, isCopied } = useCopyToClipboard();
const inputRef = useRef<HTMLInputElement>(null);
return (
<Field className="w-full max-w-xs">
<FieldLabel htmlFor="referral-link">Referral link</FieldLabel>
<div className="flex gap-2">
<Input
defaultValue="https://app.example.com/ref/abc123"
id="referral-link"
readOnly
ref={inputRef}
type="text"
/>
<Button
aria-label="Copy referral link"
onClick={() =>
inputRef.current && copyToClipboard(inputRef.current.value)
}
size="icon"
variant="outline"
>
{isCopied ? (
<CheckIcon aria-hidden="true" />
) : (
<CopyIcon aria-hidden="true" />
)}
</Button>
</div>
<FieldDescription>Share this link to invite friends.</FieldDescription>
</Field>
);
}
With Inline Icon
An absolutely-positioned icon rendered inside the input's leading edge using padding offset — no wrapper component required.
import { SearchIcon } from "lucide-react";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="w-full max-w-xs">
<FieldLabel htmlFor="input-icon-search">Search</FieldLabel>
<div className="relative">
<div
aria-hidden="true"
className="pointer-events-none absolute inset-y-0 start-0 flex items-center ps-3 text-muted-foreground/80"
>
<SearchIcon className="size-4" />
</div>
<Input
className="ps-9"
id="input-icon-search"
placeholder="Search projects…"
type="search"
/>
</div>
</Field>
);
}
Color Picker
A type="color" input styled to match the design system, used for theme or brand color selection.
Choose the primary color for your workspace theme.
import {
Field,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
return (
<Field className="max-w-xs">
<FieldLabel htmlFor="input-color">Brand color</FieldLabel>
<Input defaultValue="#6366f1" id="input-color" type="color" />
<FieldDescription>
Choose the primary color for your workspace theme.
</FieldDescription>
</Field>
);
}
With Clear Button
A text input with an × clear button that appears only when the field has a value and empties it on click.
"use client";
import { XIcon } from "lucide-react";
import { useState } from "react";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function Pattern() {
const [value, setValue] = useState("Alex Rivera");
return (
<Field className="w-full max-w-xs">
<FieldLabel htmlFor="input-clearable">Full name</FieldLabel>
<div className="relative">
<Input
className={value ? "pe-9" : ""}
id="input-clearable"
onChange={(e) => setValue(e.target.value)}
placeholder="Enter your full name"
type="text"
value={value}
/>
{value && (
<button
aria-label="Clear input"
className="absolute inset-e-0 inset-y-0 flex h-full w-9 items-center justify-center rounded-e-md text-muted-foreground/80 outline-none transition-colors hover:text-foreground focus-visible:ring-2"
onClick={() => setValue("")}
type="button"
>
<XIcon aria-hidden="true" className="size-4" />
</button>
)}
</div>
</Field>
);
}
On This Page

