Search

Jump to any page.

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/input
Brings 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 }