Forms
Input
Text input with hover, focus, invalid, disabled, read-only and autofill states, sized to match Button.
We reply from a named person, never a no-reply address.
pnpm dlx shadcn@latest add @paragon/inputBrings in@base-ui/reactclass-variance-authoritycn
States
Hover darkens the border; focus turns it fjord with a soft halo; invalid stays red even while focused. Read-only goes dashed so it never reads as disabled.
Rest
Hover
Focus
Filled
Invalid
Invalid + focus
Disabled
Read-only
Sizes
Matched to Button heights, so search bars and inline forms align without overrides.
registry/paragon/ui/input.tsx
import * as React from "react"
import { Input as InputPrimitive } from "@base-ui/react/input"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
const inputVariants = cva(
[
"w-full min-w-0 rounded-button border border-input bg-card/70 text-foreground outline-none",
"transition-[border-color,box-shadow,background-color] duration-(--duration-base) ease-paragon",
"placeholder:text-muted-foreground",
"hover:border-foreground/35",
"focus-visible:border-ring focus-visible:bg-card focus-visible:ring-4 focus-visible:ring-ring/15",
"aria-invalid:border-destructive aria-invalid:focus-visible:ring-destructive/15",
"read-only:not-disabled:border-dashed read-only:not-disabled:bg-transparent",
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-secondary/60 disabled:opacity-55",
// Browsers paint autofilled fields yellow or blue; keep them on the card surface.
"autofill:shadow-[inset_0_0_0_100px_var(--card)] autofill:[-webkit-text-fill-color:var(--foreground)]",
"file:me-3 file:inline-flex file:h-7 file:items-center file:rounded-[calc(var(--radius-button)-4px)] file:border-0 file:bg-secondary file:px-2.5 file:text-sm file:font-medium file:text-foreground",
"dark:bg-input/15 dark:focus-visible:bg-input/25",
],
{
variants: {
size: {
sm: "h-9 rounded-[calc(var(--radius-button)-2px)] px-3 text-sm",
default: "h-[2.625rem] px-3.5 text-base md:text-[0.9375rem]",
lg: "h-12 px-4 text-base",
},
},
defaultVariants: {
size: "default",
},
}
)
// Emails, URLs and phone numbers are Latin in any language, so they stay
// left-to-right inside Arabic layouts and align to the start edge.
const LTR_TYPES = new Set(["email", "url", "tel"])
/** Sizes match Button, so inputs and buttons line up in a row. */
function Input({
className,
type,
size = "default",
...props
}: Omit<React.ComponentProps<"input">, "size"> & VariantProps<typeof inputVariants>) {
const ltr = type ? LTR_TYPES.has(type) : false
return (
<InputPrimitive
type={type}
dir={ltr ? "ltr" : undefined}
data-slot="input"
data-size={size}
className={cn(inputVariants({ size }), ltr && "rtl:text-right", className)}
{...props}
/>
)
}
export { Input, inputVariants }