Web制作者のためのHTMLフォームの使い方|input・select・textarea・チェックボックス・ラジオボタンとアクセシビリティ対応


はじめに

前回の記事ではWeb制作の単価表・見積もり書の作り方として相場に合った単価の決め方・項目の書き方・クライアントに納得してもらえる見積もりの伝え方を解説しました。今回はWeb制作者のためのHTMLフォームの使い方を解説します。お問い合わせフォーム・予約フォーム・会員登録フォームなど、Webサイトに欠かせないフォームの実装は副業案件でも必ず登場します。input・select・textarea・チェックボックス・ラジオボタンなど各部品の使い方からアクセシビリティ対応まで丁寧に解説します。


1. フォームの基本構造

1.1 formタグの書き方

<!--
    フォームの基本構造

    <form> タグの重要な属性:

    action  → フォームを送信するURL(送信先)
    method  → 送信方法
              post:フォームデータをHTTPリクエストの
                    本文に含めて送信(推奨)
              get :URLのクエリパラメーターに
                    含めて送信(検索フォームなど)
    novalidate → ブラウザのデフォルトバリデーションを無効化
                 (自分でバリデーションを実装する場合)
    enctype → ファイルアップロードがある場合は
              "multipart/form-data" を指定
-->

<form
    action="/contact.php"
    method="post"
    id="contact-form"
    novalidate
>
    <!-- フォームの中身をここに書く -->
</form>

<!-- ファイルアップロードがある場合 -->
<form
    action="/upload.php"
    method="post"
    enctype="multipart/form-data"
>
    <!-- ファイルアップロードフォーム -->
</form>

1.2 labelとinputのセット

<!--
    label タグは必ずセットで使う

    label があると:
    ① クリックで対応するinputにフォーカスが当たる
    ② スクリーンリーダーが「何の入力欄か」を読み上げる
    ③ アクセシビリティが向上する

    紐付け方法①:for属性とid属性で紐付ける(推奨)
-->
<div class="form-group">
    <label for="name">お名前</label>
    <input
        type="text"
        id="name"
        name="name"
    >
</div>

<!--
    紐付け方法②:labelでinputをラップする
    → for属性・id属性が不要
    → シンプルに書ける
-->
<div class="form-group">
    <label>
        お名前
        <input type="text" name="name">
    </label>
</div>

2. inputタグの種類と使い方

2.1 テキスト系のinput

<!-- type="text":1行のテキスト入力(最も基本的) -->
<div class="form-group">
    <label for="name">お名前 <span class="required">必須</span></label>
    <input
        type="text"
        id="name"
        name="name"
        placeholder="山田 太郎"
        autocomplete="name"
        required
        maxlength="50"
    >
    <!--
        placeholder   → 未入力時のヒントテキスト
        autocomplete  → ブラウザの自動入力を制御する
        required      → 必須項目(空欄で送信できなくなる)
        maxlength     → 最大文字数
    -->
</div>

<!-- type="email":メールアドレス入力 -->
<div class="form-group">
    <label for="email">メールアドレス <span class="required">必須</span></label>
    <input
        type="email"
        id="email"
        name="email"
        placeholder="example@email.com"
        autocomplete="email"
        required
    >
    <!--
        type="email" にすると:
        → ブラウザが@の有無をチェックする
        → スマートフォンでメールキーボードが表示される
    -->
</div>

<!-- type="tel":電話番号入力 -->
<div class="form-group">
    <label for="tel">電話番号</label>
    <input
        type="tel"
        id="tel"
        name="tel"
        placeholder="090-1234-5678"
        autocomplete="tel"
        pattern="[0-9\-]{10,13}"
    >
    <!--
        type="tel" にすると:
        → スマートフォンで数字キーボードが表示される
        → ブラウザによるバリデーションはないので
          pattern属性か自前のバリデーションが必要
    -->
</div>

<!-- type="url":URL入力 -->
<div class="form-group">
    <label for="website">Webサイト</label>
    <input
        type="url"
        id="website"
        name="website"
        placeholder="https://example.com"
    >
</div>

<!-- type="number":数値入力 -->
<div class="form-group">
    <label for="quantity">数量</label>
    <input
        type="number"
        id="quantity"
        name="quantity"
        min="1"
        max="100"
        step="1"
        value="1"
    >
</div>

<!-- type="password":パスワード入力 -->
<div class="form-group">
    <label for="password">パスワード</label>
    <input
        type="password"
        id="password"
        name="password"
        autocomplete="new-password"
        minlength="8"
    >
</div>

2.2 日付・時間系のinput

<!-- type="date":日付入力 -->
<div class="form-group">
    <label for="birthday">生年月日</label>
    <input
        type="date"
        id="birthday"
        name="birthday"
        min="1900-01-01"
        max="2010-12-31"
    >
</div>

<!-- type="time":時間入力 -->
<div class="form-group">
    <label for="appointment">ご希望の時間</label>
    <input
        type="time"
        id="appointment"
        name="appointment"
        min="09:00"
        max="18:00"
        step="1800"
    >
    <!--
        step="1800" → 30分刻みで選択できる
    -->
</div>

<!-- type="datetime-local":日時入力 -->
<div class="form-group">
    <label for="reservation">ご予約日時</label>
    <input
        type="datetime-local"
        id="reservation"
        name="reservation"
    >
</div>

<!-- type="month":年月入力 -->
<div class="form-group">
    <label for="card-expiry">有効期限</label>
    <input
        type="month"
        id="card-expiry"
        name="card-expiry"
        autocomplete="cc-exp"
    >
</div>

2.3 その他のinput

<!-- type="file":ファイルアップロード -->
<div class="form-group">
    <label for="attachment">添付ファイル</label>
    <input
        type="file"
        id="attachment"
        name="attachment"
        accept=".pdf,.jpg,.jpeg,.png"
        multiple
    >
    <!--
        accept   → 選択できるファイルの種類を制限する
        multiple → 複数ファイルを選択できる
    -->
</div>

<!-- type="hidden":非表示フィールド -->
<!--
    ユーザーには見えないが
    フォームと一緒に送信される値を埋め込む
    → CSRFトークン・ページID・フォーム種別など
-->
<input type="hidden" name="csrf_token" value="xxxxxxxxxxxxxxxx">
<input type="hidden" name="form_type" value="contact">

<!-- type="range":スライダー -->
<div class="form-group">
    <label for="satisfaction">満足度:<span id="satisfaction-value">5</span></label>
    <input
        type="range"
        id="satisfaction"
        name="satisfaction"
        min="1"
        max="10"
        step="1"
        value="5"
        oninput="document.getElementById('satisfaction-value').textContent = this.value"
    >
</div>

<!-- type="color":カラーピッカー -->
<div class="form-group">
    <label for="brand-color">ブランドカラー</label>
    <input
        type="color"
        id="brand-color"
        name="brand-color"
        value="#1A5FC0"
    >
</div>

<!-- type="search":検索フォーム -->
<div class="search-form">
    <label for="search" class="visually-hidden">検索</label>
    <input
        type="search"
        id="search"
        name="q"
        placeholder="キーワードを入力"
        aria-label="サイト内検索"
    >
    <button type="submit">検索</button>
</div>

3. チェックボックスとラジオボタン

3.1 チェックボックス

<!--
    チェックボックス:複数選択できる
    → 「すべて選んでください」のような
      複数回答OKの質問に使う
-->

<!-- 単体のチェックボックス -->
<div class="form-group">
    <label class="checkbox-label">
        <input
            type="checkbox"
            name="privacy"
            value="agreed"
            required
        >
        <a href="/privacy/" target="_blank">プライバシーポリシー</a>に同意する
    </label>
</div>

<!-- 複数のチェックボックス -->
<fieldset>
    <legend>ご興味のある分野(複数選択可)</legend>

    <div class="checkbox-group">
        <label class="checkbox-label">
            <input
                type="checkbox"
                name="interests[]"
                value="design"
            >
            デザイン
        </label>

        <label class="checkbox-label">
            <input
                type="checkbox"
                name="interests[]"
                value="coding"
                checked
            >
            コーディング
        </label>

        <label class="checkbox-label">
            <input
                type="checkbox"
                name="interests[]"
                value="marketing"
            >
            マーケティング
        </label>
    </div>
</fieldset>

<!--
    name に [] をつける → 配列として送信される
    checked 属性 → 最初からチェックされた状態にする
-->

3.2 ラジオボタン

<!--
    ラジオボタン:1つだけ選択できる
    → 「1つ選んでください」のような
      単数回答の質問に使う
    → 同じ name 属性を持つものがグループになる
-->

<fieldset>
    <legend>お問い合わせ種別 <span class="required">必須</span></legend>

    <div class="radio-group">
        <label class="radio-label">
            <input
                type="radio"
                name="inquiry-type"
                value="service"
                required
            >
            サービスについて
        </label>

        <label class="radio-label">
            <input
                type="radio"
                name="inquiry-type"
                value="price"
            >
            料金について
        </label>

        <label class="radio-label">
            <input
                type="radio"
                name="inquiry-type"
                value="other"
            >
            その他
        </label>
    </div>
</fieldset>

<!--
    fieldset:関連するフォーム要素をグループ化する
    legend:グループのタイトル(スクリーンリーダーが読み上げる)

    → チェックボックスとラジオボタンのグループには
      必ず fieldset と legend を使う
    → これがアクセシビリティの基本
-->

4. selectタグ(プルダウン)

<!--
    select:選択肢の多い1択・複数択に使う

    使い分け:
    選択肢が3つ以下 → ラジオボタン
    選択肢が4つ以上 → select(プルダウン)
-->

<!-- 基本的なプルダウン -->
<div class="form-group">
    <label for="prefecture">都道府県 <span class="required">必須</span></label>
    <select id="prefecture" name="prefecture" required>
        <option value="">選択してください</option>
        <optgroup label="関東">
            <option value="tokyo">東京都</option>
            <option value="kanagawa">神奈川県</option>
            <option value="saitama">埼玉県</option>
            <option value="chiba">千葉県</option>
        </optgroup>
        <optgroup label="関西">
            <option value="osaka">大阪府</option>
            <option value="kyoto">京都府</option>
            <option value="hyogo">兵庫県</option>
        </optgroup>
    </select>
    <!--
        optgroup → 選択肢をグループ分けできる
        value="" の option → 「選択してください」の初期値
        required のある select は value="" のoptionが
        選ばれていると送信できない
    -->
</div>

<!-- 複数選択できるselect -->
<div class="form-group">
    <label for="skills">スキル(複数選択可)</label>
    <select
        id="skills"
        name="skills[]"
        multiple
        size="5"
    >
        <option value="html">HTML</option>
        <option value="css">CSS</option>
        <option value="javascript">JavaScript</option>
        <option value="php">PHP</option>
        <option value="wordpress">WordPress</option>
    </select>
    <!--
        multiple → 複数選択できる
        size     → 表示する行数
        Ctrl(Mac:Command)を押しながらクリックで複数選択
    -->
</div>

<!-- 初期値を選択済みにする -->
<select name="budget">
    <option value="">選択してください</option>
    <option value="under10">10万円未満</option>
    <option value="10to30" selected>10〜30万円</option>
    <!-- selected属性で初期選択 -->
    <option value="over30">30万円以上</option>
</select>

5. textareaタグ(複数行テキスト)

<!--
    textarea:複数行のテキスト入力
    → お問い合わせ内容・自己紹介・備考など
    → rows属性で表示する行数を指定する
    → CSSの resize プロパティで
      リサイズを制御できる
-->

<div class="form-group">
    <label for="message">
        お問い合わせ内容 <span class="required">必須</span>
    </label>
    <textarea
        id="message"
        name="message"
        rows="8"
        placeholder="お問い合わせ内容をご記入ください"
        required
        maxlength="2000"
    ></textarea>
    <!--
        rows       → 表示する行数(高さの目安)
        maxlength  → 最大文字数
        textarea の閉じタグの間には何も書かない
        (スペース・改行が初期値になってしまう)
    -->
</div>

<!-- CSSでリサイズを制御する -->
<style>
textarea {
    /* 縦方向のみリサイズ可能 */
    resize: vertical;

    /* リサイズ不可 */
    /* resize: none; */

    /* 縦横ともにリサイズ可能(デフォルト) */
    /* resize: both; */
}
</style>

6. ボタンの種類と使い方

<!--
    フォーム内のボタンには button タグを使う
    type属性が重要:

    type="submit"  → フォームを送信する(デフォルト)
    type="reset"   → フォームをリセットする
    type="button"  → 何もしない(JavaScriptで制御する)

    ⚠️ type属性を省略すると type="submit" として
    動作するので必ず明示する
-->

<!-- 送信ボタン -->
<button type="submit" class="btn-submit">
    送信する
</button>

<!-- リセットボタン(慎重に使う) -->
<!--
    ユーザーが間違えてクリックすると
    入力内容がすべて消えてしまう
    → UX上の問題があるため多用しない
-->
<button type="reset" class="btn-reset">
    リセット
</button>

<!-- JavaScriptで制御するボタン -->
<button type="button" id="add-field">
    項目を追加する
</button>

<!-- 送信中の二重送信を防ぐ -->
<button type="submit" id="submit-btn">
    送信する
</button>

<script>
document.getElementById( 'contact-form' ).addEventListener( 'submit', function () {
    const btn = document.getElementById( 'submit-btn' );
    btn.disabled = true;           // ボタンを無効化する
    btn.textContent = '送信中...'; // テキストを変える
} );
</script>

7. アクセシビリティ対応

7.1 必須項目の伝え方

<!--
    ❌ NG:色だけで必須を伝える
    → 色覚障害の方には伝わらない
-->
<label for="name" class="required-red">お名前</label>

<!--
    ✅ OK:テキストと aria 属性で伝える
-->
<div class="form-group">
    <label for="name">
        お名前
        <span class="required" aria-hidden="true">※必須</span>
    </label>
    <input
        type="text"
        id="name"
        name="name"
        required
        aria-required="true"
    >
    <!--
        aria-hidden="true" → スクリーンリーダーがスキップ
        aria-required="true" → 必須であることをスクリーンリーダーに伝える
        required → ブラウザのバリデーションを有効にする
    -->
</div>

7.2 エラーメッセージのアクセシビリティ

<!-- エラーメッセージを適切に関連付ける -->
<div class="form-group">
    <label for="email">
        メールアドレス <span class="required">必須</span>
    </label>
    <input
        type="email"
        id="email"
        name="email"
        required
        aria-required="true"
        aria-describedby="email-error"
        aria-invalid="true"
    >
    <!--
        aria-describedby → エラーメッセージのidを指定する
        aria-invalid     → 入力値が不正であることを伝える
    -->
    <span
        id="email-error"
        class="error-message"
        role="alert"
    >
        正しいメールアドレスを入力してください
    </span>
    <!--
        role="alert" → スクリーンリーダーがすぐに読み上げる
    -->
</div>

7.3 フォーカスのスタイル

css

/*
    アクセシビリティのためにフォーカスを
    見えるようにする

    ❌ NG:outline を消す
*/
input:focus {
    outline: none; /* これはやってはいけない */
}

/*
    ✅ OK:カスタムのフォーカススタイルを作る
    → キーボード操作・スクリーンリーダーで
      どこにフォーカスがあるかわかる
*/
input:focus,
select:focus,
textarea:focus,
button:focus {
    outline: 2px solid #1A5FC0;
    outline-offset: 2px;
}

/*
    マウス操作のときだけフォーカスを非表示にする
    → :focus-visible はキーボード操作のときだけ
      スタイルを適用できる
*/
input:focus:not( :focus-visible ) {
    outline: none;
}

input:focus-visible {
    outline: 2px solid #1A5FC0;
    outline-offset: 2px;
}

7.4 スクリーンリーダー向けの非表示テキスト

css

/*
    視覚的には非表示だが
    スクリーンリーダーには読み上げられるクラス
    → display:none や visibility:hidden は
      スクリーンリーダーにも無視されるので使わない
*/
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect( 0, 0, 0, 0 );
    white-space: nowrap;
    border: 0;
}
html

<!-- 使い方の例:アイコンボタン -->
<button type="button">
    <!-- SVGのアイコン -->
    <svg aria-hidden="true" focusable="false">...</svg>
    <!-- スクリーンリーダー向けのテキスト -->
    <span class="visually-hidden">メニューを開く</span>
</button>

<!-- 検索フォームのlabel -->
<label for="search" class="visually-hidden">サイト内検索</label>
<input type="search" id="search" name="q">

8. 実践:アクセシビリティ対応のお問い合わせフォーム

<form
    action="/contact.php"
    method="post"
    id="contact-form"
    novalidate
    aria-label="お問い合わせフォーム"
>
    <!-- CSRF対策 -->
    <input type="hidden" name="csrf_token" value="xxxxxxxx">

    <!-- お名前 -->
    <div class="form-group">
        <label for="name">
            お名前
            <span class="required" aria-hidden="true">必須</span>
        </label>
        <input
            type="text"
            id="name"
            name="name"
            autocomplete="name"
            required
            aria-required="true"
            maxlength="50"
        >
    </div>

    <!-- メールアドレス -->
    <div class="form-group">
        <label for="email">
            メールアドレス
            <span class="required" aria-hidden="true">必須</span>
        </label>
        <input
            type="email"
            id="email"
            name="email"
            autocomplete="email"
            required
            aria-required="true"
        >
    </div>

    <!-- お問い合わせ種別 -->
    <fieldset>
        <legend>
            お問い合わせ種別
            <span class="required" aria-hidden="true">必須</span>
        </legend>

        <div class="radio-group">
            <label class="radio-label">
                <input
                    type="radio"
                    name="type"
                    value="service"
                    required
                    aria-required="true"
                >
                サービスについて
            </label>
            <label class="radio-label">
                <input type="radio" name="type" value="price">
                料金について
            </label>
            <label class="radio-label">
                <input type="radio" name="type" value="other">
                その他
            </label>
        </div>
    </fieldset>

    <!-- お問い合わせ内容 -->
    <div class="form-group">
        <label for="message">
            お問い合わせ内容
            <span class="required" aria-hidden="true">必須</span>
        </label>
        <textarea
            id="message"
            name="message"
            rows="8"
            required
            aria-required="true"
            maxlength="2000"
        ></textarea>
    </div>

    <!-- プライバシーポリシー -->
    <div class="form-group">
        <label class="checkbox-label">
            <input
                type="checkbox"
                name="privacy"
                value="1"
                required
                aria-required="true"
            >
            <a
                href="/privacy/"
                target="_blank"
                rel="noopener noreferrer"
            >プライバシーポリシー</a>に同意する
        </label>
    </div>

    <button type="submit" class="btn-submit">
        送信する
    </button>
</form>

9. 実践チェックリスト

✓ すべてのinputにlabelが紐付いているか(for属性とid属性が一致しているか)

✓ チェックボックス・ラジオボタンのグループにfieldsetとlegendを使っているか

✓ 必須項目にrequiredとaria-required=”true”を両方設定しているか

✓ type属性を目的に合ったものを使っているか(emailにはtype=”email”など)

✓ フォーカス時のアウトラインをoutline:noneで消していないか

✓ エラーメッセージにaria-describedbyとrole=”alert”を設定しているか

✓ 送信ボタンにtype=”submit”を明記しているか

✓ 二重送信防止のためのdisabled処理を実装しているか


まとめ

今回はWeb制作者のためのHTMLフォームの使い方としてinput・select・textarea・チェックボックス・ラジオボタンの使い方からアクセシビリティ対応まで解説しました。ポイントをまとめると:

  • すべてのinputにはlabelを紐付ける・for属性とid属性を一致させることでクリック操作もスクリーンリーダーも両方に対応できる
  • inputのtype属性は目的に合ったものを選ぶ・type=”email”はスマートフォンのキーボード切り替えにも効果があり実用的
  • チェックボックス(複数選択)とラジオボタン(単数選択)を使い分ける・グループにはfieldsetとlegendを使ってアクセシビリティを確保する
  • 選択肢が3つ以下はラジオボタン・4つ以上はselect(プルダウン)を使うのが一般的なルール
  • textareaは閉じタグの間に何も書かないこと・スペースや改行が初期値になってしまう
  • ボタンはtype属性を必ず明記する・省略するとtype=”submit”として動作するため意図しない送信が起こる
  • フォーカスのoutlineをnoneにしてはいけない・キーボード操作のユーザーがどこにフォーカスがあるかわからなくなる
  • エラーメッセージはaria-describedbyとrole=”alert”で適切に関連付けることでスクリーンリーダーにも伝わる

次の記事では、HTMLのセマンティックタグ完全ガイドを解説します。header・main・article・sectionの使い分け・nav・aside・footerの役割・スクリーンリーダーへの影響とアクセシビリティ対応まで丁寧にお届けします。お楽しみに!

コメント

タイトルとURLをコピーしました