HextaUI

Input

Ein Texteingabefeld mit drei Größen, ungültigen und schreibgeschützten Zuständen, nativem Validierungsstyling und einer 16-px-Schrift für Touch, damit Smartphones nie hineinzoomen.

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

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

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

Größen

sm, default und lg entsprechen den Button-Höhen, sodass ein Input und ein Button gleicher Größe in einer Reihe fluchten.

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>
  )
}

Mit einer Beschreibung

Verweise mit aria-describedby auf den Hilfetext, damit Screenreader ihn nach dem Label vorlesen.

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>
  )
}

Ungültig

aria-invalid färbt den Rand und den Fokusring rot. Verknüpfe die Meldung mit aria-describedby, damit sie angesagt und nicht nur eingefärbt wird.

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>
  )
}

Native Validierung

Felder mit required, type="email" oder pattern werden erst rot, nachdem jemand hineingetippt oder abzusenden versucht hat, nie beim ersten Rendern. Ein Absenden, das ein Feld als ungültig vorfindet, lässt es einmal wackeln, damit der Blick auf das fällt, was korrigiert werden muss. Es wackelt nie, während du tippst oder durch die Felder tabst. Sende das leere Formular ab, um es zu sehen.

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>
  )
}

Deaktiviert

Ein deaktiviertes Input lässt sich weder fokussieren noch bearbeiten noch mit dem Formular absenden.

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>
  )
}

Schreibgeschützt

readOnly hält den Wert fokussierbar, auswählbar und sendbar, mit einer gedämpften Fläche, damit er nicht bearbeitbar aussieht. Bevorzuge es gegenüber disabled bei Werten, die Nutzer kopieren müssen.

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>
  )
}

Datei

type="file" erhält denselben Rahmen, wobei der Button des Browsers als einfacher Text gestylt wird.

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>
  )
}

Input-Typen

Passwort, Zahl, Suche, Datum und Zeit teilen sich eine Höhe und einen Rahmen. Im Dark Mode wechseln auch die Picker und Spinner des Browsers auf dunkel.

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>
  )
}

Kontrolliert

onValueChange liefert dir den String direkt, sodass kein event.target.value ausgepackt werden muss. onChange funktioniert ebenfalls weiterhin.

"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>
  )
}

Mit einem Button

Nebeneinander mit Abstand oder zu einem Steuerelement in einem <ButtonGroup /> verbunden, wo das Input die verbleibende Breite einnimmt.

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

Inputs füllen ihren Container, setze sie daher in ein Grid. Gib Grid-Zellen min-w-0, damit lange Werte keine Spalte dehnen können.

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>
  )
}

Langer Inhalt

Lange Werte scrollen im Feld, und lange Platzhalter werden abgeschnitten, ohne das Layout zu verbreitern.

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>
  )
}

Rechts nach links

Text, Cursor und Padding folgen der Richtung. Verwende dir="auto" bei Feldern mit Links-nach-rechts-Werten, etwa einer E-Mail-Adresse in einem arabischen Formular.

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>
  )
}
  • Jedes Input braucht einen Namen. Verwende ein <label> mit htmlFor oder aria-label, wenn es kein sichtbares Label gibt. Ein Platzhalter ist kein Label.
  • Verknüpfe Hilfs- und Fehlertext mit aria-describedby und setze aria-invalid erst, wenn es einen Fehler anzuzeigen gibt.
  • Auf Touchscreens ist der Text mindestens 16 px groß, damit iOS Safari die Seite nicht zoomt, wenn das Input fokussiert wird.
  • In einem Base UI Field sind Label, Beschreibung, Fehler und Validität automatisch verdrahtet.

Basiert auf dem Base UI Input. Es akzeptiert jedes native Input-Attribut.

PropTypStandard
sizeHöhe und Padding, passend zu den Buttons.
"sm" | "default" | "lg""default"
htmlSizeDas native size-Attribut, umbenannt, weil size die Variante ist.
number–
value
string | number | string[]–
defaultValue
string | number | string[]–
onValueChangeWird bei jeder Änderung mit dem neuen Wert aufgerufen.
(value: string, details) => void–
type
string"text"
disabled
booleanfalse
readOnly
booleanfalse
aria-invalidZeigt den Rand für ungültig und den Fokusring.
boolean–
className
string | (state) => string–
shakeWackelt einmal, wenn ein Formular-Submit dieses Input als ungültig vorfindet. Funktioniert mit nativer Validierung, Base UI Field und Bibliotheken, die aria-invalid setzen. Bei reduzierter Bewegung übersprungen.
booleantrue
render
ReactElement | (props, state) => ReactElement<input>
AttributBeschreibung
data-slot="input"Das Input in CSS ansprechen.
data-sizeDie aktuelle Größe.
data-shakeVorhanden, während das Input nach einem fehlgeschlagenen Absenden wackelt.
data-disabledVorhanden, wenn das Input deaktiviert ist.
data-invalidVorhanden, wenn das umgebende Field ungültig ist. Gestylt wie aria-invalid.
data-validVorhanden, wenn das umgebende Field gültig ist.
data-touchedVorhanden, nachdem das Input einmal den Fokus verloren hat, innerhalb eines Field.
data-dirtyVorhanden, sobald sich der Wert geändert hat, innerhalb eines Field.
data-filledVorhanden, wenn das Input einen Wert hat, innerhalb eines Field.
data-focusedVorhanden, solange fokussiert, innerhalb eines Field.

Die Klassennamen hinter dem Input, um ein anderes Element passend zu stylen, etwa ein natives <select> oder <textarea>. Rufe sie mit { size } auf.

Die Zeichenzahl hinter <InputGroupCount /> und <FieldCounter />. Verwende diese Teile, die das Feld für dich lesen. Greife nur dann darauf zurück, wenn du die Länge selbst verfolgst.

PropTypStandard
lengthErforderlich.
number–
maxLength
number | null–
threshold
number10% of maxLength, at most 20
announcement
(remaining: number) => string–
AttributBeschreibung
data-slot="input-count"Den Zähler in CSS ansprechen.
data-state="near" | "limit"Vorhanden innerhalb des Schwellenwerts und am Limit.

In Blocks verwendet

Blocks, die auf Input aufbauen.