入力
Inputs入力系コンポーネントの一覧、共通ルール、迷いやすい使い分けをまとめたカテゴリ概要です。完成例を見るページではなく、どのコンポーネントから確認するべきかを判断するための入口です。
コンポーネント一覧
入力系に含まれるコンポーネント、役割、代表的な用途をカテゴリごとに確認できます。サムネイルはページを重くしないため、挙動を再現しすぎない軽量プレビューにしています。
文字と数値
ユーザーが直接値を書き込む基本入力です。
選択
候補から選ぶ、または状態を切り替える入力です。
日時と範囲
日付、時刻、連続値を扱う入力です。
補足情報
一覧でコンポーネントを選んだあとに確認する、入力全体のルールと判断基準です。
入力の共通ルール
個別コンポーネントの見た目より先に、入力体験として揃えるべきルールです。
ラベルは操作対象と紐づける
Input、Checkbox、RadioGroup などは、表示ラベルと操作対象をアクセシブルに接続します。
補助テキストとエラーは同じ領域で扱う
通常時は説明、検証後はエラーを表示し、入力中に不要なエラーを出しすぎないようにします。
無効化には理由を付ける
操作できない理由は Tooltip と補助テキストで補足します。見た目だけで終わらせません。
幅と高さはフォーム単位で揃える
入力欄、セレクト、ボタン、補助 UI が同じフォーム内でばらつかないようにします。
保存や失敗にはフィードバックを返す
保存、アップロード、編集確定などの操作には Toast やインライン表示で結果を返します。
迷いやすい使い分け
似た入力を見た目だけで選ばないための判断基準です。
Select と Combobox
候補が少なく固定なら Select。候補が多く検索が必要なら Combobox。
Checkbox と Switch
フォーム送信まで確定しない同意や選択は Checkbox。即時反映される設定は Switch。
Slider と RangeSlider
1つの値なら Slider。最小値と最大値を同時に扱うなら RangeSlider。
InputOTP と専用入力
OTP は確認コード専用。電話番号、郵便番号、パスワード条件表示は別コンポーネント候補として扱います。
今後追加する候補
今回の Inputs 整備で必要性が見えたものです。個別対応ではなく、GunjoUI コンポーネントとして設計します。
候補の追加は、SSOT、docs、デモ、アクセシビリティ、無効化理由、入力幅ルールまで含めて扱います。
まとめて使う見本
この分類のコンポーネントをまとめて、設定を入力して保存する1画面を組んだ例です。そのまま貼り付けて動きます。
import { useState } from "react";
import {
Button,
Checkbox,
Form,
FormControl,
FormField,
FormLabel,
FormMessage,
Input,
NumberInput,
RadioGroup,
RadioGroupItem,
Select,
Slider,
Switch,
Textarea,
} from "@gunjo/ui";
const PLANS = [
{ value: "starter", label: "スターター", description: "1案件・レビュー担当1人。" },
{ value: "team", label: "チーム", description: "レビュー一覧と権限を共有。" },
{ value: "scale", label: "スケール", description: "操作の記録と社内アカウント連携。" },
];
const REGIONS = [
{ value: "tokyo", label: "東京" },
{ value: "osaka", label: "大阪" },
{ value: "fukuoka", label: "福岡" },
];
export function WorkspaceSettingsForm() {
const [name, setName] = useState("ブランド制作");
const [plan, setPlan] = useState("team");
const [region, setRegion] = useState("tokyo");
const [seats, setSeats] = useState(8);
const [threshold, setThreshold] = useState(60);
const [digest, setDigest] = useState(true);
const [agreed, setAgreed] = useState(false);
const [note, setNote] = useState("");
const nameError = name.trim() === "" ? "ワークスペース名を入力してください。" : undefined;
return (
<Form
className="max-w-2xl"
onSubmit={(event) => {
event.preventDefault();
}}
>
<FormField error={nameError} required>
<FormLabel>ワークスペース名</FormLabel>
<FormControl>
<Input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="ブランド制作"
/>
</FormControl>
<FormMessage />
</FormField>
<Select
label="データの保存先"
description="レビューの記録を置く場所です。"
value={region}
onChange={(event) => setRegion(event.target.value)}
>
{REGIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</Select>
<fieldset className="space-y-3">
<legend className="text-sm font-medium">契約プラン</legend>
<RadioGroup value={plan} onValueChange={setPlan}>
{PLANS.map((option) => (
<RadioGroupItem
key={option.value}
value={option.value}
label={option.label}
description={option.description}
/>
))}
</RadioGroup>
</fieldset>
<NumberInput
value={seats}
onValueChange={setSeats}
min={1}
max={200}
aria-label="席数"
/>
<Slider
label="この点数を下回れば自動で承認する"
value={threshold}
onValueChange={setThreshold}
min={0}
max={100}
showValue
/>
<Switch
checked={digest}
onCheckedChange={setDigest}
label="週次のまとめ"
description="毎週月曜に1通だけ送ります。"
/>
<Textarea
label="レビュー担当へのメモ"
description="一覧を開いた人に見えます。"
value={note}
onChange={(event) => setNote(event.target.value)}
maxLength={200}
showCount
/>
<Checkbox
checked={agreed}
onCheckedChange={setAgreed}
label="この設定はすべての案件に適用されます。"
/>
<div className="flex gap-3">
<Button type="submit" disabled={!agreed || Boolean(nameError)}>
設定を保存
</Button>
<Button type="button" variant="outline">
やめる
</Button>
</div>
</Form>
);
}設計の判断
この分類のコンポーネントをいつ使い、いつ使わないかは、UIXHERO の「UIコンポーネント完全ガイド」にある「カテゴリ 01|アクションと入力」の節にまとめています。選択肢が何個あるか、そして選ばせるのか打ち込ませるのかで、選ぶコンポーネントが変わります。
いつ・なぜ使うか(UIXHERO)
「いつ・なぜ使うか」の判断は、姉妹サイト UIXHERO の記事で解説しています。