HextaUI

Input

3つのサイズ、無効状態と読み取り専用状態、ネイティブのバリデーションスタイル、スマートフォンでズームされない16pxのタッチ用フォントを備えたテキスト入力です。

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

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

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

サイズ

sm、default、lg はボタンの高さに合っているため、同じサイズの入力欄とボタンが 1 行に揃います。

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

説明付き

aria-describedby を補足テキストに向けると、スクリーンリーダーがラベルの後にそれを読み上げます。

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

無効な値

aria-invalid は、縁とフォーカスリングを赤くします。メッセージは色が付くだけでなく通知されるよう、aria-describedby で関連付けます。

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

ネイティブの検証

required、type="email"、pattern を持つフィールドは、誰かが入力した後、または送信を試みた後にのみ赤くなり、最初のレンダリング時には赤くなりません。送信時にフィールドが無効と判定されると、そのフィールドが 1 回揺れて、修正が必要な箇所に目が向きます。入力中やタブで移動している間は揺れません。空のフォームを送信すると確認できます。

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

無効

無効な入力欄には、フォーカス、編集、フォームでの送信ができません。

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

読み取り専用

readOnly は、値をフォーカス可能、選択可能、送信可能なままにし、編集できるように見えないよう控えめな面で表示します。コピーしたい値には、disabled よりこちらをお勧めします。

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

ファイル

type="file" にも同じ枠が付き、ブラウザのボタンはプレーンなテキストとしてスタイルされます。

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

入力の種類

パスワード、数値、検索、日付、時刻は、同じ高さと枠を共有します。ダークモードでは、ブラウザのピッカーやスピナーもダークに切り替わります。

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

制御

onValueChange は文字列を直接渡すため、event.target.value を取り出す必要がありません。onChange も引き続き使えます。

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

ボタンと組み合わせる

余白を空けて横に並べるか、<ButtonGroup /> の内側で 1 つのコントロールに連結します。後者では、入力欄が残りの幅を占めます。

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

グリッド

入力欄はコンテナいっぱいに広がるため、グリッド内に配置します。長い値で列が広がらないよう、グリッドのセルに min-w-0 を付けてください。

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

長いコンテンツ

長い値はフィールド内でスクロールし、長いプレースホルダーは切り詰められ、レイアウトが広がることはありません。

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

右から左

テキスト、キャレット、パディングは方向に従います。アラビア語のフォームにあるメールアドレスのように、左から右の値を保持するフィールドには dir="auto" を使います。

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>
  )
}
  • すべての入力欄には名前が必要です。htmlFor を持つ <label> を使うか、表示されるラベルがない場合は aria-label を使ってください。プレースホルダーはラベルではありません。
  • 補足テキストとエラーテキストは aria-describedby で関連付け、aria-invalid は、表示するエラーがあるときにだけ設定してください。
  • タッチスクリーンではテキストが 16px 以上になるため、入力欄にフォーカスしても iOS Safari がページをズームすることはありません。
  • Base UI の Field の内側では、ラベル、説明、エラー、有効性が自動で結び付けられます。

Base UI の input をベースにしています。ネイティブの input のすべての属性を受け付けます。

プロパティ型デフォルト
sizeボタンに合わせた、高さとパディング。
"sm" | "default" | "lg""default"
htmlSizeネイティブの size 属性。size は variant に使われているため、名前を変えています。
number–
value
string | number | string[]–
defaultValue
string | number | string[]–
onValueChange変更のたびに、新しい値とともに呼ばれます。
(value: string, details) => void–
type
string"text"
disabled
booleanfalse
readOnly
booleanfalse
aria-invalid不正の縁とフォーカスリングを表示します。
boolean–
className
string | (state) => string–
shakeフォームの送信時にこの入力欄が無効と判定されると、1 回揺れます。ネイティブの検証、Base UI の Field、aria-invalid を設定するライブラリで動作します。モーションの低減が有効な場合はスキップされます。
booleantrue
render
ReactElement | (props, state) => ReactElement<input>
属性説明
data-slot="input"CSS で入力欄を指定します。
data-size現在のサイズ。
data-shake送信に失敗した後、入力欄が揺れている間、付与されます。
data-disabled入力欄が無効なときに付与されます。
data-invalid周囲の Field が不正なときに付与されます。aria-invalid と同じようにスタイルされます。
data-valid周囲の Field が有効なときに付与されます。
data-touchedField 内で、入力欄が一度フォーカスを失った後に付与されます。
data-dirtyField 内で、値が変更された後に付与されます。
data-filledField 内で、入力欄に値があるときに付与されます。
data-focusedField 内で、フォーカスされている間、付与されます。

入力欄の背後にあるクラス名。ネイティブの <select> や <textarea> など、他の要素のスタイルを合わせるために使います。{ size } を渡して呼び出します。

<InputGroupCount /> と <FieldCounter /> の背後にある文字数カウント。フィールドを自動で読み取ってくれるそれらのパーツを使ってください。自分で長さを追跡する場合にのみ、これを使います。

プロパティ型デフォルト
length必須。
number–
maxLength
number | null–
threshold
number10% of maxLength, at most 20
announcement
(remaining: number) => string–
属性説明
data-slot="input-count"CSSでカウントを指定します。
data-state="near" | "limit"しきい値内、および上限に達したときに付与されます。

使用しているブロック

Input の上に構築されるブロック。