📄

label で input[type="file"] を装飾するな

2022/02/28に公開
2

input[type="file"] な要素を装飾する方法を検索したときに、次のような間違った label 要素の使い方を紹介する記事ばかりヒットするのが気になったので書きました。label 要素を使っても問題ない場合もありますが、間違った使い方をしている(しようとしている)人に届きやすいようにこのような表題にしています。

<label>
  ファイルを選択
  <input type="file" />
</label>
label {
  /* 装飾 */
}

input {
  display: none;
}

何が問題か

上記の方法は input 要素を display: none で非表示にし、label 要素を使ってヒット領域を拡大させて「ファイルを選択」ボタンを表現するという方法ですが、この方法ではフォーカスが当たりません。また、tabindex 属性に非負の値を設定しフォーカスを当てられるようにしても label は clickable な要素ではないので Space / Enter キーで click イベントを発火することができません

どうするべきか

button 要素を使い、クリック時に input 要素の click イベントを発火させる方法が使えます。この方法は、アクセシビリティ向上に注力している[1] Twitter や、React でファイルの入力を扱うためのライブラリである react-dropzone などでも使われており、MDN の記事でも紹介されています。他にも方法はありますが、本質ではないので本記事では取り扱いません。

<button>ファイルを選択</button>
<input type="file" />
document.querySelector("button").addEventListener("click", () => {
  document.querySelector("input").click();
});
button {
  /* 装飾 */
}

input {
  display: none;
}

button 要素を使うのが適切でない場合は、次のようにアクセシビリティに配慮したボタンを作りましょう。

<div role="button" tabindex="0">ファイルを選択</div>
<input type="file" />
const buttonElement = document.querySelector("div");

buttonElement.addEventListener("click", () => {
  document.querySelector("input").click();
});

// Space / Enter キーで click イベントを発火できるようにする
buttonElement.addEventListener("keydown", (event) => {
  if (!buttonElement.isEqualNode(event.target)) {
    return;
  }

  if (event.keyCode === 32 || event.keyCode === 13) {
    event.preventDefault();
    document.querySelector("input").click();
  }
});
div {
  /* 装飾 */
}

input {
  display: none;
}

参考

脚注
  1. Twitter のアクセシビリティ機能 ↩︎

GitHubで編集を提案

Discussion

f-yanf-yan

コードの例だとinput[type="file"]で使用できるドラッグ&ドロップによるファイル選択も使えなくなりますね。

やはり、「label で input[type="file"] を装飾するな」ですかねー