selectのデザインをリストまでCSSで整える|base-selectの使い方
selectを開いたときのリストは、これまでCSSで装飾できませんでした。整えられるのは閉じた状態だけで、リストまでデザインどおりにしたいときは、JavaScriptで独自のセレクトを作るしかありませんでした。
閉じた状態をCSSで整える従来の方法は、こちらの記事で紹介しています。

2026年9月にSafariが対応したことで、iPhoneを含む主要な環境で、appearance: base-select を使って開いたリストまでCSSで装飾できるようになりました。この記事では、その使い方を、PC・スマホの実機で確認した結果をもとに解説し、実務でそのまま使えるデザインのデモを紹介します。
まずはデモ:開いたリストまでCSSで装飾できる
selectの開いたリストまで、こんなふうにデザインできるようになりました。下のselectを開いてみてください。
その入口になるのが、appearance: base-select です。select と ::picker(select) の両方に指定すると、これまでOSが描いていた開いたリストにも、CSSが効くようになります。
select,
::picker(select) {
appearance: base-select;
}この指定だけでは、見た目はブラウザ標準のシンプルな形のままです。上のデモは、この指定を出発点に、枠線・余白・矢印・チェックマークなどの装飾を足して作っています。デモのコードと作り方は、「基本の使い方」の章で紹介します。
対応状況は次のとおりです(2026年10月時点)。
| ブラウザ | 対応 |
|---|---|
| Chrome・Edge | 135(2025年4月)以降 |
| Safari(Mac・iPhone・iPad) | 27.0(2026年9月)以降 |
| Firefox | 未対応 |
非対応のブラウザでは指定が無視され、普通のselectとして表示されます。操作できなくなることはありません。
スマホでも、PCと同じように効きます。iPhoneでもAndroidでも、ホイール型やシート型のUIではなく、ページ内にドロップダウンが開くので、スマホでもデザインどおりのリストを見せられます。
一方で、multiple 属性や size 属性が付いたselect(一覧表示のリスト)は、ブラウザごとの挙動がそろっておらず、まだ実用には厳しい状況です。
- Chrome・Edge:装飾は効くが、一覧表示の高さが
sizeの行数どおりにならない - Safari(Mac):装飾が効かず、従来のリストのまま
- iPhone:一覧表示にならず、タップするとOS標準の選択画面が出る
複数選択のリストは、当面は従来どおりの実装にしておくのが無難です。
appearance: base-selectとは
appearance: base-select は、selectの描画を、OS標準の部品からブラウザが描くHTMLとCSSに切り替える指定です。切り替えると、これまで触れなかった部分に、疑似要素や要素を通してCSSを当てられるようになります。

上が閉じた状態、下が開いたリストです。点線は、目に見える枠ではなく、要素の範囲を示しています。
これまでとの違いは、次の3つです。
- 開いたリストの背景、枠線、角丸、影を指定できる
- 矢印やチェックマークを、
contentやrotateで変えられる - optionの中に、画像や
<span>などの要素を置ける
select自体は本物のselectのままなので、キーボード操作、読み上げ、フォーム送信は、ブラウザ標準の動きがそのまま使えます。
基本の使い方
冒頭のデモのコードを、そのまま紹介します。ラッパー要素は使わず、select 1つで作っています。まずコード全体を示し、そのあとでポイントを解説します。
コード全体
HTMLは、普通のselectに <button><selectedcontent></selectedcontent></button> を1行足しただけです(ポイント7で説明します)。
<select class="select" name="category">
<!-- 閉じた状態に、選択中の項目を表示する(長い文字の省略に使う) -->
<button><selectedcontent></selectedcontent></button>
<option value="">商品カテゴリを選択してください</option>
<option value="living">リビング・ダイニング用品</option>
<option value="bath">バス・サニタリー用品</option>
<option value="kitchen">キッチン・調理用品</option>
<option value="storage">収納・整理グッズ</option>
<option value="outdoor">アウトドア・レジャー用品</option>
</select>CSSは、すべてのブラウザ向けの「土台」と、対応ブラウザだけに適用する @supports の中身の2つに分かれています。
/* ===== 土台:すべてのブラウザ ===== */
/* 閉じた状態の箱。矢印は背景画像で描き、右側にそのぶんの余白を空ける */
.select {
appearance: none;
display: block;
width: 100%;
max-width: 500px;
height: 75px;
padding: 0 72px 0 28px;
border: 1.5px solid #d8d1fa;
border-radius: 14px;
background:
url("arrow.svg") right 18px center / 36px no-repeat,
linear-gradient(180deg, #fff, #f5f2ff);
box-shadow: 0 6px 18px rgba(108, 92, 231, 0.14);
color: #2a2640;
font: inherit;
font-size: 22px;
font-weight: 600;
letter-spacing: 0.4px;
cursor: pointer;
transition: border-color 0.2s, box-shadow 0.2s;
}
/* スマホでは、箱と矢印をひと回り小さくする */
@media (max-width: 767px) {
.select {
height: 55px;
padding: 0 56px 0 20px;
background-position: right 14px center, 0 0;
background-size: 28px, auto;
font-size: 16px;
}
}
.select:hover {
border-color: #b9adf8;
box-shadow: 0 8px 24px rgba(108, 92, 231, 0.2);
}
/* キーボード操作中は、枠とリングで強調する */
.select:focus-visible {
border-color: #7b6cf6;
box-shadow: 0 0 0 4px rgba(123, 108, 246, 0.18);
outline: none;
}
/* ===== 対応ブラウザだけ ===== */
@supports (appearance: base-select) {
/* 閉じた状態と開いたリストの両方を、CSSで装飾できるようにする */
.select,
.select::picker(select) {
appearance: base-select;
}
/* 矢印は ::picker-icon で描き直すので、背景から外し、右の余白も戻す */
.select {
display: flex;
align-items: center;
padding-right: 0;
background: linear-gradient(180deg, #fff, #f5f2ff);
}
/* リストが開いている間も、枠とリングで強調する */
.select:open {
border-color: #7b6cf6;
box-shadow: 0 0 0 4px rgba(123, 108, 246, 0.18);
}
/* 選択中の文字。長いときは、矢印の手前で「…」にする */
.select > button {
all: unset;
display: block;
flex: 1;
min-width: 0;
margin-right: 12px;
}
.select selectedcontent {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 右端の矢印。開いている間は180度回転させる */
.select::picker-icon {
content: "";
/* Safariで左にずれないよう、ブラウザ標準の余白を消す */
padding: 0;
/* 丸の大きさと位置 */
width: 36px;
height: 36px;
margin: 0 18px 0 auto;
background: url("arrow.svg") center / contain no-repeat;
transition: rotate 0.25s cubic-bezier(0.3, 0.7, 0.4, 1);
}
.select:open::picker-icon {
rotate: 180deg;
}
/* スマホでは、矢印もひと回り小さくする */
@media (max-width: 767px) {
.select::picker-icon {
width: 28px;
height: 28px;
margin-right: 14px;
}
}
/* 開いたリストの箱。閉じているときは透明で、少し上にある */
.select::picker(select) {
margin-top: 10px;
padding: 8px;
border: 1px solid #e2ddfa;
border-radius: 18px;
background: #fff;
box-shadow:
0 20px 44px rgba(108, 92, 231, 0.18),
0 2px 6px rgba(108, 92, 231, 0.08);
opacity: 0;
translate: 0 -8px;
scale: 0.98;
transform-origin: top center;
transition:
opacity 0.2s,
translate 0.2s cubic-bezier(0.3, 0.7, 0.4, 1),
scale 0.2s cubic-bezier(0.3, 0.7, 0.4, 1),
display 0.2s allow-discrete,
overlay 0.2s allow-discrete;
}
/* 開いているときの状態 */
.select:open::picker(select) {
opacity: 1;
translate: 0 0;
scale: 1;
}
/* 開いた瞬間の状態。ここから上の状態へアニメーションする */
@starting-style {
.select:open::picker(select) {
opacity: 0;
translate: 0 -8px;
scale: 0.98;
}
}
/* リストの各項目 */
.select option {
padding: 14px 20px;
border-radius: 12px;
color: #2a2640;
font-weight: 500;
transition: background-color 0.15s;
}
.select option:hover,
.select option:focus-visible {
background: #f6f4ff;
outline: none;
}
/* 選択中の項目 */
.select option:checked {
background: #ece8ff;
color: #5a4bd6;
font-weight: 700;
}
/* 選択中の項目の右端に付くチェックマーク */
.select option::checkmark {
content: "";
order: 1;
flex: none;
width: 24px;
height: 24px;
margin-left: auto;
border-radius: 50%;
background: url("check.svg") center / 70% no-repeat, #7b6cf6;
}
}arrow.svg はラベンダーの丸(#7b6cf6)に白い下向きの矢印を描いた画像、check.svg は白い線でチェックを描いた画像で、どちらもSVGファイルとして読み込んでいます。
ポイント1:selectと::picker(select)の両方に指定する
select は閉じた状態、::picker(select) は開いたリストの切り替えです。今回は両方を装飾するので、両方に指定しています。select だけに指定すれば、閉じた状態だけを新しい描画にして、開いたリストはOS標準のまま残すこともできます。
.select,
.select::picker(select) {
appearance: base-select;
}ポイント2:非対応ブラウザ向けの土台を作り、@supportsで切り替える
CSSの前半は、すべてのブラウザ向けの土台です。従来の appearance: none で装飾し、矢印は背景画像で描いています。後半の @supports (appearance: base-select) の中は、対応ブラウザだけに適用されます。
/* 土台 */
.select {
appearance: none;
padding: 0 72px 0 28px;
background:
url("arrow.svg") right 18px center / 36px no-repeat,
linear-gradient(180deg, #fff, #f5f2ff);
}
/* 対応ブラウザだけ */
@supports (appearance: base-select) {
.select {
padding-right: 0;
background: linear-gradient(180deg, #fff, #f5f2ff);
}
}対応ブラウザでは、矢印を ::picker-icon で描き直すので、背景の矢印と、そのために空けていた右の余白を外しています。padding-right や background は非対応のブラウザでも効くプロパティなので、この打ち消しを @supports の外に書くと、非対応のブラウザでも矢印が消えてしまいます。
ポイント3:閉じた状態はselect、開いたリストは::picker(select)とoptionに指定する
閉じた状態の装飾は、従来のselectと同じく、select本体に指定します。開いたリストは、箱を ::picker(select)、各項目は option、選択中の項目は option:checked に指定します。
.select::picker(select) {
border-radius: 18px;
background: #fff;
/* 省略 */
}
.select option {
padding: 14px 20px;
border-radius: 12px;
}
.select option:checked {
background: #ece8ff;
}開いたリストは、画面の下端に余白がないときは、自動で上向きに開きます。位置を調整するCSSは不要です。
ポイント4:矢印は::picker-icon。標準の余白を消しておく
右端の矢印は ::picker-icon です。選択中の文字の後ろに横並びで配置されるので、従来のように position: absolute で重ねる必要はありません。:open を使って、開いている間だけ回転させています。
.select::picker-icon {
content: "";
padding: 0;
/* 省略 */
}
.select:open::picker-icon {
rotate: 180deg;
}padding: 0 は、ブラウザ標準の余白を消す指定です。これがないと、Safariで矢印が少し左にずれました。
ポイント5:チェックマークは::checkmark
選択中の項目に付くチェックマークは option::checkmark です。デフォルトでは文字の左に付くので、order と margin-left: auto で右端に移しています。optionの中身が横並びのフレックスボックスとして描画されるので、こうした指定が効きます。また、デフォルトでは content に「✓」の文字が入っています。今回はSVGの画像で描くので、content: "" で文字を消し、背景画像で表示しています。チェックマークが不要なら、display: none で消せます。
.select option::checkmark {
content: "";
order: 1;
margin-left: auto;
background: url("check.svg") center / 70% no-repeat, #7b6cf6;
/* 省略 */
}ポイント6:開閉アニメーションはallow-discreteと@starting-style
開いたリストは、閉じている間 display: none です。display は中間の値がなく一瞬で切り替わるので、そのままでは閉じるときは即座に消え、開くときはいきなり完成した状態で現れます。これを解決するのが、allow-discrete と @starting-style です。
allow-discrete:閉じるとき、消えるのを待たせる
display と overlay は、どちらも途中の値がない、2択のプロパティです。display は表示するかしないか、overlay はリストがページの最前面の層(トップレイヤー)にいるかどうかを表していて、閉じるときはどちらも一瞬で切り替わります。
transition で、この2つに allow-discrete を付けると、切り替えがアニメーションの終わりまで待たされます。そのため、リストは最前面に表示されたまま、フェードアウトが最後まで見えます。
.select::picker(select) {
opacity: 0;
transition:
opacity 0.2s,
display 0.2s allow-discrete,
overlay 0.2s allow-discrete;
}
.select:open::picker(select) {
opacity: 1;
}@starting-style:開くとき、出発点を決める
transition は、「変化する前の値」から「変化した後の値」へ動くアニメーションです。ところが、display: none から表示されたリストには「変化する前の値」がないので、開いた瞬間から完成した状態で現れます。
@starting-style の中に値を書くと、それが表示された最初の一瞬の値として扱われます。そこから通常の値に向かって transition が動くので、フェードインになります。
@starting-style {
.select:open::picker(select) {
opacity: 0;
}
}ポイント7:長い文字は、selectedcontentで省略する
選択中の文字が長いと、そのままでは文字が矢印の下に潜り込み、途中で切れて読めなくなります。ところが、select本体に text-overflow を指定しても効きません。
そこで、HTMLに <button><selectedcontent></selectedcontent></button> を足しています。これは、閉じた状態に選択中の項目を表示する部品です。書かなくてもブラウザが内部で用意してくれますが、その内部の部品にはCSSが届きません。自分で書いておくと、selectedcontent にCSSを当てて、長い文字を矢印の手前で「…」にできます。
.select > button {
all: unset;
display: block;
flex: 1;
min-width: 0;
margin-right: 12px;
}
.select selectedcontent {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}実務で使えるデザインのデモ
実務でよく使う3つのselectを、base-select で作ったデモです。どれも、前の章の基本スタイルに、必要な指定を足しています。
都道府県:グループ分けと長いリスト
optgroup で地方ごとにまとめ、見出しを付けています。リストは高さを制限して中でスクロールさせ、開いたときは、選択中の項目が見える位置まで自動でスクロールします。
見出しは、optgroup の最初の子に <legend> を置いて作っています。base-select では、<legend> が見出しになり、普通の要素としてCSSで装飾できます。非対応のブラウザは <legend> を無視して、label 属性の文字を見出しに使います。
<select class="select select-pref" name="pref">
<button><selectedcontent></selectedcontent></button>
<option value="">都道府県を選択してください</option>
<optgroup label="北海道・東北">
<legend><span>北海道・東北</span></legend>
<option>北海道</option>
<option>青森県</option>
<!-- 省略 -->
</optgroup>
<!-- 省略 -->
</select>CSSは、「基本の使い方」のコードの後ろに、次の指定を足しています。
@supports (appearance: base-select) {
/* リストの高さを制限して、中でスクロールさせる */
.select-pref::picker(select) {
max-height: 360px;
overflow-y: auto;
}
/* Safariが標準で付ける区切り線を消す */
.select-pref optgroup {
padding: 0;
border: 0;
}
/* 地方名の見出し。背景に、文字の上下中央を通る横線を描く */
.select-pref legend {
/* Safariでは文字の幅に縮むので、リストの幅いっぱいに広げる */
box-sizing: border-box;
width: 100%;
padding: 20px 20px 8px;
background: linear-gradient(#e4e0f5, #e4e0f5) center / 100% 1px no-repeat;
background-origin: content-box;
background-clip: content-box;
color: #8a84a8;
font-size: 14px;
font-weight: 700;
}
/* 文字に白い背景を付けて、文字の部分だけ線を隠す */
.select-pref legend span {
padding-right: 12px;
background: #fff;
}
@media (max-width: 767px) {
.select-pref legend {
font-size: 13px;
}
}
/* 選べない項目 */
.select-pref option:disabled {
opacity: 0.45;
cursor: not-allowed;
}
}カラー選択:選択肢に色見本を入れる
optionの中に、色見本の要素と在庫表示を置いています。閉じた状態には、<button> の中の <selectedcontent> に選択中のoptionが複製されて表示されるので、色見本も一緒に表示されます。在庫表示だけは、閉じた状態では隠しています。
<select class="select select-color" name="color">
<button><selectedcontent></selectedcontent></button>
<option value="navy" selected>
<span class="swatch" style="background: #233a7a"></span>ネイビー<span class="note">(在庫あり)</span>
</option>
<option value="terracotta">
<span class="swatch" style="background: #c0623f"></span>テラコッタ<span class="note is-low">(残りわずか)</span>
</option>
<option value="ivory" disabled>
<span class="swatch" style="background: #f3eee2"></span>アイボリー<span class="note">(入荷待ち)</span>
</option>
<!-- 省略 -->
</select>CSSは、「基本の使い方」のコードの後ろに、次の指定を足しています。
@supports (appearance: base-select) {
/* 各項目と、閉じた状態の中身を、色見本と文字の横並びにする */
.select-color option,
.select-color selectedcontent {
display: flex;
align-items: center;
gap: 12px;
}
/* 色見本 */
.select-color .swatch {
flex: none;
width: 22px;
height: 22px;
border-radius: 50%;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}
/* 在庫表示。チェックマークの手前で右寄せにする */
.select-color .note {
margin-left: auto;
color: #8a84a8;
font-size: 14px;
font-weight: 500;
}
.select-color .note.is-low {
color: #c0623f;
}
.select-color option::checkmark {
margin-left: 12px;
}
/* 閉じた状態では、在庫表示を隠す */
.select-color selectedcontent .note {
display: none;
}
/* 選べない項目 */
.select-color option:disabled {
opacity: 0.45;
cursor: not-allowed;
}
}在庫表示を全角かっこで囲んでいるのは、非対応のブラウザ対策です。非対応のブラウザでは、optionの中のタグが無視されて、テキストだけが残ります。かっこがないと「ネイビー在庫あり」のように文字がつながってしまいますが、かっこで区切っておけば「ネイビー(在庫あり)」と読めます。なお、非対応のブラウザでは、閉じた状態にも在庫表示が出ます。
言語切り替え:閉じた状態を小さくする
コーポレートサイトのヘッダーによくある、言語切り替えのselectです。閉じた状態は地球のアイコンと言語の略称(JA、ENなど)だけにして小さく収め、開いたリストには言語名と略称を並べています。
<select class="select select-lang" name="lang" aria-label="言語">
<button><selectedcontent></selectedcontent></button>
<option value="ja" selected><span class="name">日本語</span> <span class="code">JA</span></option>
<option value="en"><span class="name">English</span> <span class="code">EN</span></option>
<option value="zh"><span class="name">简体中文</span> <span class="code">ZH</span></option>
<!-- 省略 -->
</select>CSSは、「基本の使い方」のコードの後ろに、次の指定を足しています。
/* 土台:ヘッダーに収まる小さなサイズにする */
.select-lang {
width: 150px;
max-width: none;
height: 44px;
padding: 0 44px 0 16px;
border-radius: 999px;
background-position: right 10px center, 0 0;
background-size: 24px, auto;
font-size: 15px;
}
@supports (appearance: base-select) {
.select-lang {
/* 略称の文字幅で横幅が変わらないよう、幅を固定する */
width: 120px;
padding: 0;
background: linear-gradient(180deg, #fff, #f5f2ff);
}
/* 閉じた状態:地球のアイコンと、言語の略称だけを表示する */
.select-lang > button {
display: flex;
align-items: center;
gap: 8px;
margin: 0 8px 0 0;
padding-left: 14px;
}
.select-lang > button::before {
content: "";
flex: none;
width: 20px;
height: 20px;
background: url("globe.svg") center / contain no-repeat;
}
.select-lang selectedcontent .name {
display: none;
}
.select-lang selectedcontent .code {
color: #2a2640;
font-size: 15px;
font-weight: 700;
}
.select-lang::picker-icon {
width: 24px;
height: 24px;
margin-right: 10px;
}
/* 開いたリスト:言語名と略称を並べる */
.select-lang::picker(select) {
min-width: 220px;
/* selectの右端に合わせて、左へ広げる */
position-area: block-end span-inline-start;
/* 下に余白がないときは、上向きに開く */
position-try-fallbacks: flip-block;
}
.select-lang option {
display: flex;
align-items: center;
gap: 12px;
font-size: 16px;
}
.select-lang option .code {
margin-left: auto;
color: #8a84a8;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.4px;
}
.select-lang option::checkmark {
margin-left: 12px;
}
}ポイントは次の3つです。
- 閉じた状態とリストで、見せる内容を変える:
selectedcontentの中の言語名(.name)を隠し、閉じた状態には略称だけを出しています。地球のアイコンは、<button>の::beforeで描いています - 幅を固定する:
base-selectでは、selectの幅が選択中の項目の中身で決まります。そのままだと、略称ごとの文字幅の違いで、言語を切り替えるたびに横幅が変わるので、幅を固定しています - リストを右端にそろえる:開いたリストは、標準ではselectの左端に合わせて右へ広がります。ヘッダーの右端に置くと画面からはみ出しやすいので、
position-areaで、selectの右端に合わせて左へ広がる配置にしています。position-areaを指定し直すと、下に余白がないときに上向きに開く動きも上書きされるので、position-try-fallbacks: flip-blockで改めて指定しています
非対応のブラウザでは、言語名と略称が「日本語 JA」のように表示されます。間に空白を入れておくと、文字がつながりません。
まとめ
selectと::picker(select)にappearance: base-selectを指定すると、開いたリストまでCSSで装飾できる- 対応はChrome・Edge 135以降とSafari 27.0以降。Firefoxは未対応だが、普通のselectとして動く(2026年10月時点)
- スマホでもPCと同じように効き、ホイール型のUIではなくページ内にリストが開く
- 非対応のブラウザにも見た目をそろえるなら、
appearance: noneの装飾を土台にして、@supportsで切り替える - 長い文字の省略には、HTMLに
<button><selectedcontent></selectedcontent></button>が必要 - optionの中にHTMLを入れるなら、タグが消えても読める文字にする
multipleやsize付きのselectは、ブラウザごとの挙動がそろっておらず、まだ実用には厳しい
JavaScriptでセレクトを作り込まなくても、CSSだけで開いたリストまで整えられるようになりました。非対応のブラウザでも普通のselectとして動くので、まずは身近なフォームから試してみてください。残るはFirefoxの対応です。今後の動きに注目したいところです。
押していただけると励みになります!
