CSSで、入力欄の中にアイコンを置く方法

​​CSS​HTML,Playwright,アクセシビリティ

検索欄の左端に虫めがねのアイコンを置くには、入力欄とアイコンを1つの要素で包み、アイコンを position: absolute で入力欄の上に重ねます。入力欄にはアイコンの幅の分だけ padding を足し、アイコンには pointer-events: none を付けます。こうすると、文字がアイコンに重ならず、アイコンの上をクリックしても入力欄にフォーカスが移ります。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS のページを Playwright で開き、getBoundingClientRect() で位置と大きさを、document.activeElement でフォーカスの移り先を記録しました。Firefox と Safari、スマートフォンの実機、実際のスクリーンリーダーでの読み上げは試していません。

上下に2つの検索欄が並んだ画面。上は「ふだんの表示」で、灰色の枠の入力欄の左端に灰色の虫めがねのアイコンがあり、その右に灰色の「キーワード」という文字が出ている。下は「フォーカス中(文字を入力したところ)」で、入力欄の枠が水色になり、虫めがねのアイコンも濃い水色に変わっている。入力欄にはアイコンの右側から「猫の手」と入っていて、右端には Chromium が表示した消去ボタン(×)がある
この記事の CSS で作った検索欄。上がふだんの表示、下がフォーカスして文字を入力したところ

HTML は、アイコンと入力欄を1つの要素で包む

MDN の <input> のページにあるとおり、<input> は中身を持てない空要素です。そのため、アイコンの SVG と入力欄を <div class="field"> で包みます。ラベルは <label for> で入力欄と結びつけます。

html
<label for="q" class="label">記事を検索</label>
<div class="field">
  <svg class="field-icon" viewBox="0 0 24 24" aria-hidden="true">
    <circle cx="11" cy="11" r="7" fill="none" stroke="currentColor" stroke-width="2"/>
    <path d="M16.5 16.5 21 21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
  </svg>
  <input id="q" type="search" placeholder="キーワード">
</div>

アイコンは飾りなので、aria-hidden="true" を付けています。MDN の aria-hidden のページにあるとおり、この属性を付けた要素は、子要素も含めてアクセシビリティツリーから除かれます。検証環境で Playwright の ariaSnapshot() を取ると、出てきたのは「記事を検索」の文字と、名前が「記事を検索」の searchbox だけで、SVG は入っていませんでした。

CSS は、包む要素を基準にしてアイコンを重ねる

css
.field {
  position: relative;
  max-width: 320px;
  color: #6b7a82;
}

.field-icon {
  position: absolute;
  inset-inline-start: 12px;
  top: 50%;
  translate: 0 -50%;
  width: 20px;
  height: 20px;
  pointer-events: none;
}

.field input {
  box-sizing: border-box;
  width: 100%;
  height: 44px;
  padding-inline: 44px 12px;
  border: 1px solid #9aa5ab;
  border-radius: 8px;
  color: #1f2a30;
  font: inherit;
  font-size: 16px;
}

.field:focus-within {
  color: #067a98;
}

.field input:focus {
  border-color: #00c8e8;
  outline: 2px solid #00c8e8;
  outline-offset: -1px;
}

.field に position: relative を付けると、MDN の position のページにあるとおり、position: absolute のアイコンは .field を基準に置かれます。.field の中で場所をとるのは入力欄だけなので、検証環境で測った .field の高さは、入力欄と同じ 44px でした。

縦の位置は、top: 50% でアイコンの上端を真ん中まで下げ、translate: 0 -50% でアイコンの高さの半分だけ戻して合わせます。MDN の translate プロパティのページによると、translate プロパティは translate() 関数と同じ値をとります。translate() 関数のページの説明では、縦の移動量をパーセントで書くと、参照ボックスの高さに対する割合になります。検証環境で測ると、入力欄の上端は 46px、アイコンの上端は 58px でした。top: 50% で 22px 下がり、そこからアイコンの高さ 20px の半分の 10px だけ上に戻った位置です。アイコンの左端は入力欄の左端から 12px の位置にあり、アイコンと入力欄の縦の中心のずれは 0px でした。

入力欄の padding で、アイコンの分を空ける

アイコンは入力欄の上に重ねているだけなので、入力欄の文字は、何もしないとアイコンの下から始まります。そこで、入力欄の左の padding を、アイコンの左の余白 12px とアイコンの幅 20px に、文字との間の 12px を足した 44px にしています。

上下に2つの入力欄が並んだ画面。上の「padding-inline: 12px(アイコンの分を空けない)」では、「猫の手で検索する」の最初の「猫」の字に、虫めがねのアイコンが重なっている。下の「padding-inline: 44px 12px(この記事の CSS)」では、アイコンの右側から「猫の手で検索する」が始まり、重なっていない
左の padding を、アイコンの分だけ空けない場合(上)と空けた場合(下)

検証環境では、アイコンの右端は 56px の位置にありました。入力欄の文字の入る部分の左端は、入力欄の左端 24px に、境界の 1px と padding の 44px を足した 69px です。アイコンと文字の間は 13px で、入力欄の境界の 1px の分だけ、狙った 12px より 1px 広くなります。

アイコンに pointer-events: none を付けて、押しても入力できるようにする

アイコンは入力欄の上に重なっているので、そのままではアイコンの上をクリックすると、クリックされるのはアイコンの SVG です。入力欄にはフォーカスが移りません。

上下に2つの検索欄があり、矢印のカーソルが動いてアイコンをクリックする様子。最初は右上に「クリック 0回目/2回」と出ている。「クリック 1回目/2回」で、上の「pointer-events: none あり(この記事の CSS)」の欄の虫めがねをクリックすると、その欄の枠が水色になり、下の「フォーカスがある要素」が「input#c1」になる。続いて「クリック 2回目/2回」で、下の「pointer-events: none なし」の欄の虫めがねをクリックすると、上の欄の枠は灰色に戻り、下の欄の枠も灰色のままで、「フォーカスがある要素」は「body」になる。クリックした瞬間に、回数と要素の名前がピンクになる
アイコンの上をクリックしたときの動き。上の欄は入力欄にフォーカスが移り、下の欄は移らない

MDN の pointer-events のページにあるとおり、none にした要素はポインターイベントの対象にならず、その下にある要素が対象になります。検証環境で、アイコンの中心の位置にある一番上の要素を document.elementFromPoint() で調べたところ、pointer-events: none を付けた欄では入力欄、付けていない欄では SVG でした。上の欄、下の欄の順にアイコンの中心をクリックすると、上の欄では入力欄にフォーカスが移りました。下の欄では、上の欄の入力欄からフォーカスが外れ、document.activeElement は body になりました。

フォーカスしたときにアイコンの色を変える

アイコンの SVG は、線の色を stroke="currentColor" にしています。MDN の色の値のページにある currentColor の説明のとおり、currentColor はその要素の color の値を表します。SVG は .field の color を受け継ぐので、.field の color を変えればアイコンの色が変わります。

.field:focus-within は、MDN の :focus-within のページにあるとおり、要素自身か子孫にフォーカスがあるときに当てはまります。検証環境では、アイコンの color はフォーカスの前が rgb(107, 122, 130)(#6b7a82)で、入力欄にフォーカスすると rgb(6, 122, 152)(#067a98)に変わりました。入力欄の文字の色は、.field input で別に #1f2a30 を指定しているので、アイコンと一緒には変わりません。

右から左に書く言語でも、アイコンを書き始めの側に置く

アイコンの位置は left ではなく inset-inline-start で、入力欄の余白は padding-left ではなく padding-inline で書いています。どちらも、MDN の inset-inline-start のページとpadding-inline-start のページにあるとおり、文字を書き始める側(行の先頭の側)を指す書き方です。日本語や英語のページでは左、direction が rtl のページでは右になります。

<body dir="rtl"> のページに同じ CSS を当てて測ると、アイコンは入力欄の右端から 12px の位置に置かれ、入力欄の padding は左が 12px、右が 44px になりました。CSS を書き換えずに、アイコンと余白が右側に移っています。

背景画像で置くと、HTML は増えないが color でアイコンの色を変えられない

包む要素を足さずに、入力欄の背景画像としてアイコンを出す書き方もあります。MDN の background-position のページにあるとおり、キーワードの後に書いた長さは、そのキーワードの辺からのずれになります。left 12px center と書くと、アイコンは左の辺から 12px、縦の真ん中に置かれます。

css
.bg-input {
  box-sizing: border-box;
  width: 320px;
  height: 44px;
  padding-inline: 44px 12px;
  border: 1px solid #9aa5ab;
  border-radius: 8px;
  font-size: 16px;
  color: #067a98;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='11' cy='11' r='7' fill='none' stroke='currentColor' stroke-width='2'/%3E%3Cpath d='M16.5 16.5 21 21' stroke='currentColor' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E")
    no-repeat left 12px center / 20px 20px;
}

背景は入力欄そのものの一部なので、検証環境でアイコンの位置をクリックすると、pointer-events を指定しなくても入力欄にフォーカスが移りました。

ただし、SVG の中の currentColor は、入力欄の color を受け継ぎません。入力欄の color を #067a98 にして、アイコンの部分(20×20px)の色を数えたところ、白以外で一番多かった色は黒の rgb(0, 0, 0) でした。アイコンの色を color で変えたいときは、この記事のように SVG を HTML に書いて重ねます。

​Posted by 86