CSSで、入力欄の中にアイコンを置く方法
検索欄の左端に虫めがねのアイコンを置くには、入力欄とアイコンを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、スマートフォンの実機、実際のスクリーンリーダーでの読み上げは試していません。

HTML は、アイコンと入力欄を1つの要素で包む
MDN の <input> のページにあるとおり、<input> は中身を持てない空要素です。そのため、アイコンの SVG と入力欄を <div class="field"> で包みます。ラベルは <label for> で入力欄と結びつけます。
<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 は、包む要素を基準にしてアイコンを重ねる
.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 にしています。

検証環境では、アイコンの右端は 56px の位置にありました。入力欄の文字の入る部分の左端は、入力欄の左端 24px に、境界の 1px と padding の 44px を足した 69px です。アイコンと文字の間は 13px で、入力欄の境界の 1px の分だけ、狙った 12px より 1px 広くなります。
アイコンに pointer-events: none を付けて、押しても入力できるようにする
アイコンは入力欄の上に重なっているので、そのままではアイコンの上をクリックすると、クリックされるのはアイコンの SVG です。入力欄にはフォーカスが移りません。

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、縦の真ん中に置かれます。
.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 に書いて重ねます。





