© TOOLS BOX — Next.js / React / TypeScript コードサンプル集

サンプルガイド
←サンプル一覧
nextjsui-component

React でフォームラベルに必須・任意バッジを表示する

フォームラベルの横に「必須」「任意」バッジを表示し、aria-required と組み合わせてアクセシブルなフォーム入力欄を実装するパターン。

難易度: 初級·更新: 2026-04-18

対応バージョン

nextjs 15react 19

前提環境

React の基本的なコンポーネント実装とフォームの基本を理解していること

概要

フォームラベルの横に「必須」「任意」バッジを表示し、aria-required を入力欄に付けることでスクリーンリーダーにも必須・任意を伝える。RequiredBadge / OptionalBadge を独立コンポーネントにすることで任意の場所に再利用できる。

インストール

# 追加インストールは不要

実装

バッジコンポーネント

// components/RequiredBadge.tsx
export function RequiredBadge() {
  return (
    <span
      className="ml-1 inline-block rounded bg-red-100 px-1.5 py-0.5 text-xs font-medium text-red-600"
      aria-hidden="true"
    >
      必須
    </span>
  );
}
// components/OptionalBadge.tsx
export function OptionalBadge() {
  return (
    <span
      className="ml-1 inline-block rounded bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-500"
      aria-hidden="true"
    >
      任意
    </span>
  );
}

フォームフィールドコンポーネント

// components/FormField.tsx
import { RequiredBadge } from "./RequiredBadge";
import { OptionalBadge } from "./OptionalBadge";

type Props = {
  id: string;
  label: string;
  required?: boolean;
  children: React.ReactNode;
};

export function FormField({ id, label, required = false, children }: Props) {
  return (
    <div className="space-y-1">
      <label htmlFor={id} className="flex items-center text-sm font-medium text-gray-700">
        {label}
        {required ? <RequiredBadge /> : <OptionalBadge />}
      </label>
      {children}
    </div>
  );
}

入力欄への適用

// components/SampleCreateForm.tsx
"use client";

import { FormField } from "./FormField";

export function SampleCreateForm() {
  return (
    <form className="space-y-4">
      <FormField id="title" label="タイトル" required>
        <input
          id="title"
          type="text"
          aria-required="true"
          className="w-full rounded border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-blue-400"
        />
      </FormField>

      <FormField id="summary" label="概要">
        <textarea
          id="summary"
          rows={3}
          aria-required="false"
          className="w-full rounded border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-blue-400"
        />
      </FormField>

      <FormField id="category" label="カテゴリ" required>
        <select
          id="category"
          aria-required="true"
          className="w-full rounded border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-blue-400"
        >
          <option value="">選択してください</option>
          <option value="routing">ルーティング</option>
          <option value="testing">テスト</option>
        </select>
      </FormField>

      <button
        type="submit"
        className="rounded bg-blue-600 px-4 py-2 text-sm text-white"
      >
        保存
      </button>
    </form>
  );
}

バッジを props で切り替えるパターン

// components/FieldLabel.tsx — badge を props で制御する代替設計
type BadgeVariant = "required" | "optional" | "none";

type Props = {
  htmlFor: string;
  children: React.ReactNode;
  badge?: BadgeVariant;
};

const BADGE_CONFIG: Record<
  Exclude<BadgeVariant, "none">,
  { text: string; className: string }
> = {
  required: {
    text: "必須",
    className: "ml-1 rounded bg-red-100 px-1.5 py-0.5 text-xs font-medium text-red-600",
  },
  optional: {
    text: "任意",
    className: "ml-1 rounded bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-500",
  },
};

export function FieldLabel({ htmlFor, children, badge = "none" }: Props) {
  const config = badge !== "none" ? BADGE_CONFIG[badge] : null;

  return (
    <label htmlFor={htmlFor} className="flex items-center text-sm font-medium text-gray-700">
      {children}
      {config && (
        <span className={config.className} aria-hidden="true">
          {config.text}
        </span>
      )}
    </label>
  );
}

ポイント

  • バッジに aria-hidden="true" を付けることで、スクリーンリーダーがバッジテキストを読み上げるのを防ぐ。必須かどうかは入力欄の aria-required から伝えるため、バッジは視覚的な補助にとどめる
  • aria-required="true" を <input> / <textarea> / <select> に付けることで、スクリーンリーダーがフォーカス時に「必須」と読み上げる。HTML の required 属性と組み合わせると、ブラウザのネイティブバリデーションも有効になる
  • FormField コンポーネントに required props を渡すだけでラベルのバッジと aria-required を一元管理できる。ただし children の入力欄には個別に aria-required を設定する必要がある
  • 必須バッジは赤系(bg-red-100 text-red-600)、任意バッジはグレー系(bg-gray-100 text-gray-500)にすることで、色覚異常のユーザーにも視覚的な強弱を伝えられる
  • BADGE_CONFIG パターンを使うと、将来「推奨」などのバッジ種別を追加するときに BADGE_CONFIG にエントリを加えるだけで拡張できる
  • htmlFor と id を一致させることで、ラベルクリックで入力欄にフォーカスが移る。スクリーンリーダーも <label> と入力欄を関連付けて読み上げる

注意点

react-field-help-text はヘルプテキスト・エラーメッセージの表示制御。react-inline-error-message はインラインエラーの表示。これはフォームラベル横の必須・任意バッジ表示と aria-required による支援技術対応に特化。

関連サンプル

同じテーマや技術スタックを使った実装例

  • React でフォームフィールドにヘルプテキストとエラーメッセージを表示する

    入力フィールドの下にヘルプテキスト(説明)とバリデーションエラーメッセージを表示し、スクリーンリーダー対応の aria-describedby で関連付けるアクセシブルなフォームパターン。

  • React で検索入力欄にキーボードショートカットでフォーカスする

    / や Ctrl+K などのキーボードショートカットで検索入力欄へフォーカスを移動するパターン。useEffect と keydown イベントで実装し、入力中の誤作動を防ぐ制御も示す。

  • React で検索入力欄にクリアボタンを追加する

    検索フィールドに入力内容を一発でクリアする × ボタンを追加し、入力中だけ表示する制御と URL クエリへの反映パターンを示す例。

  • React でフォームフィールド近傍にインラインエラーメッセージを表示する

    フォームの各フィールド直下にバリデーションエラーや API エラーをインライン表示するパターン。aria-describedby でアクセシブルに紐付け、エラー状態のスタイルも切り替える例。

  • React で検索キーワードに応じた 0 件 EmptyState を表示する

    検索語の有無・フィルタ条件の有無によって異なる 0 件メッセージを出し分け、クリアボタンや別キーワード提案など検索導線につながる EmptyState を実装する例。

関連仕様

このサンプルを理解するのに役立つ仕様や概念

  • FrameworkNext.jsReact ベースのフルスタックフレームワーク。SSR・SSG・App Router・API Routes を提供する。
←サンプル一覧に戻る