HextaUI

Input

Um campo de texto com três tamanhos, estados inválido e somente leitura, estilo de validação nativa e fonte de 16px no toque para que os celulares nunca façam zoom.

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function InputDemo() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <Label htmlFor="input-demo-email">Email</Label>
      <Input
        id="input-demo-email"
        type="email"
        autoComplete="email"
        placeholder="[email protected]"
      />
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/input.json

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

import { Input } from "@/components/ui/input"
<label htmlFor="email">Email</label>
<Input id="email" type="email" placeholder="[email protected]" />

Tamanhos

sm, default e lg correspondem às alturas dos botões, então um input e um botão do mesmo tamanho se alinham em uma linha.

import { Input } from "@/components/ui/input"

export function InputSizes() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <Input size="sm" aria-label="Small" placeholder="Small" />
      <Input aria-label="Default" placeholder="Default" />
      <Input size="lg" aria-label="Large" placeholder="Large" />
    </div>
  )
}

Com uma descrição

Aponte aria-describedby para o texto de ajuda para que os leitores de tela o leiam depois do rótulo.

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function InputDescription() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <Label htmlFor="input-username">Username</Label>
      <Input
        id="input-username"
        autoComplete="username"
        placeholder="preet"
        aria-describedby="input-username-description"
      />
      <p
        id="input-username-description"
        className="text-sm text-muted-foreground"
      >
        Shown on your profile and in mentions.
      </p>
    </div>
  )
}

Inválido

aria-invalid deixa a borda e o anel de foco vermelhos. Vincule a mensagem com aria-describedby para que seja anunciada, e não apenas colorida.

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function InputInvalid() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <Label htmlFor="input-invalid">Email</Label>
      <Input
        id="input-invalid"
        type="email"
        defaultValue="preet@"
        aria-invalid
        aria-describedby="input-invalid-error"
      />
      <p id="input-invalid-error" className="text-sm text-destructive">
        Enter a full email address, like [email protected].
      </p>
    </div>
  )
}

Validação nativa

Campos com required, type="email" ou pattern só ficam vermelhos depois que alguém digitou neles ou tentou enviar, nunca na primeira renderização. Um envio que encontra um campo inválido o balança uma vez, para que o olhar caia no que precisa de correção. Ele nunca balança enquanto você digita ou navega com Tab. Envie o formulário vazio para ver.

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function InputNativeValidation() {
  return (
    <form className="flex w-full max-w-sm flex-col gap-3">
      <div className="flex flex-col gap-2">
        <Label htmlFor="input-native-email">Email</Label>
        <Input
          id="input-native-email"
          name="email"
          type="email"
          required
          placeholder="[email protected]"
        />
      </div>
      <div className="flex flex-col gap-2">
        <Label htmlFor="input-native-code">Invite code</Label>
        <Input
          id="input-native-code"
          name="code"
          required
          pattern="[A-Z]{4}-[0-9]{4}"
          placeholder="ABCD-1234"
        />
      </div>
      <Button type="submit" className="self-start">
        Join
      </Button>
    </form>
  )
}

Desabilitado

Um input desativado não pode receber foco, ser editado nem ser enviado com o formulário.

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function InputDisabled() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <Label htmlFor="input-disabled">Workspace</Label>
      <Input id="input-disabled" defaultValue="Acme Inc." disabled />
    </div>
  )
}

Somente leitura

readOnly mantém o valor focável, selecionável e enviado, com uma superfície atenuada para que não pareça editável. Prefira-o a disabled para valores que as pessoas precisam copiar.

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function InputReadOnly() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <Label htmlFor="input-read-only">API key</Label>
      <Input id="input-read-only" readOnly defaultValue="sk_live_51H8a…f2Qz" />
    </div>
  )
}

Arquivo

type="file" recebe a mesma moldura, com o botão do navegador reestilizado como texto simples.

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function InputFile() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <Label htmlFor="input-file">Avatar</Label>
      <Input id="input-file" type="file" accept="image/*" />
    </div>
  )
}

Tipos de input

Password, number, search, date e time compartilham a mesma altura e moldura. No modo escuro, os seletores e spinners do navegador também ficam escuros.

import { Input } from "@/components/ui/input"

export function InputTypes() {
  return (
    <div className="grid w-full max-w-sm gap-3">
      <Input type="password" aria-label="Password" defaultValue="hunter2" />
      <Input type="number" aria-label="Seats" defaultValue={12} min={1} />
      <Input type="search" aria-label="Search" placeholder="Search…" />
      <Input type="date" aria-label="Start date" defaultValue="2026-10-03" />
      <Input type="time" aria-label="Start time" defaultValue="09:30" />
    </div>
  )
}

Controlado

onValueChange entrega a string diretamente, então não há event.target.value para desembrulhar. onChange também continua funcionando.

"use client"

import * as React from "react"

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

const limit = 32

export function InputControlled() {
  const [value, setValue] = React.useState("Quarterly planning")

  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <Label htmlFor="input-controlled">Project name</Label>
      <Input
        id="input-controlled"
        value={value}
        maxLength={limit}
        onValueChange={setValue}
        aria-describedby="input-controlled-count"
      />
      <p
        id="input-controlled-count"
        className="text-end text-sm text-muted-foreground tabular-nums"
      >
        {value.length}/{limit}
      </p>
    </div>
  )
}

Com um botão

Lado a lado com um espaço, ou unidos em um único controle dentro de um <ButtonGroup />, onde o input ocupa a largura restante.

import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import { Input } from "@/components/ui/input"

export function InputWithButton() {
  return (
    <form className="flex w-full max-w-sm flex-col gap-4">
      <div className="flex gap-2">
        <Input type="email" aria-label="Email" placeholder="[email protected]" />
        <Button type="submit">Subscribe</Button>
      </div>
      <ButtonGroup className="w-full">
        <Input type="search" aria-label="Search" placeholder="Search…" />
        <Button variant="outline">Search</Button>
      </ButtonGroup>
    </form>
  )
}

Grid

Os inputs preenchem seu contêiner, então coloque-os em um grid. Dê às células do grid min-w-0 para que valores longos não estiquem uma coluna.

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function InputGrid() {
  return (
    <div className="grid w-full max-w-sm grid-cols-2 gap-3">
      <div className="flex min-w-0 flex-col gap-2">
        <Label htmlFor="input-first-name">First name</Label>
        <Input id="input-first-name" autoComplete="given-name" />
      </div>
      <div className="flex min-w-0 flex-col gap-2">
        <Label htmlFor="input-last-name">Last name</Label>
        <Input id="input-last-name" autoComplete="family-name" />
      </div>
    </div>
  )
}

Conteúdo longo

Valores longos rolam dentro do campo e placeholders longos são cortados, sem alargar o layout.

import { Input } from "@/components/ui/input"

export function InputLongContent() {
  return (
    <div className="flex w-full max-w-56 flex-col gap-3">
      <Input
        aria-label="URL"
        defaultValue="https://example.com/a/really/long/url/without/any/spaces/at/all"
      />
      <Input
        aria-label="Note"
        placeholder="A placeholder that is far too long to fit in this field"
      />
    </div>
  )
}

Da direita para a esquerda

O texto, o cursor e o padding seguem a direção. Use dir="auto" em campos que guardam valores da esquerda para a direita, como um endereço de e-mail em um formulário em árabe.

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function InputRtl() {
  return (
    <div dir="rtl" className="flex w-full max-w-sm flex-col gap-2">
      <Label htmlFor="input-rtl">البريد الإلكتروني</Label>
      <Input id="input-rtl" placeholder="[email protected]" dir="auto" />
      <Input aria-label="الاسم" placeholder="اكتب اسمك" />
    </div>
  )
}
  • Todo input precisa de um nome. Use um <label> com htmlFor, ou aria-label quando não houver rótulo visível. Um placeholder não é um rótulo.
  • Conecte o texto de ajuda e de erro com aria-describedby, e defina aria-invalid somente quando houver um erro para mostrar.
  • Em telas sensíveis ao toque, o texto tem pelo menos 16px, para que o Safari do iOS não aplique zoom na página quando o input recebe foco.
  • Dentro de um Field do Base UI, o rótulo, a descrição, o erro e a validade são ligados para você.

Construído sobre o input do Base UI. Aceita todos os atributos nativos de input.

PropTipoPadrão
sizeAltura e padding, alinhados aos botões.
"sm" | "default" | "lg""default"
htmlSizeO atributo nativo size, renomeado porque size é a variant.
number–
value
string | number | string[]–
defaultValue
string | number | string[]–
onValueChangeChamado com o novo valor a cada mudança.
(value: string, details) => void–
type
string"text"
disabled
booleanfalse
readOnly
booleanfalse
aria-invalidMostra a borda de inválido e o anel de foco.
boolean–
className
string | (state) => string–
shakeBalança uma vez quando o envio de um formulário encontra este input inválido. Funciona com validação nativa, o Field do Base UI e bibliotecas que definem aria-invalid. Ignorado com movimento reduzido.
booleantrue
render
ReactElement | (props, state) => ReactElement<input>
AtributoDescrição
data-slot="input"Seleciona o input no CSS.
data-sizeO tamanho atual.
data-shakePresente enquanto o input balança após um envio malsucedido.
data-disabledPresente quando o input está desativado.
data-invalidPresente quando o Field ao redor é inválido. Estilizado como aria-invalid.
data-validPresente quando o Field ao redor é válido.
data-touchedPresente depois que o input perdeu o foco uma vez, dentro de um Field.
data-dirtyPresente depois que o valor mudou, dentro de um Field.
data-filledPresente quando o input tem um valor, dentro de um Field.
data-focusedPresente enquanto tem foco, dentro de um Field.

Os nomes de classe por trás do input, para estilizar outro elemento de forma equivalente, como um <select> ou <textarea> nativo. Chame com { size }.

A contagem de caracteres por trás de <InputGroupCount /> e <FieldCounter />. Use essas partes, que leem o campo para você. Recorra a esta apenas quando você mesmo acompanhar o tamanho.

PropTipoPadrão
lengthObrigatório.
number–
maxLength
number | null–
threshold
number10% of maxLength, at most 20
announcement
(remaining: number) => string–
AtributoDescrição
data-slot="input-count"Selecione a contagem no CSS.
data-state="near" | "limit"Presente dentro do limite e no máximo.

Usado em blocos

Blocos que se baseiam em Input.