@cf/ui

@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 FieldLabel or Label whose htmlFor matches the input's id, or give it an aria-label when there's no visible label.

    label-every-input

  • must

    Don't override the text size below md to anything under 16px; if you change it, keep the text-base md:text-sm pairing.

    iOS zooms the page into any field set smaller than 16px and never zooms back out.

    keep-16px-on-touch

  • must

    Inside an InputGroup, use InputGroupInput instead 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-invalid on the input and data-invalid on its Field, and know that Input has no aria-invalid styles of its own yet.

    invalid-needs-your-own-styles

Install

bunx shadcn@latest add @cf/input

Needs the @cf registry in your components.json once. The theme and any sibling components come along automatically.

Source

input.tsxShow
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 };