@cf/input
Input
A single-line text field, 44px tall and 16px type on touch screens, shrinking to 36px and 14px at md.
A plain <input>, not a Base UI part. The 16px and 44px sizing on touch screens is the whole point of it: iOS zooms into anything smaller than 16px and a 36px field is awkward to hit, so both shrink back to the desktop size at md.
It's older than the rest of the set and it shows. It uses forwardRef, rounded-md and a ring-1 focus ring, where Textarea uses rounded-lg, a ring-3 focus ring and aria-invalid styles. The access form paints its own error border because of that gap. Bringing Input in line with Textarea is on the list.
import { Input } from "@/components/ui/input";
<Input id="email" type="email" autoComplete="email" />Rules
The same rules ship in DESIGN.md and the MCP server, for the agents building with this.
- must
Pair every Input with a
FieldLabelorLabelwhosehtmlFormatches the input'sid, or give it anaria-labelwhen there's no visible label.label-every-input
- must
Don't override the text size below
mdto anything under 16px; if you change it, keep thetext-base md:text-smpairing.iOS zooms the page into any field set smaller than 16px and never zooms back out.
keep-16px-on-touch
- must
Inside an
InputGroup, useInputGroupInputinstead of Input.The group's focus and invalid rings key off
data-slot="input-group-control", which only InputGroupInput sets.use-group-control-in-groups
- should
When a value is invalid, set
aria-invalidon the input anddata-invalidon itsField, and know that Input has noaria-invalidstyles of its own yet.invalid-needs-your-own-styles
Install
bunx shadcn@latest add @cf/inputNeeds the @cf registry in your components.json once. The theme and any sibling components come along automatically.
Source
input.tsxShowHide
import { cn } from "cn";
import * as React from "react";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
/**
* 16px and 44px tall on a touch screen: iOS zooms the page into any field set
* smaller than 16px and never zooms back out, and a 36px field is an awkward
* thing to hit. Both shrink to the desktop size at `md`, the same way
* components/ui/textarea.tsx and the chat composer already do it.
*/
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => (
<input
className={cn(
"flex h-11 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:h-9 md:text-sm",
className
)}
ref={ref}
type={type}
{...props}
/>
)
);
Input.displayName = "Input";
export { Input };