UI 擬似クラス - ウェブ開発を学ぶ

UI 擬似クラス
========

* 前のページ
* Overview: Forms
* 次のページ

これまでの記事では、様々なフォームコントロールのスタイル設定について、一般的な方法で説明しました。これには、例えばチェックボックスが選択されたときだけターゲットにするために `:checked` を使用するような、擬似クラスの使用方法も記載しました。この記事では、さまざまな状態のフォームをスタイル設定するために、現代のブラウザーで利用できるさまざまな UI 擬似クラスについて詳しく説明します。

|  |  |
| --- | --- |
| 前提条件: |
 基本的なコンピューターリテラシー、基本的な
 HTML および
 CSS を理解していること、一般的な
 擬似クラスと擬似要素の知識を含む。
  |
| 目的: | フォームのどの部分がスタイル設定しにくいのか、その理由を理解すること。カスタマイズするために何ができるかを学ぶこと。 |

利用できる擬似クラスには何があるのか
------------------

フォームに関連する、（CSS 2.1 時点で）元から利用できる擬似クラスは次の通りです。

* `:hover`: マウスポインターを当てたときだけ要素を選択します。
* `:focus`: フォーカスされているとき（キーボードからタブで移動しているとき）にのみ、要素を選択します。
* `:active`: その要素がアクティブ化されているとき（つまり、クリックされたとき、またはキーボード操作の場合は `Return`/`Enter` キーが押されたとき）だけ、要素を選択します。

これらの基本的な擬似クラスは、もうお馴染みになっているはずです。さらに最近、 CSS Selector Level 3 と CSS Basic UI Level 3 では、HTML フォームに関連する擬似クラスが追加され、他にもいくつかの便利なターゲット条件を提供しているので、これを利用してください。これらについては、以下の節で詳しく説明しますが、簡単に説明すると、主なものは以下のとおりです。

* `:required` と `:optional`: 必須または省略可能なフォームコントロールを対象とします。
* `:valid` と `:invalid`、 `:in-range` と `:out-of-range`: 設定されたフォーム検証の制約に従って有効/無効、または範囲内/範囲外のフォームコントロールを対象とします。
* `:enabled` と `:disabled`、 `:read-only` と `:read-write`: 有効または無効なフォームコントロール（例: HTML の `disabled` 属性が設定されている）、および読み書き可能または読み取り専用のフォームコントロール（例: HTML の `readonly` 属性が設定されている）を対象としています。
* `:checked`, `:indeterminate`, `:default`: それぞれ、チェックされているチェックボックスとラジオボタン、不確定な状態（チェックされていないかチェックされていないかのどちらか）、ページを読み込んだときの既定の選択オプション（例えば、 `checked` 属性が設定されている `<input type="checkbox">` 、または `selected` 属性が設定されている `<option>` 要素などが対象です。）

他にもたくさんありますが、上に挙げたものが明らかに最も有用です。他のいくつかは、非常に特殊なニッチな問題を解決することを目的としていたり、まだブラウザーがあまり対応していないものもあります。上に挙げたものはすべて、ブラウザーの対応がかなり進んでいますが、もちろん、フォームの実装を慎重にテストして、対象となるユーザーに動作することを確認する必要があります。

**メモ:** ここで説明する多くの擬似クラスは、検証状態（データが有効か無効か）に基づくフォームコントロールのスタイル設定に関係しています。検証制約の設定と操作については、次の記事「クライアントサイドのフォーム検証」で詳しく説明しますが、今はフォーム検証に関して単純にしておくので、混乱することはありません。

入力が必須か否かによるスタイル設定
-----------------

クライアントサイドのフォーム検証に関する最も基本的な概念の一つは、フォームの入力が必須（フォームを送信する前に入力する必要がある）か任意（オプション）かです。

`<input>`、`<select>`、`<textarea>` の各要素には `required` 属性を利用することができ、設定すると、フォームが正常に送信される前にそのコントロールに入力しなければならないことを意味しています。例えば、

```html
<form>
  <fieldset>
    <legend>Feedback form</legend>
    <div>
      <label for="fname">First name: </label>
      <input id="fname" name="fname" type="text" required />
    </div>
    <div>
      <label for="lname">Last name: </label>
      <input id="lname" name="lname" type="text" required />
    </div>
    <div>
      <label for="email"
 >Email address (include if you want a response):
      </label>
      <input id="email" name="email" type="email" />
    </div>
    <div><button>Submit</button></div>
  </fieldset>
</form>
```

ここでは、最初の名前と姓は必須ですが、電子メールアドレスは任意です。

この 2 つの状態を一致させるには、 `:required` と `:optional` 擬似クラスが使用できます。例えば、上記の HTML に以下のような CSS を適用するとします。

```css
input:required {
  border: 1px solid black;
}

input:optional {
  border: 1px solid silver;
}
```

必要な操作には黒い枠線が、オプションの操作には銀色の枠線が、このように表示されます。

また、フォームに入力せずに送信してみると、ブラウザーが既定で出すクライアントサイドの検証エラーメッセージを見ることができます。

上記のフォームは悪くはないのですが、とても良いわけではありません。第一に、必須とオプションの状態を色だけで表示していることです。これは色覚障碍者にとってはあまり良いことではありません。第二に、ウェブでの標準的な必須状態の表記は、アスタリスク (\*)、または「必須」という言葉を該当する操作に関連付けることだからです。

次の節では、 `:required` を使用して必須項目を示すより良い例を見ていきます。これは、生成コンテンツを使用することについても掘り下げます。

**メモ:** おそらく `:optional` 擬似クラスを使用することはあまりないでしょう。フォームコントロールは既定でオプションなので、オプションのスタイル設定を既定値で行い、必要なコントロールのスタイルを上から追加すればよいのです。

**メモ:** 同じ名前のラジオボタン群の中の 1 つのラジオボタンに `required` 属性がある場合、すべてのラジオボタンは 1 つが選択されるまで無効ですが、属性が割り当てられたものだけが実際に `:required` に一致することになります。

擬似クラスでコンテンツを生成する
----------------

これまでの記事で、生成コンテンツの使い方を見てきましたが、今こそもう少し詳しく話をする良い機会だと思い、今回はその内容を紹介します。

これは、 `::before` と `::after` という擬似要素と `content` というプロパティを使用して、影響を受ける要素の前または後にコンテンツの塊を表示させることができるというものです。コンテンツの塊は DOM に追加されないので、スクリーンリーダーからは見えません。それは文書のスタイルの一部なのです。それは擬似要素なので、実際の DOM ノードができるのと同じ方法でスタイルの対象にすることができます。

これは、ラベルやアイコンのような視覚的なインジケータを要素に追加したいが、支援技術に拾われたくない場合に、実に有用な方法です。たとえば、カスタムラジオボタンの例では、ラジオボタンが選択されたときに内側の円の配置とアニメーションを処理するために生成されたコンテンツを使用しています。

```css
input[type="radio"]::before {
  display: block;
  content: " ";
  width: 10px;
  height: 10px;
  border-radius: 6px;
  background-color: red;
  font-size: 1.2em;
  transform: translate(3px, 3px) scale(0);
  transform-origin: center;
  transition: all 0.3s ease-in;
}

input[type="radio"]:checked::before {
  transform: translate(3px, 3px) scale(1);
  transition: all 0.3s cubic-bezier(0.25, 0.25, 0.56, 2);
}
```

これは実に有用です。スクリーンリーダーでは、遭遇したラジオボタンやチェックボックスがチェックまたは選択されているかをすでにユーザーに知らせているので、選択を示す別の DOM 要素を読み上げさせたくはないでしょう。紛らわしくなる可能性があるからで宇s。純粋に視覚的なインジケーターを置くことで、この問題は解決されます。

**メモ:** これは、必要に応じて擬似クラスと擬似要素を組み合わせる方法も示しています。

先ほどの必須/オプションの例に戻りますが、今回は入力自体の外観は変更しません。生成されたコンテンツを使用して、指示するラベルを追加します（こちらでライブで確認するか、こちらでソースコードを確認するかしてください。

まず最初に、フォームの一番上に、何を求めているのか、という段落を追加します。

```html
<p>Required fields are labelled with "required".</p>
```

画面の内側から読み上げるリーダーには、 "required" が追加情報として読み上げられ、目の見えるユーザーには、このラベルが表示されます。

フォーム入力は生成されたコンテンツを直接保持しないので（生成されたコンテンツは要素のフォーマットボックスに相対的に配置されますが、フォーム入力は置換された要素のように動作するので、フォーマットボックスを持たないからです）、空の `<span>` を追加して生成されたコンテンツをそこに置くことにしましょう。

```html
<div>
  <label for="fname">First name: </label>
  <input id="fname" name="fname" type="text" required />
  <span></span>
</div>
```

この場合、入力とラベルの両方が `width: 100%` に設定されているため、span が入力の下の新しい行にドロップされてしまうという問題がありました。これを修正するために、親の `<div>` をフレックスコンテナーにするスタイルを設定し、さらにコンテンツが長くなりすぎた場合は新しい行に折り返すように指示します。

```css
fieldset > div {
  margin-bottom: 20px;
  display: flex;
  flex-flow: row wrap;
}
```

この効果として、ラベルと入力はどちらも `width: 100%` なので別個の行に表示されますが、 `<span>` は width が 0 なので、入力欄と同じ行に表示されます。

次に、生成されたコンテンツについて説明します。この CSS を使用してコンテンツを作成します。

```css
input + span {
  position: relative;
}

input:required + span::after {
  font-size: 0.7rem;
  position: absolute;
  content: "required";
  color: white;
  background-color: black;
  padding: 5px 10px;
  top: -26px;
  left: -70px;
}
```

ここでは `<span>` を `position: relative` に設定しているので、生成されるコンテンツを `position: absolute` に設定すると、位置は <body> ではなく `<span>` からの相対位置になります（位置決めのために生成コンテンツは生成要素の子ノードであるかのように動作します）。

そして、生成されたコンテンツに、このラベルに書かせたい内容である "required" を与え、好きなようにスタイルと位置を設定します。結果は以下のようになります。

データが妥当が否かでコントロールをスタイル設定する
-------------------------

フォーム検証におけるもう一つの実に重要で基本的な概念は、フォームコントロールのデータが有効か無効か（数値データの場合、範囲内と範囲外のデータという言い方もできる）です。制約制限を持つフォームコントロールは、これらの状態に基づいて対象とすることができます。

### :valid と :invalid

フォームコントロールは `:valid` と `:invalid` 擬似クラスを使用して対象とすることができます。この点については、いくつか留意すべき点があります。

* 制約検証を行わないコントロールは常に有効であり、したがって `:valid` と一致します。
* `required` が設定されているコントロールで、値がないものは無効とみなされ、 `:invalid` および `:required` と一致します。
* 組み込みの検証機能を持つコントロール、たとえば `<input type="email">` や `<input type="url">` は、入力されたデータが検索するパターンと一致しない場合、 `:invalid` に一致します（ただし、空の場合は有効になります）。
* 現在の値が `min` と `max` 属性で指定した範囲の外にあるコントロールは、後述するように `:invalid` と一致しますが `:out-of-range` によっても一致させることができます。
* 他にも `:valid`/`:invalid` に一致する要素を作る方法はいくつかありますが、それはクライアントサイドのフォーム検証 の記事で見てください。しかし、今は事をシンプルにすることにしましょう。

それでは、 `:valid`/`:invalid` の簡単な例を見てみましょう（ライブ版は valid-invalid.html を、またソースコードも参照してください）。

前の例と同様に、コンテンツを生成するための余分な `<span>` を保有しており、これを使用して、有効/無効なデータのインジケーターを提供していきます。

```html
<div>
  <label for="fname">First name *: </label>
  <input id="fname" name="fname" type="text" required />
  <span></span>
</div>
```

これらの指標を提供するために、以下の CSS を使用しています。

```css
input + span {
  position: relative;
}

input + span::before {
  position: absolute;
  right: -20px;
  top: 5px;
}

input:invalid {
  border: 2px solid red;
}

input:invalid + span::before {
  content: "✖";
  color: red;
}

input:valid + span::before {
  content: "✓";
  color: green;
}
```

先ほどと同様に、`<span>` を `position: relative` に設定し、生成されるコンテンツを相対的に配置できるようにします。そして、フォームのデータが有効か無効かによって、それぞれ緑色のチェックと赤色のクロスで、異なる生成コンテンツを絶対に配置するようにします。無効なデータに少し緊急性を追加するために、無効なときに入力に太い赤のボーダーを指定しました。

**メモ:** ここで `::before` を使用してラベルを追加したのは、 `::after` を既に "required" ラベルのために使用していたからです。

次のもので試してみてください。

必須のテキスト入力は、空の状態では無効ですが、何か記入されている状態では有効であることに注目してください。一方、 email の入力欄は、必須でないため空の状態では有効ですが、適切なメールアドレスでないものが含まれていると無効となります。

### 範囲内と範囲外のデータ

上で触れたように、他にも2つの関連する擬似クラスがあります。 `:in-range` と `:out-of-range` です。これらは、 `min` と `max` によって範囲指定された数値入力に対して、そのデータがそれぞれ指定した範囲内または範囲外にあるときに一致させます。

**メモ:** 数値の入力型とは、 `date`, `month`, `week`, `time`, `datetime-local`, `number`, `range` のことです。

注目すべきは、データが範囲内にある入力欄は `:valid` 擬似クラスに一致し、データが範囲外である入力欄は `:invalid` 擬似クラスに一致することです。では、なぜこの 2 つが存在するのでしょうか？実に意味論上の問題です。範囲外であるということは無効であることを伝えるためのより具体的なものなので、入力が範囲外であるというと、単に「無効」と言うよりユーザーに役立つ可能性があります。両方を提供することもできます。

まさにこれを行う例を見てみましょう。この out-of-range.html デモ（ソースコード も参照）は、前の例の上に構築されており、数値入力に対して範囲外である場合のメッセージと、それが必要であるかどうかを示しています。

数値入力欄はこのようになります。

```html
<div>
  <label for="age">年齢（12 歳以上）:</label>
  <input id="age" name="age" type="number" min="12" max="120" required />
  <span></span>
</div>
```

そして、 CSS は次のようになります。

```css
input + span {
  position: relative;
}

input + span::after {
  font-size: 0.7rem;
  position: absolute;
  padding: 5px 10px;
  top: -26px;
}

input:required + span::after {
  color: white;
  background-color: black;
  content: "Required";
  left: -70px;
}

input:out-of-range + span::after {
  color: white;
  background-color: red;
  width: 155px;
  content: "Outside allowable value range";
  left: -182px;
}
```

これは以前の `:required` の例と同じような話ですが、ここでは `::after` の内容に適用される宣言を別のルールに分割し、 `:required` と `:out-of-range` 状態のための `::after` の内容に、それぞれ別のコンテンツとスタイルを保有するようにした点が異なっています。こちらで試すことができます。

数値の入力が必須であると同時に範囲外である可能性もありますが、その場合はどうなるのでしょうか？ `:out-of-range` のルールは `:required` のルールよりもソースコード上の後で現れるため、カスケードルールが作用して、範囲外のメッセージが表示されます。

これはとてもうまく動作します。最初にページを読み込むと、赤いバツ印と枠線とともに "Required" が表示されます。有効な年齢（12 ～ 120 歳の範囲）を入力すると、入力欄は有効になります。しかし、年齢を範囲外の値に変更すると、 "Required" の代わりに "Outside allowable value range" というメッセージが表示されます。

**メモ:** 無効な値や範囲外の値を入力するには、実際にフォームにフォーカスを当てて、キーボードを使用して入力する必要があります。スピナーボタンでは、許容範囲外の値まで増加/減少させることはできません。

有効/無効や読み取り専用/読み書き可能の入力をスタイル設定する
-------------------------------

有効な要素とは、選択、クリック、入力などが可能な、アクティブにできる要素のことです。一方、無効な要素はどのような方法でも操作することができず、そのデータがサーバーに送信されることもありません。

この 2 つの状態は `:enabled` と `:disabled` を使用して対象化することができます。無効化された入力はなぜ有用なのでしょうか？あるデータが特定のユーザーに適用されない場合、そのユーザーがフォームを送信するときにそのデータを送信したくないことがあります。請求先と配送先に同じ住所を使用するかどうか尋ねられることがよくありますが、その場合は単一の住所をサーバーに送信すればよく、請求先フィールドを使用しないようにすることもできます。

このような例を見てみましょう。まず最初の HTML は、テキスト入力と、請求先住所の無効化をオン・オフするためのチェックボックスを含むシンプルなフォームです。請求先住所フィールドは既定値で無効になっています。

```html
<form>
  <fieldset id="shipping">
    <legend>Shipping address</legend>
    <div>
      <label for="name1">Name: </label>
      <input id="name1" name="name1" type="text" required />
    </div>
    <div>
      <label for="address1">Address: </label>
      <input id="address1" name="address1" type="text" required />
    </div>
    <div>
      <label for="pcode1">Zip/postal code: </label>
      <input id="pcode1" name="pcode1" type="text" required />
    </div>
  </fieldset>
  <fieldset id="billing">
    <legend>Billing address</legend>
    <div>
      <label for="billing-checkbox">Same as shipping address:</label>
      <input type="checkbox" id="billing-checkbox" checked />
    </div>
    <div>
      <label for="name" class="billing-label disabled-label">Name: </label>
      <input id="name" name="name" type="text" disabled required />
    </div>
    <div>
      <label for="address2" class="billing-label disabled-label"
 >Address:
      </label>
      <input id="address2" name="address2" type="text" disabled required />
    </div>
    <div>
      <label for="pcode2" class="billing-label disabled-label"
 >Zip/postal code:
      </label>
      <input id="pcode2" name="pcode2" type="text" disabled required />
    </div>
  </fieldset>

  <div><button>Submit</button></div>
</form>
```

さて、CSS の話です。この例で最も関連性の高い部分は以下の通りです。

```css
input[type="text"]:disabled {
  background: #eee;
  border: 1px solid #ccc;
}

.disabled-label {
  color: #aaa;
}
```

無効にしたい入力欄を、 `input[type="text"]:disabled` を使用して直接選択しましたが、対応するテキストラベルも灰色で表示したいと思います。これらは選択するのがそれほど簡単ではないので、スタイル設定を提供するためにクラスを使用しました。

最後に、請求先住所フィールドの無効化を切り替えるために、いくらかの JavaScript を使用しました。

```js
// Wait for the page to finish loading
document.addEventListener(
  "DOMContentLoaded",
  () => {
    // Attach `change` event listener to checkbox
    document
      .getElementById("billing-checkbox")
      .addEventListener("change", toggleBilling);
  },
  false,
);

function toggleBilling() {
  // Select the billing text fields
  const billingItems = document.querySelectorAll('#billing input[type="text"]');
  // Select the billing text labels
  const billingLabels = document.querySelectorAll(".billing-label");

  // Toggle the billing text fields and labels
  for (let i = 0; i < billingItems.length; i++) {
    billingItems[i].disabled = !billingItems[i].disabled;

    if (
      billingLabels[i].getAttribute("class") === "billing-label disabled-label"
    ) {
      billingLabels[i].setAttribute("class", "billing-label");
    } else {
      billingLabels[i].setAttribute("class", "billing-label disabled-label");
    }
  }
}
```

これは `change`イベントを使用して、ユーザーが請求フィールドを有効/無効にしたり、関連するラベルのスタイルを切り替えたりできるようにします。

この例は以下で見ることができます（ここでライブで見ることや、ソースコードも見ることができます。

### read-only と read-write

disabled`と`:enabled`と同様に、`:read-only`および`:read-write` 擬似クラスは、フォーム入力が切り替えられる 2 つの状態を対象としています。 read-only は値をサーバーに送信しますが、ユーザーが編集することはできません。一方、 read-write は編集可能な状態、つまり既定の状態です。

入力は `readonly` 属性を使用して読み取り専用に設定されます。例として、開発者が前のページで入力された内容をこのページに送り、ユーザーに一箇所で確認してもらい、必要なデータを追加し、送信して注文を確定させることを目的とした確認ページを想像してください。この点で、すべての最終的なフォームデータを一度にサーバーに送信することができます。

フォームがどのようなものか見てみましょう（ライブ例は readonly-confirmation.html を参照してください。またソースコードも参照してください）。

HTML の一部を以下に示します。 readonly 属性に注意してください。

```html
<div>
  <label for="name">Name: </label>
  <input id="name" name="name" type="text" value="Mr Soft" readonly />
</div>
```

ライブ例を試してみると、一番上のフォーム要素のセットはフォーカスされませんが、フォームが送信されたときに値が送信されることがわかります。このように、`:read-only` と `:read-write` 擬似クラスを使用して、フォームコントロールのスタイル設定を行いました。

```css
:is(
 input:read-only,
 input:-moz-read-only,
 textarea:-moz-read-only,
 textarea:read-only
 ) {
  border: 0;
  box-shadow: none;
  background-color: white;
}

:is(textarea:-moz-read-write, textarea:read-write) {
  box-shadow: inset 1px 1px 3px #ccc;
  border-radius: 5px;
}
```

Firefox はバージョン 78 まではこれらの擬似クラスに接頭辞を付けて対応していましたが、バージョン 78 以降は接頭辞を付けないバージョンに対応するようになりました。完全な例は次のようになります。

**メモ:** また、 `:enabled` と `:read-write` は、入力要素の既定の状態を記述する擬似クラスで、おそらくほとんど使用することはないでしょう。

ラジオボタンとチェックボックスの状態 — チェック済み、既定、不定
---------------------------------

このモジュールの以前の記事で見たように、radio buttons と checkboxes にはチェックと解除の状態があります。しかし、他にも考慮すべき状態がいくつかあります。

* `:default`: ページ読み込み時に既定で（つまり、それらに `checked` 属性を設定することで）チェックされているラジオボタン/チェックボックスに一致します。これらは、ユーザーがチェックを外したとしても、 `:default` 擬似クラスに一致します。
* `:indeterminate`: ラジオボタン/チェックボックスがチェックも解除もされていない状態で、これらは *不定* とみなされ、 `:indeterminate` 擬似クラスに一致します。これが何を意味しているかは、以下で詳しく説明します。
### :checked

チェックされたとき、それらは `:checked` 擬似クラスによって一致させられます。

最も一般的な使用方法は、チェックボックスやラジオボタンがチェックされたときに、 `appearance: none;` を使用してシステムの既定値のスタイルを削除し、自分でスタイルを構築し直したい場合に、別のスタイルを追加することである。前回の記事で、ラジオ/チェックボックスで `appearance: none` を使用するについて述べたときに、この例を見ることが出来ました。

おさらいですが、 Styled radio buttons の例の `:checked` コードは以下のようになっています。

```css
input[type="radio"]::before {
  display: block;
  content: " ";
  width: 10px;
  height: 10px;
  border-radius: 6px;
  background-color: red;
  font-size: 1.2em;
  transform: translate(3px, 3px) scale(0);
  transform-origin: center;
  transition: all 0.3s ease-in;
}

input[type="radio"]:checked::before {
  transform: translate(3px, 3px) scale(1);
  transition: all 0.3s cubic-bezier(0.25, 0.25, 0.56, 2);
}
```

こちらで試すことができます。

基本的には、ラジオボタンの「内側の円」のスタイルを `::before` 擬似要素を使用して構築しますが、それに `scale(0)` `transform` を設定します。次に、 `transition` を使用して、ラジオが選択/チェックされたときにうまくアニメーションして表示されるようにしています。 `width`/`height` の遷移ではなく、変換を使用する利点は、円の角から伸びるように見えるのではなく、円の中心から伸びるようにするために `transform-origin` を使用することができるところです。

### :default と :indeterminate

前述のように、 `:default` 擬似クラスは、ページ読み込み時に既定でチェックされるラジオ／チェックボックスに、チェックされていないときでも一致します。これは、ユーザーが選択をリセットしたい場合に備えて、既定値（または開始時の選択肢）が何であったかを思い出させるために、選択肢のリストにインジケータを追加するのに有用でしょう。

また、上記のラジオボタン/チェックボックスは、チェックでも解除でもない状態にあるとき、 `:indeterminate` 擬似クラスに一致します。しかし、これはどういう意味でしょうか？ indeterminate になる要素には、以下のようなものが記載されています。

* `<input/radio>` 入力で、同じ名前のグループ内のすべてのラジオボタンのチェックが外れている場合
* `<input/checkbox>` 入力の `indeterminate` プロパティが、JavaScript によって `true` に設定された場合。
* `<progress>` の要素に値がない場合。

これは、あまり頻繁に使用するものではないでしょう。1つの用途は、ユーザーが次に移動する前に、必ずラジオボタンを選択する必要があることを指示するためのものです。

既定値が何であるかをユーザーに知らせ、不確定な場合にラジオボタンのスタイルを設定する、前の例の修正版をいくつか見てみましょう。これらは両方とも、入力に以下のような HTML 構造を有しています。

```html
<p>
  <input type="radio" name="fruit" value="cherry" id="cherry" />
  <label for="cherry">Cherry</label>
  <span></span>
</p>
```

`default` の例では、中央のラジオボタン入力に `checked` 属性を追加して、読み込んだときに既定で選択されるようにしました。そして、以下の CSS でスタイルを設定しています。

```css
input ~ span {
  position: relative;
}

input:default ~ span::after {
  font-size: 0.7rem;
  position: absolute;
  content: "Default";
  color: white;
  background-color: black;
  padding: 5px 10px;
  right: -65px;
  top: -3px;
}
```

これは、ページが読み込まれたときに最初に選択されたものの上に、小さな "Default" ラベルを提供しています。ここでは、次兄弟結合子 (`+`) ではなく、後続兄弟結合子 (`~`) を使用していることに注意してください。 `<span>` がソース順では `<input>` のすぐ後に来ないので、このようにする必要があるのです。

以下のライブ結果を見てください。

**メモ:** この例は GitHub の radios-checked-default.html でもライブで見ることができます（ソースコード も参照してください）。

`:indeterminate` の例では、既定値で選択されたラジオボタンがありません。これは重要なことで、もしあれば、スタイル設定する不定値の状態は存在しません。不確定なラジオボタンには以下の CSS でスタイルを設定しています。

```css
input[type="radio"]:indeterminate {
  border: 2px solid red;
  animation: 0.4s linear infinite alternate border-pulse;
}

@keyframes border-pulse {
  from {
    border: 2px solid red;
  }

  to {
    border: 6px solid red;
  }
}
```

これは、ラジオボタンの上に楽しい小さなアニメーションの境界線を作成し、あなたがそれらのいずれかを選択する必要があることを期待します。

以下のライブ結果をご覧ください。

**メモ:** この例は GitHub の radios-checked-indeterminate.html でライブで見ることもできます（ソースコード も参照してください）。

**メモ:** `indeterminate` 状態を含む興味深い例 が `<input type="checkbox">` リファレンスページに掲載されていますので、参考にしてください。

その他の擬似クラス
---------

他にも興味のある擬似クラスはたくさんありますが、ここでそのすべてを詳しく書くスペースはありません。ここでは、時間をかけて調査する必要があるいくつかのものについて説明します。

以下のものは、現代のブラウザーはかなり対応しています。

* `:focus-within` 擬似クラスは、フォーカスを保有する要素、あるいはフォーカスを保有する要素を 含む\_ 要素に一致します。これは、フォーム内の入力にフォーカスが当たったとき、フォーム全体を何らかの方法でハイライトさせたい場合に有用です。
* `:focus-visible` 擬似クラスは、（タッチやマウスではなく）キーボード操作によってフォーカスを受けた要素に一致します。マウス（または他の）フォーカスとは異なるスタイルをキーボードフォーカスのために使用したい場合、有用です。
* `:placeholder-shown` 擬似クラスは、`<input>` と `<textarea>` 要素の値が空であるため、そのプレースホルダーを表示している（すなわち `placeholder` 属性の内容を示している）要素に一致します。

以下のものも興味深いですが、まだブラウザーの対応が十分でありません。

* `:blank` 擬似クラスは、空のフォームコントロールを選択します。`:empty` も `<input>` のように子を持たない要素に一致しますが、より一般的です。 `<br>` や `<hr>` など別の空の要素にも一致します。`:empty` はそれなりにブラウザーが対応しています。`:blank` 擬似クラスの仕様書はまだ完成していないので、どのブラウザーも対応しているわけではありません。
* `:user-invalid` 疑似クラスは、対応されると `:invalid` に似ていますが、使い勝手がより良くなるでしょう。入力にフォーカスが当たった時に値が有効であれば、ユーザーがデータを入力する際に、その要素が `:invalid` に一致する可能性がありますが、フォーカスが外れた時には `:user-invalid` にのみ一致することになります。元々無効な値であった場合は、フォーカスがある間中、 `:invalid` と `:user-invalid` の両方に一致することになります。 `:invalid` と同様に、値が有効になると `:user-invalid` と一致しなくなります。
スキルをテストしましょう
------------

この記事の終わりまで到達しました。しかし、肝要な点を思い起こせるでしょうか？次に進む前に、テストによって知識の定着を試すことができます——スキルテスト：高度なスタイル設定 (en-US)をご覧ください。

まとめ
---

これで、フォーム入力に関連する UI 擬似クラスについての説明は完全に終了しました。どんどん使って、楽しいフォームスタイルを作成してください。次は別のもの、クライアントサイドフォーム検証に移動しましょう。

* 前のページ
* Overview: Forms
* 次のページ


カスタムフォームコントロールの作成方法 - ウェブ開発を学ぶ

カスタムフォームコントロールの作成方法
===================

HTML フォームで使用可能なコントロールだけでは十分でない場合が多くあります。例えば、`<select>` 要素のようなコントロールに高度なスタイル設定を行いたい場合や、独自の動作を提供したい場合は、あなた独自のコントロールを作成するしかありません。

本記事では、そのようなコントロールの作り方を見ていきます。その目的のため、次の例に取り組みます: `<select>` 要素の再構築です。このほかに独自コントロール作成をどういう方法でするか、いつするか、またそれが意味をなすのかや、コントロール作成が必須なときに何に気をつけるべきかを扱います。

**メモ:** ここではコントロールの構築に注目しており、汎用かつ再利用可能なコードの作成法は見ていきません。それには JavaScript の重要なコードや未知のコンテキストでの DOM 操作が組み合わされており、本記事の対象から外れます。

デザイン、構造、セマンティクス
---------------

カスタムコントロールを作成する前に、何をしたいかをはっきりと理解することから始めるべきです。これはあなたの貴重な時間を節約するでしょう。特に、コントロールの全状態を明確に定義することが重要です。これを行うには、状態や動作がよく知られている既存のコントロールからスタートするとよいでしょう。この結果、簡単に可能な限りの模倣を行えます。

本記事の例では、 `<select>` 要素を再構築します。以下が、私たちが実現したい成果です:

![The three states of a select box](/ja/docs/Learn/Forms/How_to_build_custom_form_controls/custom-select.png)

このスクリーンショットでは、コントロールの主要な状態 3 つを示しています: 通常状態 (左)、アクティブ状態 (中央)、そして開いた状態 (右) です。

動作の点では、ネイティブな HTML 用要素を再作成します。このため、ネイティブ HTML 要素と同様な動作や意味を持たせるべきです。独自のコントロールもネイティブコントロールと同様に、キーボードだけでなくマウスでも使用できるように、またスクリーンリーダーに理解できるようにしたいと考えます。コントロールがどのように各状態に達するかを定義することから始めましょう:

**コントロールは以下のときに通常状態になります。**

* ページを読み込む
* コントロールはアクティブであったが、ユーザーがコントロール以外のどこかをクリックした
* コントロールはアクティブであったが、キーボードを使用して別のコントロールにフォーカスを移した (例: `Tab` キー)

**コントロールは以下のときにアクティブ状態になります。**

* ユーザーがコントロール上でクリックする
* ユーザーが Tab キーを押下して、コントロールがフォーカスを得る
* コントロールが開いた状態で、ユーザーがコントロールをクリックする

**コントロールは以下のときに開いた状態になります。**

* コントロールが開いた状態ではないときに、ユーザーがコントロールをクリックした

状態をどのように変えるかを理解したら、コントロールの値をどのように変えるかの定義が重要になります。

**以下のときに値が変わります。**

* コントロールが開いた状態であるときに、ユーザーが選択肢をクリックする
* コントロールがアクティブ状態であるときに、ユーザーが上下矢印キーを押下する

**以下のときには値は変わりません。**

* 最初のオプションが選択済みのときに、ユーザーが上矢印をクリックする
* 最後のオプションが選択済みのときに、ユーザーが下矢印をクリックする

最後に、コントロールの選択肢がどのように動作するかを定義しましょう。

* コントロールが開いているとき、選択されている選択肢は強調されます
* マウスポインタが選択肢の上にあるときはその選択肢が強調され、また前に強調されていた選択肢は通常状態に戻ります

この例の用途としては、ここまでです。しかし注意深い読者の方は、いくつかの動作が欠けていることに気づくでしょう。例えば、コントロールが開いた状態であるときにユーザーが Tab キーを押すと何が起きると考えますか? その答えは... 何も起きません。正しい動作は明らかでしょうが、実際は私たちの仕様で定義されていないため、とても見逃されやすいのです。これは、コントロールの動作を設計する人と実装する人が異なるチーム環境で特に当てはまります。

別のおもしろい例です。コントロールが開いた状態であるときに上下矢印キーを押すと何が起きるのでしょうか? こちらはやや難しくなります。アクティブ状態と開いた状態をまったく別のものと考えるなら、その答えはやはり "何も起きません" です。これは、開いた状態でのキーボードの作用を定義していないためです。一方、アクティブ状態と開いた状態が少し重なると考えるなら、値は替わるかもしれませんがそれに対応して選択肢が強調されることはないでしょう。繰り返しになりますが、これはコントロールが開いた状態の選択肢に対するキーボードの作用を定義していないためです (コントロールが開いた状態で何が起きるかだけを定義しており、その後がないためです)。

もう少し突っ込んで考えてみます: エスケープキーはどうでしょう? `Esc` キーを押すと開いた select が閉じます。ネイティブの `<select>` と同じ機能を提供する場合、キーボードやマウスやスクリーンリーダーへのタッチ、その他あらゆる入力機器まで、全てのユーザーにとっての select の動作と全く同じようにふるまうべきです。

この例では欠けている仕様が明らかですので対処するでしょうが、めずらしい新たなコントロールでは真の問題になり得ます。標準要素では、 `<select>` もその 1 つですが、仕様の作成者は膨大な時間をかけて全てのユースケースの全ての入力機器の全ての操作を指定します。新コントロールの作成は簡単ではなく、特にそれが作成されたことのないものの場合は、どのような動作が正しいかについて、わずかなアイデアですら誰も持っていないため簡単ではないです。少なくとも select はこれまでやってきたため、どうふるまうかはわかっています。

一般的に、新しい操作を設計するのは、標準を作成するに十分なリーチを持った、とても大きな産業プレイヤーだけの選択肢です。例えば、Apple は 2001 年に iPod にスクロールホイールを導入しました。完全に新しい操作方法のデバイスを導入するのに成功するマーケットシェアがありましたが、たいていのデバイス会社はそうはいきません。

新しいユーザーインタラクションを発明しないのがベストです。インタラクションを追加する場合、設計段階で時間を使うのが重要です。動作の定義が貧弱であったり定義もれがあったりした場合、いったんユーザーが使い始めると動作を再定義するのが非常に困難になると思われますので、設計段階に時間をかけることは賢明です。もし疑っているのでしたら、他の人に意見を聞きましょう。また予算を持っているのでしたら、ユーザーテストの実施をためらってはいけません。このプロセスは、UX デザインと呼ばれます。この点について詳しく学びたいのでしたら、以下の役に立つリソースをご覧になるとよいでしょう。

* UXMatters.com
* UXDesign.com
* The UX Design section of SmashingMagazine

**メモ:** さらにほとんどのシステムでは、使用できる選択肢すべてを見るために `<select>` 要素を開く手段があります (これは `<select>` 要素をマウスでクリックするのと同じです)。これは Windows では `Alt` + `Down` キー で実現できますが、この例では実装しません。しかし、仕組みはすでに `click` イベント向けに実装されていますので、行うのは簡単です。

HTML の構造とセマンティクスの定義
-------------------

コントロールの基本的な機能が決まりましたので、構築を始めるときが来ました。最初のステップはコントロールの HTML 構造の定義と、基本的なセマンティクスの付与です。こちらが、 `<select>` 要素の再構築に必要な HTML です。

```html
<!-- これはコントロールの中心的なコンテナーです。
 tabindex 属性は、ユーザーがコントロールにフォーカスを当てられるようにするものです。
 これを JavaScript で設定する方がよいことは、後で見ていきます。-->
<div class="select" tabindex="0">
  <!-- このコンテナは、コントロールの現在の値を表示するために使用します。-->
  <span class="value">Cherry</span>

  <!-- このコンテナは、コントロールで使用できるすべての選択肢を包含します。
 これはリストですから、ul 要素を使用するとよいでしょう。-->
  <ul class="optList">
    <!-- 各々の選択肢は表示される値だけを包含しており、フォームのデータで送信される
 実際の値を処理する方法は後で見ていきます。-->
    <li class="option">Cherry</li>
    <li class="option">Lemon</li>
    <li class="option">Banana</li>
    <li class="option">Strawberry</li>
    <li class="option">Apple</li>
  </ul>
</div>
```

クラス名の使い方に注目してください。これらは基盤となる実際の HTML とは関係なく、フォームに関するそれぞれの部分を示します。これは CSS や JavaScript を強固な HTML の構造と結びつけないようにするために重要であり、そのためにコントロールを扱うコードを壊すことなく、後から実装を変更することができます。例えば `<optgroup>` 要素と同等の機能を実装したい場合などです。

クラス名は、しかしながら、意味のある値ではありません。現在の状態では、スクリーンリーダーのユーザーのみがリストを"見る"ことができます。ARIA セマンティクスを少し追加します。

CSS でルックアンドフィールを作成する
--------------------

構造ができましたので、コントロールのデザインを始められます。カスタムコントロールを作成する上でのポイントは、望むとおりにコントロールへスタイルを設定できることです。そのために、CSS を 2 つの部分に分けます: ひとつはコントロールが `<select>` 要素のように動作するために欠かせない CSS ルールであり、もうひとつは希望する見た目にするための好みのスタイルで構成されます。

### 必須のスタイル

必須のスタイルは、コントロールの 3 つの状態を扱うために欠かせないものです。

```css
.select {
  /\* 選択肢のリスト向けのポジショニングコンテキストを作成します;
 adding this to .select:focus-within will be a better option when fully supported
 \*/
  position: relative;

  /\* コントロールをテキストフローの一部かつまとまった大きさにします \*/
  display: inline-block;
}
```

アクティブ状態であるコントロールのルックアンドフィールを定義するため、追加で `active` クラスが必要です。このコントロールはフォーカスを得ることができますので、同様に動作させるためにカスタムスタイルを `:focus` 擬似クラスにも適用します。

```css
.select.active,
.select:focus {
  outline: none;

  /\* box-shadow プロパティは必須ではありませんが、これをデフォルト値として使用するのは
 アクティブ状態を見えるようにするために重要です。自由に書き換えてください。\*/
  box-shadow: 0 0 3px 1px #227755;
}
```

次に、選択肢のリストを扱いましょう:

```css
/\* .select セレクターは、私たちが定義するクラスがコントロールの内部にあることを
 確実にするためのシンタックスシュガーです。\*/
.select .optList {
  /\* 選択肢のリストが値の下部かつ HTML フローの外側に表示される
 ようにします。\*/
  position: absolute;
  top: 100%;
  left: 0;
}
```

選択肢のリストが隠れている状態を扱うための追加クラスも必要です。これはアクティブ状態と開いた状態で完全には一致しない相違点を扱うために必要です。

```css
.select .optList.hidden {
  /\* これはアクセシブルな方法でリストを隠すための簡単な方法です。
 アクセシビリティについては最後に説明します。\*/
  max-height: 0;
  visibility: hidden;
}
```

**メモ:** 選択肢のリストに高さと幅を与えないように `transform: scale(1, 0)` も使えます。

### 美化

基本的な機能性を適切に置きましたので、戯れを始められます。以下は何ができるかの例であり、本記事の冒頭で示したスクリーンショットに一致するものです。とはいえ、自由に実験して何ができるかを見てみるとよいでしょう。

```css
.select {
  /\* 算出結果は、ほとんどのブラウザーで既定値である 1em が 16px を想定します。
 px から em への変換がわからない場合は http://riddle.pl/emcalc/ を試してください \*/
  font-size: 0.625em; /\* この値 (10px) は、本コンテキストにおける新たなフォントサイズの em 単位値です。\*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  /\* 後で追加する下向き矢印のためのスペースが必要です \*/
  padding: 0.1em 2.5em 0.2em 0.5em;
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000;
  border-radius: 0.4em;
  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45);

  /\* 最初の宣言は、線形グラデーションをサポートしないブラウザー向けのものです。\*/
  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  /\* 値がコントロールの幅より大きくなる可能性があるため、コントロールの幅を
 変更しないようにすることが必要です。内容物がオーバーフローした場合は、
 省略記号をつけるとよいでしょう。\*/
  display: inline-block;
  width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}
```

下向き矢印をデザインするための追加要素は不要です。代わりに `::after` 擬似要素を使用します。ただし、`select` クラスでシンプルな背景画像を使用することによる実装も可能です。

```css
.select:after {
  content: "▼"; /\* Unicode 文字 U+25BC を使用します。 charset メタタグの設定を確認してください。 \*/
  position: absolute;
  z-index: 1; /\* これは、矢印が選択肢のリストに重ならないようにするために重要です \*/
  top: 0;
  right: 0;

  box-sizing: border-box;

  height: 100%;
  width: 2em;
  padding-top: 0.1em;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
  text-align: center;
}
```

次に、選択肢のリストにスタイルを設定しましょう。

```css
.select .optList {
  z-index: 2; /\* 選択肢のリストが下向き矢印より上になるよう、明示的に示します。\*/

  /\* ul 要素のデフォルトスタイルを初期化します。\*/
  list-style: none;
  margin: 0;
  padding: 0;

  box-sizing: border-box;

  /\* 値の幅がコントロールの幅より小さい場合でも、選択肢のリストの幅が
 コントロール自体と同じになるようにします。\*/
  min-width: 100%;

  /\* リストが長すぎる場合に、内容物が垂直方向にはみ出します (自動的に
 垂直スクロールバーを表示します) が、水平方向にはみ出しません
 (幅を指定しないため、リストは自身の幅へ自動的に調整されます。
 それができない場合は、内容物が切り詰められます) 。\*/
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;

  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
  background: #f0f0f0;
}
```

選択肢向けに、ユーザーが選択しようとしている (あるいは選択した) 値を示せるようにするための `highlight` クラスを追加しなければなりません。

```css
.select .option {
  padding: 0.2em 0.3em; /\* 2px 3px \*/
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

これで、3 つの状態の結果は以下のようになります。

| 通常状態 | アクティブ状態 | 開いた状態 |
| --- | --- | --- |
|  |  |  |
| ソースコードを確認する |

JavaScript でコントロールに命を吹き込む
-------------------------

デザインや構造の準備ができましたので、コントロールが実際に動作するようにするための JavaScript コードを記述できます。

**警告:** 以下は教育目的のコードであり、そのままで使用するべきではありません。ご覧のとおり、さまざまな箇所に将来性のないものや古いブラウザーで動作しないものがあります。また、本番のコードでは最適化すべき冗長な箇所もあります。

### なぜ動作しないのか?

始める前に、重要事項を覚えておくことが大切です: **ブラウザー内の JavaScript は信頼できる技術ではありません**。カスタムコントロールを作成するとき、すべてをつなぎ合わせるために必要であることから JavaScript に頼らなければならないでしょう。ところが、JavaScript をブラウザーで実行できない場合がいくつもあります:

* ユーザーが JavaScript を無効にしている。これはもっともめずらしいケースです。ごく一部の人々は、今でも JavaScript を無効にしています。
* スクリプトが読み込まれません。これはよくあるケースのひとつであり、特にネットワークの信頼性が低いモバイル環境で発生します。
* スクリプトに不具合があります。この可能性は常に考慮すべきです。
* スクリプトがサードパーティのスクリプトと競合しています。これは、トラッキングのスクリプトやユーザーが使用するブックマークレットとの間で発生する可能性があります。
* スクリプトがブラウザーの拡張機能 (Firefox の NoScript 拡張機能や Chrome の ScriptBlock 拡張機能など) と競合したり、拡張機能の影響を受けたりしています。
* ユーザーが古いブラウザーを使用しており、必要な機能のいずれかがサポートされていません。これは、最先端の API を使用するときに頻繁に発生します。
* ユーザーは JavaScript が完全にダウンロード、解析、実行される前にコンテンツを操作します。

このようなリスクがあるため、JavaScript が動作しない場合に何が起きるかを注意深く考えることが重要です。この問題について詳しく扱うのは、スクリプトをどれだけ汎用および再利用可能にしたいかと密接に関わりますので本記事の範囲を外れますが、本サンプルでは基本的な部分について考えていきます。

本記事の例では、JavaScript コードが実行されない場合に標準の `<select>` 要素にフォールバックします。われわれのコントロールと `<select>`を導入し、いずれが表示されるかは body 要素のり class に依存します。読み込みに成功したとき、body 要素の class がコントロールの関数をなす script により更新されます。

これを実現するには、2 つのことが必要です。

第一に、カスタムコントロールを使用する前に通常の `<select>` 要素を追加することが必要です。実際は、これは残りのフォームデータと共にカスタムコントロールのデータを送信できるようにするために必要です。詳しくは後述します。

```html
<body class="no-widget">
  <form>
    <select name="myFruit">
      <option>Cherry</option>
      <option>Lemon</option>
      <option>Banana</option>
      <option>Strawberry</option>
      <option>Apple</option>
    </select>

    <div class="select">
      <span class="value">Cherry</span>
      <ul class="optList hidden">
        <li class="option">Cherry</li>
        <li class="option">Lemon</li>
        <li class="option">Banana</li>
        <li class="option">Strawberry</li>
        <li class="option">Apple</li>
      </ul>
    </div>
  </form>
</body>
```

第二に、スクリプトが実行されていない場合はカスタムコントロールを、実行されている場合は「本物の」 `<select>` 要素を視覚的に隠すことができます。既定値では、 HTML コードはカスタムコントロールを隠します。

```css
.widget select,
.no-widget .select {
  /\* この CSS セレクターの基本的な意味は:
 - body のクラスを "widget" に設定して、本物の <select> 要素を隠す
 - または body のクラスを変更せずに "no-widget" のままにしておくことで、
 クラスが "select" である要素が隠される \*/
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}
```

この CSS は要素の 1 つを見えなくしますが、スクリーンリーダーからは利用できます。

ここで、スクリプトを実行するか否かを判断するための JavaScript スイッチが必要になります。このスイッチはとても簡単です: ページを読み込むときにスクリプトを実行したら、`no-widget` クラスを削除して `widget` クラスを追加します。これにより `<select>` 要素やカスタムコントロールの可視性を切り替えます。

```js
window.addEventListener("load", () => {
  document.body.classList.remove("no-widget");
  document.body.classList.add("widget");
});
```

| JS なし | JS あり |
| --- | --- |
|  |  |
| ソースコードを確認する |

**メモ:** コードを本当に汎用かつ再利用可能にしたい場合はクラスを切り替えるのではなく、単に `<select>` 要素を隠すためのコントロールのクラスを追加して、ページ内にあるすべての `<select>` 要素の後ろにカスタムコントロールを表す DOM ツリーを動的に追加する方がはるかによいでしょう。

### 作業をより簡単に

作成しようとしているコードでは、必要な作業すべてのために標準の JavaScript と DOM API を使用するでしょう。ここで使用するつもりである機能は以下のとおりです:

1. `classList`
2. `addEventListener()`
3. `NodeList.forEach()`
4. `querySelector()` および `querySelectorAll()`
### イベントコールバックを作成する

基盤が整いましたので、ユーザーがコントロールと対話するたびに使用されるすべての関数を定義し始めることができます。

```js
// この関数は、カスタムコントロールを非アクティブにしたいときに使用します。
// 引数は 1 つあります。
// select : 非アクティブにする `select` クラスの DOM ノード
function deactivateSelect(select) {
  // コントロールがアクティブではないときは何もしません。
  if (!select.classList.contains("active")) return;

  // カスタムコントロールの選択肢のリストを取得することが必要です。
  const optList = select.querySelector(".optList");

  // 選択肢のリストを閉じます。
  optList.classList.add("hidden");

  // そして、カスタムコントロール自身を非アクティブにします。
  select.classList.remove("active");
}

// この関数は、ユーザーがコントロールをアクティブ/非アクティブにしたがっているときに使用します。
// (順番に、他の選択操作を無効にする）
// 引数は 2 つあります。
// select : アクティブにする `select` クラスの DOM ノード
// selectList : `select` クラスであるすべての DOM ノードのリスト
function activeSelect(select, selectList) {
  // コントロールがすでにアクティブであるときは何もしません。
  if (select.classList.contains("active")) return;

  // すべてのカスタムコントロールを非アクティブにすることが必要です。
  // deactivateSelect 関数は forEach コールバック関数の要件を
  // すべて満たしていますので、仲介する無名関数を使用せずに
  // 直接使用しています。
  selectList.forEach(deactivateSelect);

  // そして、指定されたコントロールをアクティブ状態にします。
  select.classList.add("active");
}

// この関数は、ユーザーが選択肢のリストを開く/閉じることを求めたときに使用します。
// 引数は 1 つあります:
// select : 表示を切り替えるリストの DOM ノード
function toggleOptList(select) {
  // リストはコントロールから確保します。
  const optList = select.querySelector(".optList");

  // リストのクラスを表示/非表示に切り替えます。
  optList.classList.toggle("hidden");
}

// この関数は、選択肢を強調したいときに使用します。
// 引数は 2 つあります。
// select : 強調する選択肢を包含する `select` クラスの DOM ノード
// option : 強調する `option` クラスの DOM ノード
function highlightOption(select, option) {
  // カスタムコントロールで使用可能なすべての選択肢のリストを取得します。
  const optionList = select.querySelectorAll(".option");

  // すべての選択肢から強調効果を取り除きます。
  optionList.forEach((other) => {
    other.classList.remove("highlight");
  });

  // 適切な選択肢を強調します。
  option.classList.add("highlight");
}
```

以上が、カスタムコントロールのさまざまな状態を制御するために必要なもののすべてです。

次に、これらの関数と適切なイベントを関連づけます。

```js
// ドキュメントが読み込まれたときのイベントの関連づけを制御します。
window.addEventListener("load", () => {
  const selectList = document.querySelectorAll(".select");

  // 各々のコントロールは初期化が必要です。
  selectList.forEach((select) => {
    // すべての `option` も同様です。
    const optionList = select.querySelectorAll(".option");

    // ユーザーが選択肢にマウスポインタを乗せるたびに、その選択肢を強調します。
    optionList.forEach((option) => {
      option.addEventListener("mouseover", () => {
        // メモ: 変数 `select` および `option` は、関数呼び出しのスコープ内でのみ
        // 使用可能なクロージャです。
        highlightOption(select, option);
      });
    });

    // ユーザーが独自の select 要素でクリックするたびに
    select.addEventListener("click", (event) => {
      // メモ: 変数 `select` は、関数呼び出しのスコープ内でのみ
      // 使用可能なクロージャです。

      // 選択肢のリストの可視性を切り替えます。
      toggleOptList(select);
    });

    // コントロールが再びフォーカスを得た場合
    // ユーザーがコントロールをクリックしたり、コントロールへアクセスするために
    // Tab キーを使用するたびに、コントロールはフォーカスを得ます。
    select.addEventListener("focus", (event) => {
      // メモ: 変数 `select` および `selectList` は、関数呼び出しのスコープ内でのみ
      // 使用可能なクロージャです。

      // コントロールをアクティブにします。
      activeSelect(select, selectList);
    });

    // コントロールがフォーカスを失った場合
    select.addEventListener("blur", (event) => {
      // メモ: 変数 `select` は、関数呼び出しのスコープ内でのみ
      // 使用可能なクロージャです。

      // コントロールを非アクティブにします。
      deactivateSelect(select);
    });

    // ユーザーが`esc`を押した場合にフォーカスを外す
    select.addEventListener("keyup", (event) => {
      // deactivate on keyup of `esc`
      if (event.key === "Escape") {
        deactivateSelect(select);
      }
    });
  });
});
```

この時点でコントロールは設計どおりに状態が変わりますが、コントロール値はまだ更新されません。次の章でこれを扱います。

| Live example |
| --- |
|  |
| ソースコードを確認する |

### コントロールの値を制御する

コントロールが動作するようになりましたので、ユーザーの入力に従って値を更新して、フォームデータと共にその値を送信できるようにするコードを追加しなければなりません。

これを行うもっとも簡単な方法は、覆い隠したネイティブコントロールを使用することです。そのようなコントロールはブラウザーが提供するすべての組み込みのコントロールと共に値の経過を保持しており、フォームを送信するときは通常どおりに値を送信します。これらすべてを行えるようにするために、車輪の再発明を行うのは無駄です。

先ほど見たように、アクセシビリティの理由からフォールバック手段としてすでにネイティブの select コントロールを使用しています。単純に、その値をカスタムコントロールの値と同期することができます:

```js
// この関数は、表示される値を更新してネイティブコントロールの値と同期します。
// 引数は 2 つあります:
// select : 更新する値を持つ `select` クラスの DOM ノード
// index : 選択される値のインデックス
function updateValue(select, index) {
  // 指定されたカスタムコントロール向けのネイティブコントロールを取得することが必要です。
  // この例では、ネイティブコントロールはカスタムコントロールの兄弟です。
  const nativeWidget = select.previousElementSibling;

  // カスタムコントロールの値のプレースホルダーの取得も必要です。
  const value = select.querySelector(".value");

  // そして、選択肢の全リストが必要です。
  const optionList = select.querySelectorAll(".option");

  // 選択した値のインデックスを、selectedIndex に設定します。
  nativeWidget.selectedIndex = index;

  // 上記に応じて、値のプレースホルダーも更新します。
  value.innerHTML = optionList[index].innerHTML;

  // そして、カスタムコントロールで対応する選択肢を強調します。
  highlightOption(select, optionList[index]);
}

// この関数は、ネイティブコントロールで現在選択されているインデックスを返します。
// 引数は 1 つあります:
// select : ネイティブコントロールに関係する `select` クラスの DOM ノード
function getIndex(select) {
  // 指定されたカスタムコントロール向けのネイティブコントロールにアクセスすることが必要です。
  // この例では、ネイティブコントロールはカスタムコントロールの兄弟です。
  const nativeWidget = select.previousElementSibling;

  return nativeWidget.selectedIndex;
}
```

これら 2 つの関数で、ネイティブコントロールとカスタムコントロールを関連づけることができます:

```js
// ドキュメントが読み込まれたときのイベントの関連づけを制御します。
window.addEventListener("load", () => {
  const selectList = document.querySelectorAll(".select");

  // 各々のコントロールは初期化が必要です。
  selectList.forEach((select) => {
    const optionList = select.querySelectorAll(".option");
    const selectedIndex = getIndex(select);

    // カスタムコントロールがフォーカスを得られるようにします。
    select.tabIndex = 0;

    // ネイティブコントロールがフォーカスを得ないようにします。
    select.previousElementSibling.tabIndex = -1;

    // デフォルトで選択されている値が正しく表示されるようにします。
    updateValue(select, selectedIndex);

    // ユーザーが選択肢をクリックするのに応じて値を更新します。
    optionList.forEach((option, index) => {
      option.addEventListener("click", (event) => {
        updateValue(select, index);
      });
    });

    // フォーカスがあるコントロールでユーザーがキーボードを使用するのに応じて、値を更新します。
    select.addEventListener("keyup", (event) => {
      let index = getIndex(select);

      // ユーザーが下矢印キーを押すと、次の選択肢にジャンプします。
      if (event.key === "ArrowDown" && index < optionList.length - 1) {
        index++;
      }

      // ユーザーが上矢印キーを押すと、前の選択肢にジャンプします。
      if (event.key === "ArrowUp" && index > 0) {
        index--;
      }

      updateValue(select, index);
    });
  });
});
```

上記のコードで、 `tabIndex` プロパティを使用していることは注目に値します。このプロパティは、ネイティブコントロールにフォーカスが当たらないようにすることと、ユーザーがキーボードやマウスを使用するとカスタムコントロールがフォーカスを得るようにするために必要です。

これで完了です! 結果は以下のとおりです。

| Live example |
| --- |
|  |
| ソースコードを確認する |

ちょっと待ってください、本当に終わったのでしょうか?

アクセシブルにする
---------

フル機能のセレクトボックスとはかけ離れていますが動作するものはできましたし、よく動作しています。しかし、私たちが行ってきたことは DOM の操作にすぎません。これには実際のセマンティクスがなく、またセレクトボックスのように見えていてもブラウザーの視点からはそうではないため、支援技術はそれがセレクトボックスであるとは理解できません。つまり、このきれいなセレクトボックスはアクセシブルではありません!

幸いなことに解決策があり、それは ARIA と呼ばれます。ARIA は "Accessible Rich Internet Application" を表し、その W3C 仕様 は私たちがここで行っていることに特化して設計されています: ウェブアプリケーションやカスタムコントロールをアクセシブルにします。これは基本的には、私たちが作り出した要素がネイティブコントロールとして通るかのように、役割や状態や特性をより説明できるようにするために HTML を拡張する属性のセットです。これらの属性の使用はとても簡単ですので、行ってみましょう。

### `role` 属性

ARIA で使用される主要な属性が、`role` 属性です。`role` 属性は、要素を何に使用するかを定義する値を受け入れます。それぞれのロールは、自身の要件や動作を定義します。本記事の例では、ロール `listbox` を使用します。これは "composite role" であり、このロールの要素は子要素を持ち、またそれぞれの子要素も特定のロールを持ちます (この例では、ロール `option` の子要素が少なくとも 1 つ)。

また、ARIA は標準の HTML マークアップにデフォルトで適用されるロールを定義することも特筆に値します。例えば、`<table>` 要素はロール `grid` に、`<ul>` 要素はロール `list` にマッチします。`<ul>` 要素を使用しているため、私たちのコントロールのロール `listbox` が、`<ul>` 要素のロール `list` を置き換えるようにしなければなりません。そのために、ロール `presentation` を使用します。このロールは要素に特別な意味はないことを示せるようにするためのものであり、単に情報を与えるために使用されます。これを `<ul>` 要素に適用します。

ロール `listbox` に対応するため、HTML を以下のように更新する必要があります。

```html
<!-- 最初の要素に role="listbox" 属性を追加します -->
<div class="select" role="listbox">
  <span class="value">Cherry</span>
  <!-- ul 要素に role="presentation" を追加します -->
  <ul class="optList" role="presentation">
    <!-- すべての li 要素に role="option" 属性を追加します -->
    <li role="option" class="option">Cherry</li>
    <li role="option" class="option">Lemon</li>
    <li role="option" class="option">Banana</li>
    <li role="option" class="option">Strawberry</li>
    <li role="option" class="option">Apple</li>
  </ul>
</div>
```

**メモ:** `role` 属性と `class` 属性の両方を含める方法は、CSS 属性セレクターに対応しない古いブラウザーをサポートしたい場合にのみ必要です。

### `aria-selected` 属性

`role` を使用するだけでは不十分です。ARIA は、状態や特性を表す多くの属性も提供します。これらをより多くまた適切に使用すると、コントロールが支援技術にもっと良く理解されるようになります。ここでは、使用する属性を 1 つに絞ります。 `aria-selected` です。

`aria-selected` 属性は、どの選択肢が現在選択されているかを示すために使用します。これにより、支援技術はユーザーに現在何が選択されているかを伝えることができます。ここではユーザーが選択肢を選択するたびに、選択された選択肢を示すためにこの属性を JavaScript で動的に使用します。このために、`updateValue()` 関数の変更が必要です。

```js
function updateValue(select, index) {
  const nativeWidget = select.previousElementSibling;
  const value = select.querySelector(".value");
  const optionList = select.querySelectorAll('[role="option"]');

  // すべての選択肢が選択されていないようにします。
  optionList.forEach((other) => {
    other.setAttribute("aria-selected", "false");
  });

  // 指定された選択肢が選択されているようにします。
  optionList[index].setAttribute("aria-selected", "true");

  nativeWidget.selectedIndex = index;
  value.innerHTML = optionList[index].innerHTML;
  highlightOption(select, optionList[index]);
}
```

スクリーンリーダーにオフスクリーン select に焦点をあてて他のスタイルを無視するようにした法が簡単に見えますが、これはアクセシブルな解決策ではありません。スクリーンリーダーは盲目の人だけのものではありません。低視力や、完全な視力の人もこれを使います。このため、スクリーンリーダーをオフスクリーン要素だけに焦点をあてるようにはできません。

以下がこれらの変更を施した最終結果です （NVDA や VoiceOver などの支援技術でコントロールを使用してみても、よい感触を得られるでしょう）。

| Live example |
| --- |
|  |
| ソースコードを確認する |

もっと先に進むには、この例でのコードは汎用性や再利用性に改善が必要です。これは課題として挑戦できます。この 2 つのヒントを挙げると:すべての関数で最初の引数は同じで、つまりこれらの関数は同じコンテキストが必要です。そのコンテキストを共有するオブジェクトを作るのが賢明です。

他の方法: ラジオボタンを使う
---------------

上の例では、非動的な HTML、CSS、JavaScript を使って `<select>` 要素を再発明しました。この select は限られた選択肢から 1 つが選択され、それは radio ボタンのグループと同様な機能です。

このため、代わりにラジオボタンを使って再発明できます。このオプションを見てみましょう。

完全に意味のある、アクセシブルで、順序のない、関連する`<label>`つきのradio ボタンのリストから始めます、グループ全体を適切な意味のある`<fieldset>` と`<legend>` のペアにラベルづけします。

```html
<fieldset>
  <legend>Pick a fruit</legend>
  <ul class="styledSelect">
    <li><input type="radio" name="fruit" value="Cherry" id="fruitCherry" checked><label for="fruitCherry">Cherry</label></li>
    <li><input type="radio" name="fruit" value="Lemon" id="fruitLemon"><label for="fruitLemon">Lemon</label></li>
    <li><input type="radio" name="fruit" value="Banana" id="fruitBanana"><label for="fruitBanana"">Banana</label></li>
    <li><input type="radio" name="fruit" value="Strawberry" id="fruitStrawberry"><label for="fruitStrawberry">Strawberry</label></li>
    <li><input type="radio" name="fruit" value="Apple" id="fruitApple"><label for="fruitApple">Apple</label></li>
  </ul>
</fieldset>
```

(legend/fieldset ではなく)ラジオボタンリストに少しスタイルづけをして、前の例と同じ見た目にし、完了したことがわかるようにします。

```css
.styledSelect {
  display: inline-block;
  padding: 0;
}
.styledSelect li {
  list-style-type: none;
  padding: 0;
  display: flex;
}
.styledSelect [type="radio"] {
  position: absolute;
  left: -100vw;
  top: -100vh;
}
.styledSelect label {
  margin: 0;
  line-height: 2;
  padding: 0 0 0 4px;
}
.styledSelect:not(:focus-within) input:not(:checked) + label {
  height: 0;
  outline: none;
  overflow: hidden;
}
.styledSelect:not(:focus-within) input:checked + label {
  border: 0.2em solid #000;
  border-radius: 0.4em;
  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45);
}
.styledSelect:not(:focus-within) input:checked + label::after {
  content: "▼";
  background: black;
  float: right;
  color: white;
  padding: 0 4px;
  margin: 0 -4px 0 4px;
}
.styledSelect:focus-within {
  border: 0.2em solid #000;
  border-radius: 0.4em;
  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45);
}
.styledSelect:focus-within input:checked + label {
  background-color: #333;
  color: #fff;
  width: 100%;
}
```

JavaScript なしで少しの CSS にて、ラジオボタンのリストをスタイルづけしてチェック済み項目のみを表示することができます。フォーカスが`<fieldset>`内の `<ul>` に来ると、リストは開いて、上下左右の矢印が前後の項目を選択するのに使えます。次で試してください:

これはある程度、JavaScript なしで動作します。JavaScript が失敗しても動作する、われわれのカスタムコントロールど同じものを作ってきました。よい解決策でしょう？これはキーボードでは動作しますが、マウスクリックではそうなりません。ネイティブな意味づけのない要素を作るフレームワークに依存する代わりに、ウェブ標準をカスタムコントロールの基礎として使った方が意味があります。しかし、われわれのコントロールは `<select>` が自ずと持つ機能と同じものを備えていません。

いい面として、このコントロールはスクリーンリーダーにとって完全にアクセシブルでキーボードで完全に操作できます。しかし、このコントロールは `<select>` 要素の置き換えではありません。異なる機能や足りない機能があります。例えば、4 つの矢印は選択肢を操作できますが、最後のボタンで下を押すと最初のボタンに移動します。`<select>` のように上端、下端で止まりません。

この足りない機能の追加は、読者の課題としておきます。

まとめ
---

独自のフォームコントロールの作成方法を見てきましたが、ご覧いただいたようにこれは容易なことではありません。独自のカスタムコントロールを作る前に、HTML に要求を十分に満たす代替要素がないかを検討してください。本当にカスタムコントロールを作成する必要がある場合、サードパーティのライブラリーに頼るほうが簡単かつよいことも少なくありません。独自作成する場合、既存の要素を編集するか、準備されたコントロールを実装するフレームワークを使うようにして、実用的でアクセシブルなフォームコントロールの作成は見た目より複雑であることを忘れないでください。

自分でコーディングする前に検討するとよいライブラリーをいくつか紹介します。

* jQuery UI
* AXE accessible custom select dropdowns
* msDropDown

ラジオボタン、独自 JavaScript 、またはサードパーティライブラリーで代替コントロールを作る場合、アクセシブルかつ機能への耐性を高めましょう。すなわち Web 標準の実装状況がまちまちである、多様なブラウザーで良好に動作できるようにすることが必要です。楽しんでください!


フォームの構築方法 - ウェブ開発を学ぶ

フォームの構築方法
=========

* 前のページ
* Overview: Forms
* 次のページ

基本を押さえた上で、フォームの各パーツに構造と意味を提供している要素について、より詳しく見ていきましょう。

|  |  |
| --- | --- |
| 前提条件: |
 基本的なコンピューターリテラシーと、
 HTML の基本的な理解.
  |
| 目的: | HTML フォームを構造化して意味を与えて使いやすくアクセシブルにする方法を理解すること。 |

フォームは柔軟性があるため、HTML で最も複雑な構造の 1 つとなっています。専用のフォーム要素と属性を使用して、あらゆる種類の基本フォームを作成できます。 HTML フォームを構築するときに正しい構造を使用すると、フォームの使用性やアクセシビリティを保証するのに役立ちます。

<form> 要素
---------

`<form>` 要素はフォームを正式に定義するとともに、属性でフォームの動作を定義します。HTML フォームを作成しようとするたびに、この要素から始めて、すべてのコンテンツをその中に入れなければなりません。多くの支援技術やブラウザープラグインは `<form>` 要素を検出して、フォームを使いやすくするための特別なフックを実装しています。

前の記事ですでにこれを見ています。

**警告:** フォームの中にフォームを入れ子にすることは厳格に禁じられています。これは予期せぬ動作を発生するおそれがあるので、悪い考えです。

`<form>` 要素の外部でもフォームウィジェットを使用できますが、そのフォームウィジェットは `form` 属性を用いて関連付けなければ、どのフォームに対しても何も行わないことに注意してください。これは、フォーム内に包含されていないコントロールを明示的にフォームへ紐付けられるように導入されました。

それでは、フォームに入れ子になっている構造要素に移動してみましょう。

<fieldset> および <legend> 要素
--------------------------

`<fieldset>` 要素は、スタイルや意味付けのために、同じ目的を持つウィジェットのグループの作成に便利です。 `<fieldset>` 要素にラベルを付与するには、 `<legend>` 要素を `<fieldset>` の開始タグの直下に入れることで実現できます。`<legend>` 要素は、`<fieldset>` 要素の目的を正式に説明します。

多くの支援技術は `<legend>` 要素を、対応する `<fieldset>` 要素内にある各ウィジェットのラベルの一部であるかのように扱うでしょう。例えば Jaws や NVDA といったスクリーンリーダーは、各ウィジェットのラベルを読み上げる前に legend の内容を読み上げます。

以下に小さなサンプルを挙げます。

```html
<form>
  <fieldset>
    <legend>Fruit juice size</legend>
    <p>
      <input type="radio" name="size" id="size\_1" value="small" />
      <label for="size\_1">Small</label>
    </p>
    <p>
      <input type="radio" name="size" id="size\_2" value="medium" />
      <label for="size\_2">Medium</label>
    </p>
    <p>
      <input type="radio" name="size" id="size\_3" value="large" />
      <label for="size\_3">Large</label>
    </p>
  </fieldset>
</form>
```

**メモ:** この例は fieldset-legend.html で見ることができます(ライブ版も見てください)。

この例では、スクリーンリーダーは最初のウィジェットを "Fruit juice size small"、2 番目を "Fruit juice size medium"、3 番目を "Fruit juice size large" と読み上げるでしょう。

この例での用途は、最も重要なものの 1 つです。一連のラジオボタンを有するたびに、それらを `<fieldset>` 要素の内部に入れ子にする必要があります。他にも用途があり、一般的に `<fieldset>` 要素はフォームを分割するために使用することもできます。長いフォームは複数のページにまたがるのが理想的ですが、フォームが長くなって単一のページに収めなければならない場合、異なるフィールドセット内に異なる関連するセクションを置くことでユーザビリティを向上させることができます。

支援技術に対する影響力が大きいため、 `<fieldset>` 要素はアクセシブルなフォームを構築するための重要な要素の 1 つです。しかし、それを乱用しないことはあなたの責任です。可能であれば、フォームを作成するたびに、スクリーンリーダーがどのように解釈するかを聞いてみるようにしてください。変だと感じたら、フォームの構造を改善するようにしましょう。

<label> 要素
----------

これまでの記事で見てきたように、`<label>` 要素は、HTML フォームウィジェットのラベルを定義する正式な方法です。これは、アクセシブルなフォームを作成したい場合にもっとも重要な要素です — 適切に実装された時は、スクリーンリーダーはフォーム要素のラベルと関連する指示を一緒に読み上げます。前の記事で見てきたこの例を見てみます:

```html
<label for="name">Name:</label> <input type="text" id="name" name="user\_name" />
```

`<label>` と`<input>` とがそれぞれ `for` と `id` 属性により正し関連付けられると (label の `for` 属性は対応するウィジェットの `id` 属性を参照します)、スクリーンリーダーは "Name, edit text"のように読み上げます。

フォームコントロールとラベルを関連付けるもう 1 つの方法は、フォームコントロールを `<label>` の中でネストすることで、暗黙的に関連付けることです。

```html
<label for="name">
  Name: <input type="text" id="name" name="user\_name" />
</label>
```

この場合でも `for` 属性を設定することがベストプラクティスと考えられています。これは、ラベルとウィジェットの暗黙的な関係を理解できない支援技術があるためです。

ラベルがなかったり、フォームコントロールが明示的/暗黙にラベルに関連付けられていない場合、スクリーンリーダーは全く役立たない "Edit text blank" のような読み上げを行います。

### ラベルもクリック可能です！

ラベルをセットアップするもう 1 つの利点は、ユーザーがラベルをクリックするとウィジェットをアクティブにすることが、あらゆるブラウザーで可能になります。これは例えば、テキスト入力で、入力と同様にラベルをクリックしてフォーカスさせることができますし、ラジオボタンやチェックボックスで特に有用です — このコントロールのヒットエリアはとても小さく、できるだけ大きくしておくのは便利です。

例えば、次の例で "I like cherry" の テキストをクリックすると選択された *taste\_cherry* チェックボックスの状態が切り替わります:

```html
<form>
  <p>
    <input type="checkbox" id="taste\_1" name="taste\_cherry" value="cherry" />
    <label for="taste\_1">I like cherry</label>
  </p>
  <p>
    <input type="checkbox" id="taste\_2" name="taste\_banana" value="banana" />
    <label for="taste\_2">I like banana</label>
  </p>
</form>
```

**メモ:** この例は checkbox-label.html で見ることができます(ライブ版も見てください)。

### 複数のラベル

厳密に言うと、1 つのウィジェット内に複数のラベルを入れることができますが、複数のラベルを持つウィジェットの扱いに問題がある支援技術があるかもしれません。複数のラベルがある場合、アクセシブルなフォームを作成するには 1 つの `<label>` 要素内にウィジェットを入れ子にするとよいでしょう。

以下の例について考えてみましょう。

```html
<p>Required fields are followed by <span aria-label="required">*</span>.</p>

<!-- このようにします -->
<!--div>
 <label for="username">Name:</label>
 <input id="username" type="text" name="username">
 <label for="username"><span aria-label="required">\*</abbr></label>
</div-->

<!-- より良い例です -->
<!--div>
 <label for="username">
 <span>Name:</span>
 <input id="username" type="text" name="username">
 <span aria-label="required">\*</span>
 </label>
</div-->

<!-- これが最も良いでしょう -->
<div>
  <label for="username">Name: <span aria-label="required">*</span></label>
  <input id="username" type="text" name="username" />
</div>
```

このサンプルでは、最初の段落で入力必須の要素の規則を定義しています。ユーザーが入力必須の要素を見つける前にスクリーンリーダーのような支援技術が注意事項を表示したり読み上げたりするためには、規則をはじめに置かなければなりません。これがユーザーにアスタリスクの意味を知らせても、それに依存することはできません。スクリーンリーダーはアスタリスクが出てくると "スター" と読み上げます。視力のあるユーザーがマウスを持ってくると、`title` 属性によって"必須"と表示されます。タイトルはスクリーンリーダーの設定により読み上げられるので、常にスクリーンリーダーに読み上げられる `aria-label` (en-US) 属性を入れておくのがより信頼性が高いでしょう。

上記の違いをふまえると、以降を効率よく見ていけるでしょう。

* 最初の例では、 label 要素すべての入力が読み上げられません — "edit text blank" だけです。その上に実際のラベルは別々に読まれます。複数の `<label>` 要素はスクリーンリーダーを混乱させます。
* 2 つ目の例では、少し明確になります "name star name edit text required" というようにラベルが入力と一緒に読み上げられます。しかしラベルはまだ別々に読み上げられます。少し混乱しやすいですが、これは `<input>` と関連したラベルがあるためまだ良いでしょう。
* 3 つ目の例が最も良いです — 実際のラベルがすべて一緒に読み上げられ、 "name required edit text" のようにラベルと入力が一緒に読み上げられます。

**メモ:** スクリーンリーダーによっては、少し異なる結果になる場合もあります。これは VoiceOver (と同様に動作する NVDA)でテストしています。あなたの体験を聞きたいです。

**メモ:** この例は GitHub の required-labels.html で見ることができます(ライブ版も見てください)。2 や 3 のコメントを外したバージョンの例を実行しないでください — 複数の label と複数の同じ input ID があると、スクリーンリーダーは確実に混乱します!

フォームで使用される一般的な HTML 構造
----------------------

ウェブフォーム特有の構造の前提として、フォームは単に HTML であると覚えておくとよいでしょう。つまり、ウェブフォームを組み立てるために HTML のすべての力を利用できるのです。

サンプルでわかるように、ラベルとそのウィジェットを `<ul>` や `<ol>` リストの中の `<li>` 要素で包み込むのが一般的な慣習です。HTML リストにあるように、`<p>` 要素と `<div>` 要素も良く使われます。リストは複数のチェックボックスやラジオボタンを構造化するのに最もよく使われます。

`<fieldset>` 要素に加えて、複雑なフォームの構築に HTML の見出し (例`<h1>`, `<h2>`) やセクション (例 `<section>`) を使うことも一般的です。

とりわけ、コーディングスタイルがどうあるのが心地よく、どれがアクセシブルで使いやすいフォームとなるのかを見つけるのはあなた次第です。別の機能セクションは別の `<section>` 要素と、ラジオボタンを含む `<fieldset>` にそれぞれ分けておくべきです。

### アクティブラーニング: フォーム構造を構築する

これらのアイデアを実践し、もう少し複雑なフォーム構造、つまり支払いフォームを作成しましょう。このフォームはあなたがまだ理解していないかもしれないウィジェットタイプをいくつも含みますが、今はそのことを心配しないでください。次の記事 (基本的なネイティブフォームコントロール) でそれらがどのように機能するのかがわかります。今のところ、以下の説明に沿って説明を注意深く読み、フォームを構成するためにどのラッパー要素を使用しているか、そしてその理由を理解することから始めてください。

1. あらかじめ、空のテンプレートファイルとお支払いフォームの CSS のローカルコピーをコンピューターの新しいディレクトリーに作成します。
2. まず最初に、HTML `<head>` 内に次の行を追加して CSS を HTML に適用します。

```html
<link href="payment-form.css" rel="stylesheet" />
```

3. 次に、外側の `<form>` 要素を追加してフォームを作成します:

```html
<form></form>
```

4. `<form>` タグ内に、必須フィールドにマークを付ける方法をユーザーに通知するための見出しと段落を追加します:

```html
<h1>Payment form</h1>
<p>
  Required fields are followed by
  <strong><abbr title="required">*</abbr></strong
 >.
</p>
```

5. 次に、前のエントリーの下に、より大きなコードセクションをフォームに追加します。ここでは、連絡先情報フィールドを個別の `<section>` 要素内にラップしていることがわかります。さらに、2 つのラジオボタンのセットがあり、それぞれ独自のリスト (`<li>`) 要素の中に入れています。最後に、2 つの標準テキスト `<input>` とそれに関連する `<label>` 要素があり、それぞれ `<p>` の内側に含まれていて、パスワードを入力するためのパスワード入力があります。フォームにこのコードを追加してください:

```html
<section>
  <h2>Contact information</h2>
  <fieldset>
    <legend>Title</legend>
    <ul>
      <li>
        <label for="title\_1">
          <input type="radio" id="title\_1" name="title" value="K" />
          King
        </label>
      </li>
      <li>
        <label for="title\_2">
          <input type="radio" id="title\_2" name="title" value="Q" />
          Queen
        </label>
      </li>
      <li>
        <label for="title\_3">
          <input type="radio" id="title\_3" name="title" value="J" />
          Joker
        </label>
      </li>
    </ul>
  </fieldset>
  <p>
    <label for="name">
      <span>Name: </span>
      <strong><abbr title="required">*</abbr></strong>
    </label>
    <input type="text" id="name" name="username" />
  </p>
  <p>
    <label for="mail">
      <span>E-mail: </span>
      <strong><abbr title="required">*</abbr></strong>
    </label>
    <input type="email" id="mail" name="usermail" />
  </p>
  <p>
    <label for="pwd">
      <span>Password: </span>
      <strong><abbr title="required">*</abbr></strong>
    </label>
    <input type="password" id="pwd" name="password" />
  </p>
</section>
```

6. それでは、フォームの 2 番目の `<section>` — 支払い情報に目を向けます。ここには 3 つの異なるウィジェットとそのラベルがあり、それぞれ `<p>` の中に含まれています。1 つ目は、クレジットカードの種類を選択するためのドロップダウンメニュー (`<select>`) です。2 番目は、クレジットカード番号を入力するための `tel` 型の `<input>` 要素です。`number` 型を使うこともできますが、そのスピナー UI は望ましくありません。最後のものは、カードの有効期限を入力するための `date` 型の `<input>` 要素です。これは、サポートしているブラウザーでは日付選択ウィジェットが表示され、サポートしていないブラウザーでは通常のテキスト入力に戻ります。新しい入力型は HTML5 の入力型で再度紹介します。
 前のセクションの下に次のように入力してください。

```html
<section>
  <h2>Payment information</h2>
  <p>
    <label for="card">
      <span>Card type:</span>
    </label>
    <select id="card" name="usercard">
      <option value="visa">Visa</option>
      <option value="mc">Mastercard</option>
      <option value="amex">American Express</option>
    </select>
  </p>
  <p>
    <label for="number">
      <span>Card number:</span>
      <strong><abbr title="required">*</abbr></strong>
    </label>
    <input type="tel" id="number" name="cardnumber" />
  </p>
  <p>
    <label for="date">
      <span>Expiration date:</span>
      <strong><abbr title="required">*</abbr></strong>
      <em>formatted as mm/dd/yyyy</em>
    </label>
    <input type="date" id="date" name="expiration" />
  </p>
</section>
```

7. 最後に追加するセクションはもっと単純で、フォームデータを送信するための `submit` タイプの `<button>` のみを含みます。これをフォームの一番下に追加してください:

```html
<p><button type="submit">Validate the payment</button></p>
```

完成したフォームは以下のように動作しています (GitHub でも確認できます。payment-form.html ソースを参照してライブ実行してください)。

あなたのスキルをテストしてみましょう!
-------------------

この記事はここまでですが、最も重要な情報を覚えていますか？ 先に進む前に、この情報を保持しているかどうかを確認するためのテストがあります — Test your skills: Form structure (en-US) を参照してください。

まとめ
---

ウェブフォームを適切に構築するためのあらゆる知識を得ることができました。これからここで出てきた機能を見ていき、次の記事では、ユーザーから情報を集めるのに使いたくなるすべての種類のフォームウィジェットの詳細な実装について詳しく見ていきます。

関連情報
----

* A List Apart: *Sensible Forms: A Form Usability Checklist*
* 前のページ
* Overview: Forms
* 次のページ


フォームコントロール向けの CSS プロパティの互換性一覧表 - ウェブ開発を学ぶ

フォームコントロール向けの CSS プロパティの互換性一覧表
==============================

以下の実装状況一覧表で、 HTML フォーム向けの CSS の対応状況を要約しています。 HTML フォームや CSS の複雑さにより、これらの表は完全なリファレンスであるとはいえません。それでも、何ができて何ができないかの見識を得られるでしょう。また、どうすればよいかを知る助けにもなるでしょう。

表の見方
----

### 値

それぞれのプロパティで取り得る値は 4 種類あります。

✅ Yes

プロパティは各ブラウザー間で、一貫してほどよく対応されています。もっとも、特殊なケースでは奇妙な副作用に直面するかもしれません。

⚠ Partial

プロパティは動作しますが、たびたび奇妙な副作用や一貫性のなさに直面するかもしれません。まず副作用について熟知しているのでなければ、これらのプロパティは避けるべきでしょう。

❌ No

プロパティは動作しない、または頼りにできるほどの一貫性がありません。

n.a.

当該ウィジェットに対して、そのプロパティは意味がありません。

### レンダリング

それぞれのプロパティで可能なレンダリング方法は 2 種類あります:

N (Normal)

プロパティをそのまま適用することを表します。

T (Tweaked)

下記の追加ルールとともにプロパティを適用することを表します:

```css
\* {
  /\* これは、WebKit ベースのブラウザーでネイティブのルックアンドフィールを無効にします \*/
  appearance: none;

  /\* Internet Explorer 用 \*/
  background: none;
}
```

互換性一覧表
------

### 全体的な動作

全体的なレベルで多くのブラウザーに共通する動作があります。

`border`, `background`, `border-radius`, `height`

これらのプロパティのいずれかを使用すると、一部のブラウザーでネイティブのルックアンドフィールが部分的に、あるいは全面的に無効になることがあります。これらを使用する際は注意してください。

`line-height`

このプロパティの対応状況は各ブラウザー間で一貫性がありませんので、避けるべきでしょう。

`text-decoration`

このプロパティはフォームウィジェットにおいて Opera が対応していません。

`text-overflow`

Opera、Safari および IE9 ではフォームウィジェットでこのプロパティに対応していません。

`text-shadow`

Opera は `text-shadow` をフォームウィジェットで対応していません。また、IE9 はまったく対応していません。

### テキストフィールド

`text`, `search`, `password` の入力型を見てください。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ⚠ Partial[1][2] | ✅ Yes | 1. WebKit ブラウザーは (主に Mac OSX や iOS
 で)、検索フィールドでネイティブのルックアンドフィールを使用します。従って、検索フィールドにこのプロパティを適用できるようにするために、`-webkit-appearance:none`
 を使用することが必要です。
2. Windows 7 で Internet Explorer 9 は、
 `background:none` が適用されなければ境界を適用しません。
 |
| `border` | ⚠ Partial[1][2] | ✅ Yes | 1. WebKit ブラウザーは (主に Mac OSX や iOS
 で)、検索フィールドでネイティブのルックアンドフィールを使用します。従って、検索フィールドにこのプロパティを適用できるようにするために、`-webkit-appearance:none`
 を使用することが必要です。
2. Windows 7 で Internet Explorer 9 は、`background:none`
 が適用されなければ境界を適用しません。
 |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ⚠ Partial[1][2] | ✅ Yes | 1. WebKit ブラウザーは (主に Mac OSX や iOS
 で)、検索フィールドでネイティブのルックアンドフィールを使用します。従って、検索フィールドにこのプロパティを適用できるようにするために、`-webkit-appearance:none`
 を使用することが必要です。
2. Windows 7 で Internet Explorer 9 は、`background:none`
 が適用されなければ境界を適用しません。
 |
| *テキストとフォント* |
| --- |
| `color`[1] | ✅ Yes | ✅ Yes | 1. `border-color`
 プロパティが設定されていない場合、一部の WebKit ベースブラウザーは
 ``<textarea>``
 において、フォントだけでなく境界にも `color`
 プロパティを適用します。
 |
| `font` | ✅ Yes | ✅ Yes | `line-height` の注意事項をご覧ください。 |
| `letter-spacing` | ✅ Yes | ✅ Yes |  |
| `text-align` | ✅ Yes | ✅ Yes |  |
| `text-decoration` | ⚠ Partial | ⚠ Partial | Opera に関する注意事項をご覧ください。 |
| `text-indent` | ⚠ Partial[1] | ⚠ Partial[1] | 1. IE9 はこのプロパティを
 ``<textarea>`` のみで、Opera
 は単一行のテキストフィールドのみで対応しています。
 |
| `text-overflow` | ⚠ Partial | ⚠ Partial |  |
| `text-shadow` | ⚠ Partial | ⚠ Partial |  |
| `text-transform` | ✅ Yes | ✅ Yes |  |
| *境界と背景* |
| --- |
| `background` | ⚠ Partial[1] | ✅ Yes | 1. WebKit ブラウザーは (主に Mac OSX や iOS
 で)、検索フィールドでネイティブのルックアンドフィールを使用します。従って、検索フィールドにこのプロパティを適用できるようにするために、`-webkit-appearance:none`
 を使用することが必要です。Windows 7 で Internet Explorer 9 は、`background:none`
 が適用されなければ境界を適用しません。
 |
| `border-radius` | ⚠ Partial[1][2] | ✅ Yes | 1. WebKit ブラウザーは (主に Mac OSX や iOS
 で)、検索フィールドでネイティブのルックアンドフィールを使用します。従って、検索フィールドにこのプロパティを適用できるようにするために、`-webkit-appearance:none`
 を使用することが必要です。Windows 7 で Internet Explorer 9 は、`background:none`
 が適用されなければ境界を適用しません。
2. Opera では、明示的に境界が設定されている場合にのみ
 `border-radius` が適用されます。
 |
| `box-shadow` | ❌ No | ⚠ Partial[1] | 1. IE9 はこのプロパティに対応していません。
 |

### ボタン

`button`, `submit`, and `reset` input types and the ``<button>`` 要素を見てください。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ⚠ Partial[1] | ✅ Yes | 1. Mac OSX や iOS では、WebKit
 ベースブラウザーでこのプロパティが適用されません。
 |
| `border` | ⚠ Partial | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ⚠ Partial[1] | ✅ Yes | 1. Mac OSX や iOS では、WebKit
 ベースブラウザーでこのプロパティが適用されません。
 |
| *テキストとフォント* |
| --- |
| `color` | ✅ Yes | ✅ Yes |  |
| `font` | ✅ Yes | ✅ Yes | `line-height` の注意事項をご覧ください。 |
| `letter-spacing` | ✅ Yes | ✅ Yes |  |
| `text-align` | ❌ No | ❌ No |  |
| `text-decoration` | ⚠ Partial | ✅ Yes |  |
| `text-indent` | ✅ Yes | ✅ Yes |  |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ⚠ Partial | ⚠ Partial |  |
| `text-transform` | ✅ Yes | ✅ Yes |  |
| *境界と背景* |
| --- |
| `background` | ✅ Yes | ✅ Yes |  |
| `border-radius` | ✅ Yes[1] | ✅ Yes[1] | 1. Opera では、明示的に境界が設定されている場合にのみ
 `border-radius` が適用されます。
 |
| `box-shadow` | ❌ No | ⚠ Partial[1] | 1. IE9 はこのプロパティに対応していません。
 |

### 数値

`number` 入力タイプを見てください。フィールドの値の変更に使用するスピンボタンのスタイルを変更するための、標準的な方法はありません。Safari では、スピンボタンがフィールドの外部にあることは知っておくべきです。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Opera
 ではスピンボタンが拡大されて、フィールドの内容物を隠す場合があります。
 |
| `border` | ✅ Yes | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Opera
 ではスピンボタンが拡大されて、フィールドの内容物を隠す場合があります。
 |
| *テキストとフォント* |
| --- |
| `color` | ✅ Yes | ✅ Yes |  |
| `font` | ✅ Yes | ✅ Yes | `line-height` の注意事項をご覧ください。 |
| `letter-spacing` | ✅ Yes | ✅ Yes |  |
| `text-align` | ✅ Yes | ✅ Yes |  |
| `text-decoration` | ⚠ Partial | ⚠ Partial |  |
| `text-indent` | ✅ Yes | ✅ Yes |  |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ⚠ Partial | ⚠ Partial |  |
| `text-transform` | N.A. | N.A. |  |
| *境界と背景* |
| --- |
| `background` | ❌ No | ❌ No | 対応していますが、ブラウザー間で頼りにできるほどの一貫性はありません。 |
| `border-radius` | ❌ No | ❌ No |
| `box-shadow` | ❌ No | ❌ No |

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

`checkbox` と `radio` 入力タイプを見てください。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ❌ No[1] | ❌ No[1] | 1. 一部のブラウザーでは、マージンを追加したりウィジェットを引き伸ばしたりします。
 |
| `height` | ❌ No[1] | ❌ No[1] | 1. 一部のブラウザーでは、マージンを追加したりウィジェットを引き伸ばしたりします。
 |
| `border` | ❌ No | ❌ No |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ❌ No | ❌ No |  |
| *テキストとフォント* |
| --- |
| `color` | N.A. | N.A. |  |
| `font` | N.A. | N.A. |  |
| `letter-spacing` | N.A. | N.A. |  |
| `text-align` | N.A. | N.A. |  |
| `text-decoration` | N.A. | N.A. |  |
| `text-indent` | N.A. | N.A. |  |
| `text-overflow` | N.A. | N.A. |  |
| `text-shadow` | N.A. | N.A. |  |
| `text-transform` | N.A. | N.A. |  |
| *境界と背景* |
| --- |
| `background` | ❌ No | ❌ No |  |
| `border-radius` | ❌ No | ❌ No |  |
| `box-shadow` | ❌ No | ❌ No |  |

### セレクトボックス (単一行)

``<select>``、 ``<optgroup>``、``<option>`` 要素を見てください。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ⚠ Partial[1] | ⚠ Partial[1] | 1. このプロパティは
 ``<select>``
 要素では問題ありませんが、
 ``<option>`` 要素や
 ``<optgroup>``
 要素では効果がありません。
 |
| `height` | ❌ No | ✅ Yes |  |
| `border` | ⚠ Partial | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ❌ No[1] | ⚠ Partial[2] | 1. プロパティは適用されますが、Mac OSX
 ではブラウザー間の一貫性がありません。
2. このプロパティは
 ``<select>``
 要素には適用されますが、
 ``<option>`` 要素や
 ``<optgroup>``
 要素では処理に一貫性がありません。
 |
| *テキストとフォント* |
| --- |
| `color` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Mac OSX では、WebKit
 ベースのブラウザーがネイティブウィジェットでこのプロパティに対応せず、また
 ``<option>`` 要素や
 ``<optgroup>`` 要素では Opera
 を含めてまったく対応していません。
 |
| `font` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Mac OSX では、WebKit
 ベースのブラウザーがネイティブウィジェットでこのプロパティに対応せず、また
 ``<option>`` 要素や
 ``<optgroup>`` 要素では Opera
 を含めてまったく対応していません。
 |
| `letter-spacing` | ⚠ Partial[1] | ⚠ Partial[1] | 1. IE9 は `<select>`、
 ``<option>`` および
 ``<optgroup>``
 要素でこのプロパティに対応していません。Mac OSX で WebKit
 ベースのブラウザーは、
 ``<option>`` 要素や
 ``<optgroup>``
 要素でこのプロパティに対応していません。
 |
| `text-align` | ❌ No[1] | ❌ No[1] | 1. Windows 7 で IE9 および Mac OSX で WebKit
 ベースのブラウザーは、このウィジェットで本プロパティに対応していません。
 |
| `text-decoration` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Firefox のみがこのプロパティを完全対応しています。 Opera
 はこのプロパティに全く対応しておらず、また他のブラウザーは
 ``<select>``
 要素のみに対応しています。
 |
| `text-indent` | ⚠ Partial[1][2] | ⚠ Partial[1][2] | 1. ほとんどのブラウザーは、``<select>``
 要素のみでこのプロパティに対応しています。
2. IE9 はこのプロパティに対応していません。
 |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ⚠ Partial[1][2] | ⚠ Partial[1][2] | 1. ほとんどのブラウザーは、``<select>``
 要素のみでこのプロパティに対応しています。
2. IE9 はこのプロパティに対応していません。
 |
| `text-transform` | ⚠ Partial[1] | ⚠ Partial[1] | 1. ほとんどのブラウザーは、``<select>``要素のみでこのプロパティに対応しています。
 |
| *境界と背景* |
| --- |
| `background` | ⚠ Partial[1] | ⚠ Partial[1] | 1. ほとんどのブラウザーは、``<select>``要素のみでこのプロパティに対応しています。
 |
| `border-radius` | ⚠ Partial[1] | ⚠ Partial[1] |
| `box-shadow` | ❌ No | ⚠ Partial[1] |

Firefox では``<select>`` 要素の下矢印を変更する方法はありません。.

### セレクトボックス (複数行)

``<select>``、``<optgroup>``、``<option>`` の各要素と`size` 属性を参照してください。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ✅ Yes | ✅ Yes |  |
| `border` | ✅ Yes | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Opera は `padding-top` および
 `padding-bottom` に
 ``<select>``
 要素で対応していません。
 |
| *テキストとフォント* |
| --- |
| `color` | ✅ Yes | ✅ Yes |  |
| `font` | ✅ Yes | ✅ Yes | `line-height` の注意事項をご覧ください。 |
| `letter-spacing` | ⚠ Partial[1] | ⚠ Partial[1] | 1. IE9 は ``<select>`` 、
 ``<option>`` および
 ``<optgroup>``
 要素でこのプロパティに対応していません。Mac OSX で WebKit
 ベースのブラウザーは、
 ``<option>`` 要素や
 ``<optgroup>``
 要素でこのプロパティに対応していません。
 |
| `text-align` | ❌ No[1] | ❌ No[1] | 1. Windows 7 で IE9 および Mac OSX で WebKit
 ベースのブラウザーは、このウィジェットで本プロパティに対応していません。
 |
| `text-decoration` | ❌ No[1] | ❌ No[1] | 1. Firefox および IE9 以上のみが対応しています。
 |
| `text-indent` | ❌ No | ❌ No |  |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ❌ No | ❌ No |  |
| `text-transform` | ⚠ Partial[1] | ⚠ Partial[1] | 1. ほとんどのブラウザーは、
 ``<select>``
 要素のみでこのプロパティに対応します。
 |
| *境界と背景* |
| --- |
| `background` | ✅ Yes | ✅ Yes |  |
| `border-radius` | ✅ Yes[1] | ✅ Yes[1] | 1. Opera では、明示的に境界が設定されている場合にのみ
 `border-radius` が適用されます。
 |
| `box-shadow` | ❌ No | ⚠ Partial[1] | 1. IE9 はこのプロパティに対応していません。
 |

### Datalist

``<datalist>`` and ``<input>`` 要素と `list` 属性を見てください。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ❌ No | ❌ No |  |
| `height` | ❌ No | ❌ No |  |
| `border` | ❌ No | ❌ No |  |
| `margin` | ❌ No | ❌ No |  |
| `padding` | ❌ No | ❌ No |  |
| *テキストとフォント* |
| --- |
| `color` | ❌ No | ❌ No |  |
| `font` | ❌ No | ❌ No |  |
| `letter-spacing` | ❌ No | ❌ No |  |
| `text-align` | ❌ No | ❌ No |  |
| `text-decoration` | ❌ No | ❌ No |  |
| `text-indent` | ❌ No | ❌ No |  |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ❌ No | ❌ No |  |
| `text-transform` | ❌ No | ❌ No |  |
| *境界と背景* |
| --- |
| `background` | ❌ No | ❌ No |  |
| `border-radius` | ❌ No | ❌ No |  |
| `box-shadow` | ❌ No | ❌ No |  |

### ファイルピッカー

`file` 入力タイプを見てください。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ❌ No | ❌ No |  |
| `height` | ❌ No | ❌ No |  |
| `border` | ❌ No | ❌ No |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ❌ No | ❌ No |  |
| *テキストとフォント* |
| --- |
| `color` | ✅ Yes | ✅ Yes |  |
| `font` | ❌ No[1] | ❌ No[1] | 1. 対応されていますが、ブラウザー間で頼りにできるほどの一貫性はありません。
 |
| `letter-spacing` | ⚠ Partial[1] | ⚠ Partial[1] | 1. ほとんどのブラウザーは、このプロパティを選択ボタンにも適用します。
 |
| `text-align` | ❌ No | ❌ No |  |
| `text-decoration` | ❌ No | ❌ No |  |
| `text-indent` | ⚠ Partial[1] | ⚠ Partial[1] | 1. ウィジェットの外側に左マージンがあるような状態になります。
 |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ❌ No | ❌ No |  |
| `text-transform` | ❌ No | ❌ No |  |
| *境界と背景* |
| --- |
| `background` | ❌ No[1] | ❌ No[1] | 1. 対応されていますが、ブラウザー間で頼りにできるほどの一貫性はありません。
 |
| `border-radius` | ❌ No | ❌ No |  |
| `box-shadow` | ❌ No | ⚠ Partial[1] | 1. IE9 はこのプロパティに対応していません。
 |

### 日付選択

`date` と `time` 入力タイプを見てください。多くのプロパティが対応されていますが、ブラウザー間で頼りにできるほどの一貫性はありません。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ❌ No | ❌ No |  |
| `height` | ❌ No | ❌ No |  |
| `border` | ❌ No | ❌ No |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ❌ No | ❌ No |  |
| *テキストとフォント* |
| --- |
| `color` | ❌ No | ❌ No |  |
| `font` | ❌ No | ❌ No |  |
| `letter-spacing` | ❌ No | ❌ No |  |
| `text-align` | ❌ No | ❌ No |  |
| `text-decoration` | ❌ No | ❌ No |  |
| `text-indent` | ❌ No | ❌ No |  |
| `text-overflow` | ❌ No | ❌ No |  |
| `text-shadow` | ❌ No | ❌ No |  |
| `text-transform` | ❌ No | ❌ No |  |
| *境界と背景* |
| --- |
| `background` | ❌ No | ❌ No |  |
| `border-radius` | ❌ No | ❌ No |  |
| `box-shadow` | ❌ No | ❌ No |  |

### カラーピッカー

`color` 入力タイプを見てください。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ❌ No[1] | ✅ Yes | 1. Opera はこのプロパティを、select
 ウィジェットと同じ制限事項のもとに処理します。
 |
| `border` | ✅ Yes | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ❌ No[1] | ✅ Yes | 1. Opera はこのプロパティを、select
 ウィジェットと同じ制限事項のもとに処理します。
 |
| *テキストとフォント* |
| --- |
| `color` | N.A. | N.A. |  |
| `font` | N.A. | N.A. |  |
| `letter-spacing` | N.A. | N.A. |  |
| `text-align` | N.A. | N.A. |  |
| `text-decoration` | N.A. | N.A. |  |
| `text-indent` | N.A. | N.A. |  |
| `text-overflow` | N.A. | N.A. |  |
| `text-shadow` | N.A. | N.A. |  |
| `text-transform` | N.A. | N.A. |  |
| *境界と背景* |
| --- |
| `background` | ❌ No[1] | ❌ No[1] | 1. 対応されていますが、ブラウザー間で頼りにできるほどの一貫性はありません。
 |
| `border-radius` | ❌ No[1] | ❌ No[1] |
| `box-shadow` | ❌ No[1] | ❌ No[1] |

### メーターとプログレスバー

``<meter>`` と ``<progress>`` 要素を見てください:

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ✅ Yes | ✅ Yes |  |
| `border` | ⚠ Partial | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ✅ Yes | ⚠ Partial[1] | 1. Chrome は調整された (Tweaked) 状態の要素に
 `padding`
 プロパティが適用されていると、`<progress>`
 要素や `<meter>` 要素を隠します。
 |
| *テキストとフォント* |
| --- |
| `color` | N.A. | N.A. |  |
| `font` | N.A. | N.A. |  |
| `letter-spacing` | N.A. | N.A. |  |
| `text-align` | N.A. | N.A. |  |
| `text-decoration` | N.A. | N.A. |  |
| `text-indent` | N.A. | N.A. |  |
| `text-overflow` | N.A. | N.A. |  |
| `text-shadow` | N.A. | N.A. |  |
| `text-transform` | N.A. | N.A. |  |
| *境界と背景* |
| --- |
| `background` | ❌ No[1] | ❌ No[1] | 1. 対応されていますが、ブラウザー間で頼りにできるほどの一貫性はありません。
 |
| `border-radius` | ❌ No[1] | ❌ No[1] |
| `box-shadow` | ❌ No[1] | ❌ No[1] |

### Range

`range` 入力タイプを見てください。スライダーのつまみのスタイルを変更するための標準的な方法はなく、また Opera ではウィジェットのデフォルトの表示を調整する方法がありません。

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ⚠ Partial[1] | ⚠ Partial[1] | 1. Chrome や Opera はウィジェットの周りに余白を追加します。また Windows
 7 の Opera では、スライダーのつまみが引き伸ばされます。
 |
| `border` | ❌ No | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ⚠ Partial[1] | ✅ Yes | 1. `padding`
 は適用されますが、視覚的な効果はありません。
 |
| *テキストとフォント* |
| --- |
| `color` | N.A. | N.A. |  |
| `font` | N.A. | N.A. |  |
| `letter-spacing` | N.A. | N.A. |  |
| `text-align` | N.A. | N.A. |  |
| `text-decoration` | N.A. | N.A. |  |
| `text-indent` | N.A. | N.A. |  |
| `text-overflow` | N.A. | N.A. |  |
| `text-shadow` | N.A. | N.A. |  |
| `text-transform` | N.A. | N.A. |  |
| *境界と背景* |
| --- |
| `background` | ❌ No[1] | ❌ No[1] | 1. 対応されていますが、ブラウザー間で頼りにできるほどの一貫性はありません。
 |
| `border-radius` | ❌ No[1] | ❌ No[1] |
| `box-shadow` | ❌ No[1] | ❌ No[1] |

### 画像ボタン

`image` 入力タイプを見てください:

| プロパティ | N | T | 備考 |
| --- | --- | --- | --- |
| *CSS ボックスモデル* |
| --- |
| `width` | ✅ Yes | ✅ Yes |  |
| `height` | ✅ Yes | ✅ Yes |  |
| `border` | ✅ Yes | ✅ Yes |  |
| `margin` | ✅ Yes | ✅ Yes |  |
| `padding` | ✅ Yes | ✅ Yes |  |
| *テキストとフォント* |
| --- |
| `color` | N.A. | N.A. |  |
| `font` | N.A. | N.A. |  |
| `letter-spacing` | N.A. | N.A. |  |
| `text-align` | N.A. | N.A. |  |
| `text-decoration` | N.A. | N.A. |  |
| `text-indent` | N.A. | N.A. |  |
| `text-overflow` | N.A. | N.A. |  |
| `text-shadow` | N.A. | N.A. |  |
| `text-transform` | N.A. | N.A. |  |
| *境界と背景* |
| --- |
| `background` | ✅ Yes | ✅ Yes |  |
| `border-radius` | ⚠ Partial[1] | ⚠ Partial[1] | 1. IE9 はこのプロパティに対応していません。
 |
| `box-shadow` | ⚠ Partial[1] | ⚠ Partial[1] | 1. IE9 はこのプロパティに対応していません。
 |

関連情報
----

### 学習経路

* 初めてのフォーム
* ウェブフォームの構築方法
* ネイティブのフォームウィジェット
* HTML5 の入力型
* 高度なフォームコントロール
* UI 擬似クラス
* ウェブフォームへのスタイル設定
* クライアントサイドのフォーム検証
* フォームデータの送信
### 高度なトピック

* JavaScript によるフォームの送信
* カスタムフォームウィジェットの作成方法
* 古いブラウザーでの HTML フォーム
* フォームへの高度なスタイル設定
* フォームコントロール向けの CSS プロパティの互換性一覧表


例 - ウェブ開発を学ぶ

例
=

これは記事 ウェブフォームの構築方法の基本的なお支払いフォームの例です。

お支払いフォーム
--------

### HTML コンテンツ

```html
<form method="post">
  <h1>お支払いフォーム</h1>
  <p>
    必須のフィールドには
    <strong><span aria-label="required">*</span></strong> が付いています。
  </p>
  <section>
    <h2>連絡先情報</h2>
    <fieldset>
      <legend>肩書</legend>
      <ul>
        <li>
          <label for="title\_1">
            <input type="radio" id="title\_1" name="title" value="A" />
            エース
          </label>
        </li>
        <li>
          <label for="title\_2">
            <input type="radio" id="title\_2" name="title" value="K" />
            キング
          </label>
        </li>
        <li>
          <label for="title\_3">
            <input type="radio" id="title\_3" name="title" value="Q" />
            クイーン
          </label>
        </li>
      </ul>
    </fieldset>
    <p>
      <label for="name">
        <span>氏名: </span>
        <strong><span aria-label="required">*</span></strong>
      </label>
      <input type="text" id="name" name="username" />
    </p>
    <p>
      <label for="mail">
        <span>メールアドレス: </span>
        <strong><span aria-label="required">*</span></strong>
      </label>
      <input type="email" id="mail" name="usermail" />
    </p>
    <p>
      <label for="pwd">
        <span>パスワード: </span>
        <strong><span aria-label="required">*</span></strong>
      </label>
      <input type="password" id="pwd" name="password" />
    </p>
  </section>
  <section>
    <h2>お支払い情報</h2>
    <p>
      <label for="card">
        <span>カードの種類:</span>
      </label>
      <select id="card" name="usercard">
        <option value="visa">Visa</option>
        <option value="mc">Mastercard</option>
        <option value="amex">American Express</option>
      </select>
    </p>
    <p>
      <label for="number">
        <span>カード番号:</span>
        <strong><span aria-label="required">*</span></strong>
      </label>
      <input type="tel" id="number" name="cardnumber" />
    </p>
    <p>
      <label for="expiration">
        <span>有効期限:</span>
        <strong><span aria-label="required">*</span></strong>
      </label>
      <input
 type="text"
 id="expiration"
 required="true"
 placeholder="MM/YY"
 pattern="^(0[1-9]|1[0-2])\/([0-9]{2})$" />
    </p>
  </section>
  <section>
    <p><button type="submit">お支払いを検証</button></p>
  </section>
</form>
```

### CSS コンテンツ

```css
h1 {
  margin-top: 0;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

form {
  margin: 0 auto;
  width: 400px;
  padding: 1em;
  border: 1px solid #ccc;
  border-radius: 1em;
}

div + div {
  margin-top: 1em;
}

label span {
  display: inline-block;
  width: 120px;
  text-align: right;
}

input,
textarea {
  font: 1em sans-serif;
  width: 250px;
  box-sizing: border-box;
  border: 1px solid #999;
}

input[type="checkbox"],
input[type="radio"] {
  width: auto;
  border: none;
}

input:focus,
textarea:focus {
  border-color: #000;
}

textarea {
  vertical-align: top;
  height: 5em;
  resize: vertical;
}

fieldset {
  width: 250px;
  box-sizing: border-box;
  margin-left: 136px;
  border: 1px solid #999;
}

button {
  margin: 20px 0 0 124px;
}

label {
  position: relative;
}

label em {
  position: absolute;
  right: 5px;
  top: 20px;
}
```

### 結果


例 1 - ウェブ開発を学ぶ

例 1
===

これは、カスタムフォームウィジェットの作成方法を説明する最初のコード例です。

Basic state
-----------

### HTML

```html
<div class="select">
  <span class="value">Cherry</span>
  <ul class="optList hidden">
    <li class="option">Cherry</li>
    <li class="option">Lemon</li>
    <li class="option">Banana</li>
    <li class="option">Strawberry</li>
    <li class="option">Apple</li>
  </ul>
</div>
```

### CSS

```css
/\* --------------- \*/
/\* Required Styles \*/
/\* --------------- \*/

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline: none;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

/\* ------------ \*/
/\* Fancy Styles \*/
/\* ------------ \*/

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select:after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

### Result for basic state

Active state
------------

### HTML

```html
<div class="select active">
  <span class="value">Cherry</span>
  <ul class="optList hidden">
    <li class="option">Cherry</li>
    <li class="option">Lemon</li>
    <li class="option">Banana</li>
    <li class="option">Strawberry</li>
    <li class="option">Apple</li>
  </ul>
</div>
```

### CSS

```css
/\* --------------- \*/
/\* Required Styles \*/
/\* --------------- \*/

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline: none;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

/\* ------------ \*/
/\* Fancy Styles \*/
/\* ------------ \*/

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select:after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

### Result for active state

Open state
----------

### HTML

```html
<div class="select active">
  <span class="value">Cherry</span>
  <ul class="optList">
    <li class="option highlight">Cherry</li>
    <li class="option">Lemon</li>
    <li class="option">Banana</li>
    <li class="option">Strawberry</li>
    <li class="option">Apple</li>
  </ul>
</div>
```

### CSS

```css
/\* --------------- \*/
/\* Required Styles \*/
/\* --------------- \*/

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline: none;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

/\* ------------ \*/
/\* Fancy Styles \*/
/\* ------------ \*/

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select:after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #fff;
}
```

### Result for open state


例 5 - ウェブ開発を学ぶ

例 5
===

これが、カスタムフォームウィジェットの作成方法を説明する最後の例です。

状態を変更する
-------

### HTML コンテンツ

```html
<form class="no-widget">
  <select name="myFruit">
    <option>Cherry</option>
    <option>Lemon</option>
    <option>Banana</option>
    <option>Strawberry</option>
    <option>Apple</option>
  </select>

  <div class="select" role="listbox">
    <span class="value">Cherry</span>
    <ul class="optList hidden" role="presentation">
      <li class="option" role="option" aria-selected="true">Cherry</li>
      <li class="option" role="option">Lemon</li>
      <li class="option" role="option">Banana</li>
      <li class="option" role="option">Strawberry</li>
      <li class="option" role="option">Apple</li>
    </ul>
  </div>
</form>
```

### CSS コンテンツ

```css
.widget select,
.no-widget .select {
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}

/\* --------------- \*/
/\* Required Styles \*/
/\* --------------- \*/

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline: none;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

/\* ------------ \*/
/\* Fancy Styles \*/
/\* ------------ \*/

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select:after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

### JavaScript コンテンツ

```js
// -------------------- //
// Function definitions //
// -------------------- //

function deactivateSelect(select) {
  if (!select.classList.contains("active")) return;

  const optList = select.querySelector(".optList");

  optList.classList.add("hidden");
  select.classList.remove("active");
}

function activeSelect(select, selectList) {
  if (select.classList.contains("active")) return;

  selectList.forEach(deactivateSelect);
  select.classList.add("active");
}

function toggleOptList(select, show) {
  const optList = select.querySelector(".optList");

  optList.classList.toggle("hidden");
}

function highlightOption(select, option) {
  const optionList = select.querySelectorAll(".option");

  optionList.forEach((other) => {
    other.classList.remove("highlight");
  });

  option.classList.add("highlight");
}

function updateValue(select, index) {
  const nativeWidget = select.previousElementSibling;
  const value = select.querySelector(".value");
  const optionList = select.querySelectorAll(".option");

  optionList.forEach((other) => {
    other.setAttribute("aria-selected", "false");
  });

  optionList[index].setAttribute("aria-selected", "true");

  nativeWidget.selectedIndex = index;
  value.innerHTML = optionList[index].innerHTML;
  highlightOption(select, optionList[index]);
}

function getIndex(select) {
  const nativeWidget = select.previousElementSibling;

  return nativeWidget.selectedIndex;
}

// ------------- //
// Event binding //
// ------------- //

window.addEventListener("load", () => {
  const form = document.querySelector("form");

  form.classList.remove("no-widget");
  form.classList.add("widget");
});

window.addEventListener("load", () => {
  const selectList = document.querySelectorAll(".select");

  selectList.forEach((select) => {
    const optionList = select.querySelectorAll(".option");
    const selectedIndex = getIndex(select);

    select.tabIndex = 0;
    select.previousElementSibling.tabIndex = -1;

    updateValue(select, selectedIndex);

    optionList.forEach((option, index) => {
      option.addEventListener("mouseover", () => {
        highlightOption(select, option);
      });

      option.addEventListener("click", (event) => {
        updateValue(select, index);
      });
    });

    select.addEventListener("click", (event) => {
      toggleOptList(select);
    });

    select.addEventListener("focus", (event) => {
      activeSelect(select, selectList);
    });

    select.addEventListener("blur", (event) => {
      deactivateSelect(select);
    });

    select.addEventListener("keyup", (event) => {
      let index = getIndex(select);

      if (event.keyCode === 27) {
        deactivateSelect(select);
      }
      if (event.keyCode === 40 && index < optionList.length - 1) {
        index++;
      }
      if (event.keyCode === 38 && index > 0) {
        index--;
      }

      updateValue(select, index);
    });
  });
});
```

### 結果


例 4 - ウェブ開発を学ぶ

例 4
===

これは、カスタムフォームウィジェットの作成方法を説明する 4 番目の例です。

状態を変更する
-------

### HTML コンテンツ

```html
<form class="no-widget">
  <select name="myFruit">
    <option>Cherry</option>
    <option>Lemon</option>
    <option>Banana</option>
    <option>Strawberry</option>
    <option>Apple</option>
  </select>

  <div class="select">
    <span class="value">Cherry</span>
    <ul class="optList hidden">
      <li class="option">Cherry</li>
      <li class="option">Lemon</li>
      <li class="option">Banana</li>
      <li class="option">Strawberry</li>
      <li class="option">Apple</li>
    </ul>
  </div>
</form>
```

### CSS コンテンツ

```css
.widget select,
.no-widget .select {
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}

/\* --------------- \*/
/\* Required Styles \*/
/\* --------------- \*/

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline: none;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

/\* ------------ \*/
/\* Fancy Styles \*/
/\* ------------ \*/

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select:after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

### JavaScript コンテンツ

```js
// -------------------- //
// Function definitions //
// -------------------- //

function deactivateSelect(select) {
  if (!select.classList.contains("active")) return;

  const optList = select.querySelector(".optList");

  optList.classList.add("hidden");
  select.classList.remove("active");
}

function activeSelect(select, selectList) {
  if (select.classList.contains("active")) return;

  selectList.forEach(deactivateSelect);
  select.classList.add("active");
}

function toggleOptList(select, show) {
  const optList = select.querySelector(".optList");

  optList.classList.toggle("hidden");
}

function highlightOption(select, option) {
  const optionList = select.querySelectorAll(".option");

  optionList.forEach((other) => {
    other.classList.remove("highlight");
  });

  option.classList.add("highlight");
}

function updateValue(select, index) {
  const nativeWidget = select.previousElementSibling;
  const value = select.querySelector(".value");
  const optionList = select.querySelectorAll(".option");

  nativeWidget.selectedIndex = index;
  value.innerHTML = optionList[index].innerHTML;
  highlightOption(select, optionList[index]);
}

function getIndex(select) {
  const nativeWidget = select.previousElementSibling;

  return nativeWidget.selectedIndex;
}

// ------------- //
// Event binding //
// ------------- //

window.addEventListener("load", () => {
  const form = document.querySelector("form");

  form.classList.remove("no-widget");
  form.classList.add("widget");
});

window.addEventListener("load", () => {
  const selectList = document.querySelectorAll(".select");

  selectList.forEach((select) => {
    const optionList = select.querySelectorAll(".option");

    optionList.forEach((option) => {
      option.addEventListener("mouseover", () => {
        highlightOption(select, option);
      });
    });

    select.addEventListener("click", (event) => {
      toggleOptList(select);
    });

    select.addEventListener("focus", (event) => {
      activeSelect(select, selectList);
    });

    select.addEventListener("blur", (event) => {
      deactivateSelect(select);
    });
  });
});

window.addEventListener("load", () => {
  const selectList = document.querySelectorAll(".select");

  selectList.forEach((select) => {
    const optionList = select.querySelectorAll(".option");
    const selectedIndex = getIndex(select);

    select.tabIndex = 0;
    select.previousElementSibling.tabIndex = -1;

    updateValue(select, selectedIndex);

    optionList.forEach((option, index) => {
      option.addEventListener("click", (event) => {
        updateValue(select, index);
      });
    });

    select.addEventListener("keyup", (event) => {
      let index = getIndex(select);

      if (event.keyCode === 27) {
        deactivateSelect(select);
      }
      if (event.keyCode === 40 && index < optionList.length - 1) {
        index++;
      }
      if (event.keyCode === 38 && index > 0) {
        index--;
      }

      updateValue(select, index);
    });
  });
});
```

### 結果


例 2 - ウェブ開発を学ぶ

例 2
===

これは、カスタムフォームウィジェットの作成方法を説明する 2 番目の例です。

JS
--

### HTML コンテンツ

```html
<form class="no-widget">
  <select name="myFruit">
    <option>Cherry</option>
    <option>Lemon</option>
    <option>Banana</option>
    <option>Strawberry</option>
    <option>Apple</option>
  </select>

  <div class="select">
    <span class="value">Cherry</span>
    <ul class="optList hidden">
      <li class="option">Cherry</li>
      <li class="option">Lemon</li>
      <li class="option">Banana</li>
      <li class="option">Strawberry</li>
      <li class="option">Apple</li>
    </ul>
  </div>
</form>
```

### CSS コンテンツ

```css
.widget select,
.no-widget .select {
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}

/\* --------------- \*/
/\* Required Styles \*/
/\* --------------- \*/

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline: none;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

/\* ------------ \*/
/\* Fancy Styles \*/
/\* ------------ \*/

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select:after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

### JavaScript コンテンツ

```js
window.addEventListener("load", () => {
  const form = document.querySelector("form");

  form.classList.remove("no-widget");
  form.classList.add("widget");
});
```

### JS の結果

No JS
-----

### HTML コンテンツ

```html
<form class="no-widget">
  <select name="myFruit">
    <option>Cherry</option>
    <option>Lemon</option>
    <option>Banana</option>
    <option>Strawberry</option>
    <option>Apple</option>
  </select>

  <div class="select">
    <span class="value">Cherry</span>
    <ul class="optList hidden">
      <li class="option">Cherry</li>
      <li class="option">Lemon</li>
      <li class="option">Banana</li>
      <li class="option">Strawberry</li>
      <li class="option">Apple</li>
    </ul>
  </div>
</form>
```

### CSS コンテンツ

```css
.widget select,
.no-widget .select {
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}
```

### No JS の結果


例 3 - ウェブ開発を学ぶ

例 3
===

これは、カスタムフォームウィジェットの作成方法を説明する 3 番目の例です。

状態を変更する
-------

### HTML コンテンツ

```html
<form class="no-widget">
  <select name="myFruit" tabindex="-1">
    <option>Cherry</option>
    <option>Lemon</option>
    <option>Banana</option>
    <option>Strawberry</option>
    <option>Apple</option>
  </select>

  <div class="select" tabindex="0">
    <span class="value">Cherry</span>
    <ul class="optList hidden">
      <li class="option">Cherry</li>
      <li class="option">Lemon</li>
      <li class="option">Banana</li>
      <li class="option">Strawberry</li>
      <li class="option">Apple</li>
    </ul>
  </div>
</form>
```

### CSS コンテンツ

```css
.widget select,
.no-widget .select {
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}

/\* --------------- \*/
/\* Required Styles \*/
/\* --------------- \*/

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline: none;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

/\* ------------ \*/
/\* Fancy Styles \*/
/\* ------------ \*/

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select:after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

### JavaScript コンテンツ

```js
// -------------------- //
// Function definitions //
// -------------------- //

function deactivateSelect(select) {
  if (!select.classList.contains("active")) return;

  const optList = select.querySelector(".optList");

  optList.classList.add("hidden");
  select.classList.remove("active");
}

function activeSelect(select, selectList) {
  if (select.classList.contains("active")) return;

  selectList.forEach(deactivateSelect);
  select.classList.add("active");
}

function toggleOptList(select, show) {
  const optList = select.querySelector(".optList");

  optList.classList.toggle("hidden");
}

function highlightOption(select, option) {
  const optionList = select.querySelectorAll(".option");

  optionList.forEach((other) => {
    other.classList.remove("highlight");
  });

  option.classList.add("highlight");
}

// ------------- //
// Event binding //
// ------------- //

window.addEventListener("load", () => {
  const form = document.querySelector("form");

  form.classList.remove("no-widget");
  form.classList.add("widget");
});

window.addEventListener("load", () => {
  const selectList = document.querySelectorAll(".select");

  selectList.forEach((select) => {
    const optionList = select.querySelectorAll(".option");

    optionList.forEach((option) => {
      option.addEventListener("mouseover", () => {
        highlightOption(select, option);
      });
    });

    select.addEventListener(
      "click",
      (event) => {
        toggleOptList(select);
      },
      false,
    );

    select.addEventListener("focus", (event) => {
      activeSelect(select, selectList);
    });

    select.addEventListener("blur", (event) => {
      deactivateSelect(select);
    });

    select.addEventListener("keyup", (event) => {
      if (event.keyCode === 27) {
        deactivateSelect(select);
      }
    });
  });
});
```

### 結果