CSS

selectのデザインをリストまでCSSで整える|base-selectの使い方

みなと

selectを開いたときのリストは、これまでCSSで装飾できませんでした。整えられるのは閉じた状態だけで、リストまでデザインどおりにしたいときは、JavaScriptで独自のセレクトを作るしかありませんでした。

閉じた状態をCSSで整える従来の方法は、こちらの記事で紹介しています。

あわせて読みたい
select要素のデザインを思い通りに整える|主要ブラウザで崩れないCSS設計
select要素のデザインを思い通りに整える|主要ブラウザで崩れない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・Edge135(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を当てられるようになります。

selectの各部分と、CSSで指定する名前を示した図。閉じた状態の箱がselect、その中の選択中の文字がbuttonの中のselectedcontent、右端の矢印が::picker-icon。開いたリストの箱が::picker(select)、リストの各項目がoption、選択中の項目のチェックがoption::checkmark。リストが開いている状態が:open
selectの各部分と、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の対応です。今後の動きに注目したいところです。

押していただけると励みになります!

ABOUT ME
みなと
みなと
フロントエンドエンジニア
Web制作会社に勤務して18年、ナショナルクライアントの案件が動く制作現場で、コーポレートサイト・LP・CMSサイトなど、Webサイトを実装してきました。このブログでは、Web制作の最前線を現場の品質基準で検証しています。

詳しいプロフィールと、お仕事のご依頼はこちら →
記事URLをコピーしました