Input
BetaCampo de texto de una línea, con label, ayuda y error integrados.
registry/ds/input.tsxTe enviamos ahí la confirmación.
import { Input } from "@/registry/ds/input";
export default function InputBasic() {
return (
<div className="w-80">
<Input label="Correo institucional" type="email" placeholder="nombre@universidad.edu" hint="Te enviamos ahí la confirmación." />
</div>
);
}Cuándo usarlo
| Usá Input para… | Usá otra cosa para… |
|---|---|
| Texto corto y libre: nombre, correo, teléfono. | Varias líneas → Textarea. |
| Búsquedas. | Elegir de una lista conocida → Select o Radio. |
Números y fechas simples (type). | Códigos de verificación → Input Code. |
Anatomía
Podés cambiarla después.
- 1Label — Siempre visible. Dice qué dato se pide.
- 2Requerido — Asterisco. Marcá lo obligatorio — o mejor, lo opcional, si es la minoría.
- 3Campo — Contiene icono opcional, valor o placeholder.
- 4Ayuda / error — Una sola línea bajo el campo. El error la reemplaza.
Estados
import { Input } from "@/registry/ds/input";
export default function InputStates() {
return (
<div className="grid w-full max-w-xl gap-5 sm:grid-cols-2">
<Input label="Vacío" placeholder="Placeholder" />
<Input label="Con valor" defaultValue="Ingeniería Civil" />
<Input label="Requerido" required placeholder="Obligatorio" />
<Input label="Con error" defaultValue="juan@" error="Revisá el formato: falta el dominio." />
<Input label="Deshabilitado" disabled defaultValue="No editable" />
<Input label="Solo lectura" readOnly defaultValue="2026-1" />
</div>
);
}El error aparece cuando la persona terminó de escribir (al salir del campo o al enviar), no mientras tipea. El mensaje dice cómo arreglarlo, no solo que está mal.
Tamaños
import { Input } from "@/registry/ds/input";
export default function InputSizes() {
return (
<div className="flex w-80 flex-col gap-4">
<Input size="sm" aria-label="Small" placeholder="Small · 32" />
<Input size="md" aria-label="Medium" placeholder="Medium · 40" />
<Input size="lg" aria-label="Large" placeholder="Large · 48" />
</div>
);
}Mismo alto que el Button del mismo tamaño: en una fila van siempre iguales.
Con icono
Los iconos son decorativos y refuerzan el tipo de dato. No reemplazan al label.
import { Input } from "@/registry/ds/input";
import { Search } from "@/registry/ds/icons";
export default function InputIcons() {
return (
<div className="w-80">
<Input aria-label="Buscar materias" type="search" placeholder="Buscar materias…" iconLeft={<Search />} />
</div>
);
}Afijos y botón final
Un segmento fijo antes o después del valor ahorra tipeo y evita errores de
formato. Con radius="full" es el Rounded Input de Figma: la píldora para
dominios y subdominios.
import { Button } from "@/registry/ds/button";
import { Input } from "@/registry/ds/input";
export default function InputAffixes() {
return (
<div className="flex w-full max-w-md flex-col gap-5">
<Input label="Sitio web" prefix="https://" placeholder="universidad.edu" />
<Input label="Peso" suffix="kg" inputMode="decimal" placeholder="0" className="max-w-40" />
<Input label="Enlace de invitación" defaultValue="polen.edu/i/4F9K2" readOnly action={<Button variant="text" size="sm">Copiar</Button>} />
<Input label="Subdominio" radius="full" prefix="https://" suffix=".polen.edu" placeholder="mi-campus" />
</div>
);
}- El afijo es parte del valor, no una instrucción: "https://", "kg", ".com".
- El botón final (
action) es para una acción sobre el valor (Copiar, Buscar), siempre envariant="text"y neutro: el campo no es un CTA.
Contenido
- Label corto y sin dos puntos: "Correo", no "Ingresá tu correo:".
- El placeholder es un ejemplo, no una instrucción, y nunca reemplaza al label: desaparece al escribir y tiene menos contraste.
- La ayuda previene el error: formato esperado, para qué se usa el dato.
- El error es específico y amable: "Falta el dominio (@universidad.edu)", no "Campo inválido".
- El ancho del campo sugiere el largo de la respuesta.
Solo números, sin el 15.
Label visible, placeholder como ejemplo, la regla en la ayuda.
Todo en el placeholder: al escribir desaparecen el nombre del campo y la regla.
Medidas
| Size | Alto | Padding X | Valor | Icono | Radius |
|---|---|---|---|---|---|
sm | 32 | spacing-xl 12 | Text sm | 16 | radius-md 8 |
md | 40 | 14 | Text sm | 20 | radius-md 8 |
lg | 48 | spacing-2xl 16 | Text md | 20 | radius-md 8 |
Label → campo → ayuda: gap spacing-sm (6). Label y ayuda: Text sm.
Sombra del campo: Elevation/xs (se quita en disabled).
Tokens por estado
| Parte | Propiedad | Token |
|---|---|---|
| Campo | Fondo | bg/elevated |
| Campo | Borde | border/default |
| Campo · hover | Borde | border/hover |
| Campo · foco | Borde + anillo 2 px | focus/ring |
| Campo · error | Borde | danger/accent |
| Campo · error + foco | Anillo 2 px | danger/border-strong |
| Campo · disabled | Fondo | bg/subtle |
| Valor | Color | text/default |
| Placeholder | Color | text/subtle |
| Valor · disabled | Color | text/disabled |
| Icono | Color | icon/subtle |
| Afijo | Fondo | bg/subtle |
| Afijo | Texto | text/subtle |
| Afijo / botón | Divisor | border/default |
| Label | Color | text/default |
| Asterisco | Color | danger/accent |
| Ayuda | Color | text/subtle |
| Error | Color | danger/text |
En Figma
| Propiedad en Figma | Valores | En código | Nota |
|---|---|---|---|
| Size | sm · md · lg | size | — |
| State | Placeholder · Filled · Focused · Disabled | disabled | Placeholder/Filled/Focused son automáticos. |
| Destructive | False · True | error | En código, pasar el mensaje marca el campo inválido. |
| Label · Show label | Texto · on/off | label | — |
| Required | on/off | required | — |
| Hint · Show hint | Texto · on/off | hint | — |
| Icon left / right (+ swap) | on/off · icono | iconLeft · iconRight | — |
| Type | Default · Leading text · Trailing button · dropdowns · Payment · Tags | prefix · suffix · action | Dropdown, Payment y Tags siguen pendientes en código. |
| Rounded Input | Componente aparte | radius="full" | Con prefix / suffix para el afijo en píldora. |
Instalación
npx shadcn@latest add https://polen.cdc.cool/r/input.jsonPlayground
Configurá el componente y copiá el snippet: se genera del mismo estado que renderiza el preview.
Te enviamos ahí la confirmación.
<Input label="Correo institucional" placeholder="nombre@universidad.edu" hint="Te enviamos ahí la confirmación." />Ejemplos
import { Input } from "@/registry/ds/input";
export default function InputStates() {
return (
<div className="grid w-full max-w-xl gap-5 sm:grid-cols-2">
<Input label="Vacío" placeholder="Placeholder" />
<Input label="Con valor" defaultValue="Ingeniería Civil" />
<Input label="Requerido" required placeholder="Obligatorio" />
<Input label="Con error" defaultValue="juan@" error="Revisá el formato: falta el dominio." />
<Input label="Deshabilitado" disabled defaultValue="No editable" />
<Input label="Solo lectura" readOnly defaultValue="2026-1" />
</div>
);
}Props
Acepta todos los atributos nativos de <input> (type, name, value,
onChange, autoComplete, inputMode…). Label, ayuda y error se asocian al
control con Base UI Field.
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
size | "sm" | "md" | "lg" | "md" | Alto 32 / 40 / 48. |
label | ReactNode | — | Label visible. Sin él, pasá aria-label. |
hint | ReactNode | — | Ayuda bajo el campo. |
error | ReactNode | — | Mensaje de error. Marca aria-invalid y reemplaza a la ayuda. |
required | boolean | false | Agrega el asterisco y el atributo nativo. |
iconLeft | ReactNode | — | Icono decorativo antes del valor. |
iconRight | ReactNode | — | Icono o acción después del valor. |
prefix · suffix | ReactNode | — | Segmento fijo antes / después del valor (https://, kg). |
action | ReactNode | — | Botón pegado al final: un Button variant="text" size="sm". |
radius | "md" | "full" | "md" | full = Rounded Input (píldora). |
disabled | boolean | false | No editable ni enfocable. |
Qué viene resuelto
<label for>asociado: clic en el label enfoca el campo.- Ayuda y error conectados con
aria-describedby; el error marcaaria-invalid. - Foco visible en el contenedor completo (borde + anillo).
- El asterisco es decorativo (
aria-hidden): lo obligatorio lo anunciarequired.
Qué tenés que cuidar vos
typeyautoCompletecorrectos (email,tel,given-name…): activan el teclado adecuado en mobile y el autocompletado.inputMode="numeric"para números que no son cantidades (DNI, legajo).- No deshabilites el pegado.
readOnlyen vez dedisabledcuando el valor importa: se puede enfocar, copiar y se envía con el formulario.- Con varios errores, llevá el foco al primero al enviar.