Input
A text input with 3 variants, 3 sizes and 3 tones. Invalid state via aria-invalid, focus ring driven by the theme ring token.
import { Input } from "@/components/ui/input"
export default function InputDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<Input type="email" placeholder="you@company.com" />
<Input variant="soft" placeholder="Soft variant" />
<Input tone="success" defaultValue="Success tone" />
<Input placeholder="Invalid" aria-invalid />
</div>
)
}Installation
Run the following command
npx shadcn@latest add @ai2/inputDependencies, the @ai2/tokens theme and the component file are installed together.
Install dependencies
npm install class-variance-authority@^0.7.1Add the cn util
lib/utils.tsimport { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
/* Adds a source-attribution ref param to a URL (the inspiration exports mark their
outbound links with an ai2.design attribution). An invalid URL is returned as is.
This file is SHOWN TO THE CONSUMER: the docs component pages render the source of
`cn` in a code block, so a Turkish comment here would reach every one of those
pages. Keep it English. */
export function withRef(url: string, ref = "ai2.design"): string {
try {
const u = new URL(url)
u.searchParams.set("ref", ref)
return u.toString()
} catch {
return url
}
}Copy the source code
components/ui/input.tsx"use client"
import type * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const inputVariants = cva(
"flex w-full min-w-0 outline-none transition-[color,box-shadow] file:inline-flex file:border-0 file:bg-transparent file:font-medium file:text-foreground placeholder:text-muted-foreground selection:bg-brand selection:text-brand-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40",
{
variants: {
variant: {
outline: "border border-field-border bg-transparent shadow-xs dark:bg-input/30",
soft: "border border-transparent bg-surface-3 focus-visible:bg-transparent dark:bg-input/50",
ghost: "border border-transparent bg-transparent hover:bg-surface-3 focus-visible:bg-transparent",
},
size: {
/* min-h is required TOGETHER with h: inside a column-direction flex
container (for example a `flex flex-col sm:flex-row` form row,
column on mobile) height is the MAIN AXIS, and because flex-shrink
defaults to 1 the `h-9` gets overridden; the field collapses to the
content's min height (20px). It is invisible in row direction on
desktop and shows up ONLY on mobile - measured exactly like this on
the home page CTA, 2026-07-26 (input 20px, button 36px).
`shrink-0` is NOT the right fix: it locks the horizontal axis too and
causes overflow with `w-full` in row direction. min-h guards the
vertical axis only. */
sm: "h-8 min-h-8 rounded-md px-2.5 text-sm file:h-6 file:text-xs",
md: "h-9 min-h-9 rounded-lg px-3 text-sm file:h-7 file:text-sm",
lg: "h-10 min-h-10 rounded-lg px-3.5 text-base file:h-8 file:text-sm md:text-sm",
},
tone: {
neutral: "",
success:
"border-success focus-visible:border-success focus-visible:ring-success/20 dark:focus-visible:ring-success/40",
danger:
"border-danger focus-visible:border-danger focus-visible:ring-danger/20 dark:focus-visible:ring-danger/40",
},
},
defaultVariants: {
variant: "outline",
size: "md",
tone: "neutral",
},
}
)
interface InputProps
extends Omit<React.ComponentProps<"input">, "size">,
VariantProps<typeof inputVariants> {}
function Input({ className, variant, size, tone, type, ...props }: InputProps) {
return (
<input
type={type}
data-slot="input"
data-variant={variant ?? "outline"}
data-tone={tone ?? "neutral"}
className={cn(inputVariants({ variant, size, tone, className }))}
{...props}
/>
)
}
export { Input, inputVariants, type InputProps }Manual installs skip the @ai2/tokens theme, so add the token CSS from the theming guide or the surface colors will be missing.
Usage
import { Input } from "@/components/ui/input"
<Input type="email" placeholder="you@company.com" />All three axes are optional. Omit them and you get the outline medium neutral input. Any native input type works, including file inputs.
Examples
Variants
Use variant="soft" on dense forms and filled surfaces; soft and ghost become transparent while focused so the ring stays legible.
Sizes
Tones
The tone axis colors the border and focus ring with the success or danger token, giving controlled validation feedback without touching aria-invalid.
Invalid state
Set aria-invalid on validation errors. The danger border and ring style is built in, no extra classes needed.
Disabled
The native disabled attribute halves the opacity, blocks pointer events and sets a not-allowed cursor. A following ai2 Label dims with it automatically.
File input
Props
Input also accepts every native <input> prop except size, which is repurposed for the size axis.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "outline" | "soft" | "ghost" | "outline" | Visual style of the input field. |
size | "sm" | "md" | "lg" | "md" | Height, padding, radius and typography scale. Replaces the native size attribute. |
tone | "neutral" | "success" | "danger" | "neutral" | Semantic validation color: success and danger recolor the border and focus ring. |
ai2 Input: a react input with 3 variants, 3 sizes and 3 tones
The ai2 Input is a shadcn-compatible text input component for React, styled with Tailwind CSS v4 on the shared ai2 tokens. It ships three style axes from a single cva definition: 3 variants (outline, soft, ghost), 3 sizes (sm, md, lg) and 3 tones (neutral, success, danger), which is 27 combinations on top of any native input type, from email and password to file uploads.
Because it ships through the shadcn registry format, you install it with one CLI command, an MCP agent, or a copy-paste, and the source lands in your own project. You own the file; there is no runtime dependency on ai2 itself. The live example above is the exact component you get.
What is the ai2 Input?
It is a single styled <input> element driven by inputVariants, an exported cva definition with a variant axis (outline, soft, ghost), a size axis (sm, md, lg) and a tone axis (neutral, success, danger). The defaults are outline, md and neutral, and the anatomy matches shadcn/ui, so existing snippets and AI agents keep working.
Invalid state is attribute-driven: set aria-invalid and the danger border and ring styles apply on their own. File inputs are styled too, with a per-size file button treatment, and text selection uses the brand token so even highlighting matches your theme.
Why use it
- Three honest style axes: 3 variants times 3 sizes times 3 tones from one cva definition. Outline for classic forms, soft for filled surfaces, ghost for inline editing, each in three heights, with success and danger tones for validation.
- Invalid state built in: Set aria-invalid and the danger border plus ring appear automatically, in both light and dark mode. No error classes to remember.
- Focus ring from the theme: The focus-visible ring reads the theme ring token, so keyboard focus looks identical across every ai2 form control.
- File inputs handled: type="file" gets a styled, size-aware file button out of the box instead of the unstyled browser default.
- Agent-readable metadata: The registry item states the variant and size matrix in plain words, so an MCP agent can pick the right combination without reading the source.
Features
- shadcn registry install: One command adds the component and the @ai2/tokens theme to your project.
- 3 variants, 3 sizes, 3 tones: outline (default), soft and ghost, each in sm, md and lg with matched height, padding, radius and typography, plus neutral, success and danger tones for validation color.
- Focus-transparent soft and ghost: The soft and ghost variants drop their fill while focused, so the theme ring stays fully legible on any surface.
- Brand-colored selection: Selected text uses the brand token and its foreground pair, a small detail that keeps forms on-theme.
- Data attributes for styling: The element exposes data-slot="input", data-variant and data-tone, so you can restyle specific variants from CSS without forking.
- Exported inputVariants: The cva definition is exported, so you can apply the exact same styles to custom controls or wrappers.
Production tips
- Wire aria-invalid to validation: Map your form library's error state to aria-invalid instead of adding red border classes; the styles are already in the component for both modes.
- Pick variants by surface: Outline reads best on plain backgrounds, soft on cards and filled panels, ghost inside toolbars and tables where a visible border would add noise.
- Remember size replaces the native attribute: The size prop is the style axis, not the character-width attribute. Control width with classes like w-full or max-w-sm.
- Always pair with a label: Placeholders disappear on input. Use the ai2 Field or Label component so every input keeps an accessible, visible name.
- Use the right native type: email, tel, url and number types improve mobile keyboards and built-in validation for free; the styling is identical across all of them.
Works with the rest of ai2
The input is the core of the form kit. Wrap it in an ai2 Field to get label, description and error placement for free, pair it with an ai2 Label directly for simple cases, and submit with an ai2 Button.
For longer text, switch to the Textarea, which shares the same variant and size language, and for constrained choices reach for the Select. One token source keeps every control visually consistent in both modes.