デザインカンプどおりにフォームを組んだのに、Windows の Chrome と Mac の Safari、そして iPhone で見比べるとチェックボックスもセレクトボックスも別物の見た目になっている——フォームを作ると必ず通る道です。この食い違いの原因は、フォーム部品をブラウザーが OS の部品として描いていることにあります。その描画を止めて、ただの箱として自分でスタイルできるようにするのが CSS の appearance です。この記事では appearance: none の基本から、チェックボックスやセレクトボックスを自作デザインにする実践例、外した結果として気を付けたいアクセシビリティまでを、動かせるデモ付きで解説します。
目次
appearance はフォーム部品の「OS 標準の見た目」を外すプロパティ
<input type="checkbox"> と書いただけで四角い枠とチェックマークが表示されるのは、ブラウザーがその要素をプラットフォーム標準の部品として描いているからです。角の丸みや影、セレクトボックスの矢印などは HTML にも CSS にも書かれていません。ブラウザーと OS が組み合わさって決めている見た目なので、環境が変われば当然結果も変わります。
この特別扱いをやめさせるのが appearance です。appearance: none を指定すると、ブラウザーは「この要素はチェックボックスだから」という描画をやめ、<div> と同じようにボックスとして素直に描くようになります。背景色も枠線も角丸も、そこからは自分の CSS がそのまま効くわけです。
大事なのは、消えるのは見た目だけだという点です。クリックやキーボードでチェックが切り替わること、Tab キーでフォーカスが当たること、フォーム送信時に値が送られることは今までどおりです。要素そのものを display: none で隠して代わりに <label> を飾る昔ながらの手法と違い、部品としての機能を保ったまま見た目だけ差し替えられるのが appearance の利点です。
指定できる値と基本の書き方
実務で使う値は none と auto の 2 つだけです。ほかに過去のベンダー拡張との互換のために残された値がありますが、新しく書くコードで使う場面はありません。
| 値 | 効果 |
|---|---|
none | プロパティとしての初期値。要素固有の標準描画を行わず、通常の CSS ボックスとして描画する |
auto | その要素にふさわしいプラットフォーム標準の見た目で描画する。ブラウザーの既定スタイルシートがフォーム部品に指定している |
button / checkbox / textfield / menulist-button など | かつての -webkit-appearance との互換のために仕様に残された値。対象の要素では auto と同じように振る舞う。新規に使う値ではない |
表のとおり、appearance というプロパティ自体の初期値は none です。それでもフォーム部品が標準の見た目で表示されるのは、ブラウザーの既定スタイルシートが各部品に appearance: auto を当てているからです。私たちが書く appearance: none は、その既定スタイルを上書きしている、という関係になります。
.select {
/* 古い Safari(15.3 以前)は接頭辞付きしか解釈しないので先に書く */
-webkit-appearance: none;
/* 標準のプロパティは後ろに書いて、対応ブラウザーではこちらを優先させる */
appearance: none;
}
接頭辞の無い appearance は Chrome・Firefox・Safari のいずれも対応済みなので、本来は 1 行で足ります。ただし iOS の Safari は OS のバージョンに縛られて更新されないこともあるため、フォームのように壊れると致命的な箇所では -webkit-appearance を併記しておくのが無難です。接頭辞付きを先、標準を後ろに書くのが順序の原則です。
下のデモで、同じ部品が appearance: none でどう変わるかを見てください。上が標準の見た目、下が標準描画を外したものです。チェックボックスやラジオボタンは指定した瞬間に本当に何も描かれなくなるので、幅・高さ・枠線を自分で与えないと消えてしまう点に注目してください。
<div class="demo">
<div class="panel">
<p class="panel__title">標準の見た目(appearance: auto)</p>
<div class="parts">
<label><input type="checkbox" checked> チェックボックス</label>
<label><input type="radio" name="plan-a" checked> ラジオボタン</label>
<select>
<option>セレクトボックス</option>
</select>
<button type="button">ボタン</button>
</div>
</div>
<div class="panel">
<p class="panel__title">標準の描画を外した状態(appearance: none)</p>
<div class="parts parts--reset">
<label><input type="checkbox" checked> チェックボックス</label>
<label><input type="radio" name="plan-b" checked> ラジオボタン</label>
<select>
<option>セレクトボックス</option>
</select>
<button type="button">ボタン</button>
</div>
</div>
</div>
.demo {
font-family: sans-serif;
padding: 16px;
font-size: 14px;
}
.panel {
margin-bottom: 24px;
}
.panel__title {
margin: 0 0 8px;
font-weight: bold;
color: #555;
}
.parts {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16px;
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
background: #fff;
}
/* ここがポイント。フォーム部品の標準描画をまとめて外す */
.parts--reset input,
.parts--reset select,
.parts--reset button {
-webkit-appearance: none; /* 古い Safari 向け */
appearance: none;
}
/* appearance: none にすると「何も描かれない」状態になるので、
大きさや枠は自分で指定しないと消えたように見える */
.parts--reset input[type="checkbox"],
.parts--reset input[type="radio"] {
width: 18px;
height: 18px;
border: 1px solid #999;
background: #fff;
vertical-align: -3px;
}
.parts--reset select,
.parts--reset button {
padding: 8px 12px;
border: 1px solid #999;
background: #fff;
font: inherit;
}
ブラウザーによっては、セレクトボックスやボタンの標準の見た目がもともと控えめで、変化が分かりにくいかもしれません。それでも「どの環境でも同じ土俵に揃う」ことが appearance: none の価値です。ここから先は、揃った土俵の上に自分のデザインを積み上げていきます。
チェックボックスとラジオボタンを自作デザインにする
もっともよく使うのがこのケースです。手順は 3 段階で、標準描画を外す → 枠として整える → :checked のときの見た目を用意するという流れになります。チェックマークは擬似要素 ::before で描きます。appearance: none を当てた入力欄は普通のボックスとして扱われるため、display: grid で中身を中央寄せするといった指定も素直に効きます。
/* 1. 標準描画を外して、四角い箱として作り直す */
.check__input {
-webkit-appearance: none;
appearance: none;
width: 22px;
height: 22px;
margin: 0;
border: 2px solid #b4bcc6;
border-radius: 6px;
background: #fff;
/* 中のチェックマークを中央に置くため */
display: grid;
place-content: center;
}
/* 2. チェックマークは擬似要素で描く。最初は透明にしておく */
.check__input::before {
content: "";
width: 11px;
height: 6px;
border-left: 3px solid #fff;
border-bottom: 3px solid #fff;
transform: rotate(-45deg) translate(1px, -2px);
opacity: 0;
}
/* 3. チェックされたら箱の色を変えてマークを見せる */
.check__input:checked {
border-color: #007bff;
background: #007bff;
}
.check__input:checked::before {
opacity: 1;
}
チェックマークは「左と下だけ枠線を引いた長方形を -45deg 回転させる」という定番の描き方です。画像を用意しなくてよく、色も border-color で自由に変えられます。opacity で出し入れしているので、transition を足せばふわっと表示させることもできます。
ラジオボタンも同じ考え方で、border-radius: 50% を足して丸くし、::before を線ではなく円にすれば完成です。下のデモにはラジオボタン、無効状態、そしてあとで説明する :focus-visible のフォーカスリングまで含めてあります。Tab キーでの移動も試してみてください。
<form class="form">
<fieldset class="group">
<legend class="group__title">使っている言語(チェックボックス)</legend>
<label class="check">
<input type="checkbox" class="check__input" checked>
<span class="check__text">HTML / CSS</span>
</label>
<label class="check">
<input type="checkbox" class="check__input">
<span class="check__text">JavaScript</span>
</label>
<label class="check">
<input type="checkbox" class="check__input" disabled>
<span class="check__text">PHP(選択できません)</span>
</label>
</fieldset>
<fieldset class="group">
<legend class="group__title">プラン(ラジオボタン)</legend>
<label class="check">
<input type="radio" name="plan" class="check__input check__input--radio" checked>
<span class="check__text">無料プラン</span>
</label>
<label class="check">
<input type="radio" name="plan" class="check__input check__input--radio">
<span class="check__text">有料プラン</span>
</label>
</fieldset>
<p class="note">Tab キーでフォーカスを移すと、枠の外側にフォーカスリングが出ます。</p>
</form>
.form {
font-family: sans-serif;
font-size: 15px;
padding: 16px;
}
.group {
margin: 0 0 20px;
padding: 12px 16px 16px;
border: 1px solid #ddd;
border-radius: 8px;
}
.group__title {
padding: 0 4px;
color: #555;
font-size: 13px;
}
.check {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 0;
cursor: pointer;
}
/* 1. 標準の描画を外す。ここから先はすべて自分で描く */
.check__input {
-webkit-appearance: none;
appearance: none;
/* 2. 描画が消えるのでサイズ・枠・背景を指定する */
width: 22px;
height: 22px;
margin: 0;
border: 2px solid #b4bcc6;
border-radius: 6px;
background: #fff;
cursor: pointer;
/* 3. 中のチェックマークを中央に置くための箱にする */
display: grid;
place-content: center;
transition: background-color 0.15s, border-color 0.15s;
}
/* ラジオボタンは丸くする */
.check__input--radio {
border-radius: 50%;
}
/* 4. チェックマーク本体。まずは非表示にしておく */
.check__input::before {
content: "";
width: 11px;
height: 6px;
border-left: 3px solid #fff;
border-bottom: 3px solid #fff;
transform: rotate(-45deg) translate(1px, -2px);
opacity: 0;
}
.check__input--radio::before {
width: 10px;
height: 10px;
border: none;
border-radius: 50%;
background: #fff;
transform: none;
}
/* 5. チェックされたら色を変えてマークを表示する */
.check__input:checked {
border-color: #007bff;
background: #007bff;
}
.check__input:checked::before {
opacity: 1;
}
/* 6. キーボード操作のときだけフォーカスリングを出す */
.check__input:focus-visible {
outline: 3px solid #9ec8ff;
outline-offset: 2px;
}
/* 7. 無効状態も自分で表現する */
.check__input:disabled {
border-color: #e0e0e0;
background: #f2f2f2;
cursor: not-allowed;
}
.check__input:disabled + .check__text {
color: #aaa;
}
.note {
margin: 0;
color: #888;
font-size: 13px;
}
ポイントは、ラベルのテキストを <label> で囲んでいることです。こうしておけば文字の部分をクリックしても切り替わりますし、支援技術にも「この入力欄の名前」として正しく伝わります。appearance: none は見た目を作り変えるだけで、こうした HTML 側の組み立てを肩代わりはしてくれません。
select の矢印を消して独自の矢印を付ける
セレクトボックスに appearance: none を指定すると、右端の矢印(ドロップダウンマーカー)ごと描画が消えます。開けることを示す目印が無くなってしまうので、必ず自分で矢印を用意します。方法は 2 つあり、どちらを選んでもかまいません。
ひとつは select 自身の background-image として矢印の画像を置く方法です。要素をひとつ増やさずに済むので手軽で、SVG をデータ URI で埋め込めば追加のリクエストも発生しません。矢印と文字が重ならないよう、padding-right を広めに取るのを忘れないでください。
.select {
-webkit-appearance: none;
appearance: none;
padding: 10px 40px 10px 14px; /* 右側は矢印のぶん空ける */
border: 1px solid #b4bcc6;
border-radius: 8px;
background-color: #fff;
/* SVG をデータ URI で背景に置く。# は %23 に書き換える */
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%23555' stroke-width='2'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 14px center;
background-size: 12px 8px;
}
もうひとつは、select を <div> で包んで親要素の ::after で矢印を描く方法です。CSS だけで三角や山形を作れるので、色をテーマに合わせて変えやすいのが利点です。なぜ親要素なのかというと、select 自身に付けた ::before / ::after はブラウザーに表示してもらえないからです。中身の描画をブラウザーが取り仕切っている要素なので、擬似要素の入る場所が用意されていないのです。
/* select を包む要素を基準にする */
.select-wrap {
position: relative;
display: inline-block;
}
.select-wrap::after {
content: "";
position: absolute;
top: 50%;
right: 16px;
width: 8px;
height: 8px;
border-right: 2px solid #555;
border-bottom: 2px solid #555;
transform: translateY(-70%) rotate(45deg);
/* これが無いと矢印の上をクリックしたときに select が開かない */
pointer-events: none;
}
擬似要素は矢印の位置、つまりユーザーがいちばんクリックしそうな場所に重なります。pointer-events: none を付けてクリックを下の select に通してあげるのが必須です。デモで 2 つの方法を並べてあるので、見比べてみてください。
<div class="demo">
<div class="field">
<label class="field__label" for="pref">背景画像で矢印を付ける</label>
<select class="select select--bg" id="pref">
<option>北海道</option>
<option>東京都</option>
<option>沖縄県</option>
</select>
</div>
<div class="field">
<label class="field__label" for="size">親要素の ::after で矢印を付ける</label>
<div class="select-wrap">
<select class="select" id="size">
<option>S サイズ</option>
<option>M サイズ</option>
<option>L サイズ</option>
</select>
</div>
</div>
</div>
.demo {
font-family: sans-serif;
font-size: 15px;
padding: 16px;
}
.field {
margin-bottom: 24px;
}
.field__label {
display: block;
margin-bottom: 6px;
color: #555;
font-size: 13px;
}
/* 共通のリセット。標準の矢印ごと描画を外す */
.select {
-webkit-appearance: none;
appearance: none;
width: 260px;
max-width: 100%;
padding: 10px 40px 10px 14px; /* 右側は矢印のぶん空ける */
border: 1px solid #b4bcc6;
border-radius: 8px;
background-color: #fff;
color: #333;
font: inherit;
line-height: 1.4;
cursor: pointer;
}
.select:focus-visible {
outline: 3px solid #9ec8ff;
outline-offset: 2px;
}
/* 方法1: select 自身の背景画像として矢印を置く */
.select--bg {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%23555' stroke-width='2'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 14px center;
background-size: 12px 8px;
}
/* 方法2: 親要素の疑似要素で矢印を描く
(select 自身の ::before / ::after は表示されない) */
.select-wrap {
position: relative;
display: inline-block;
}
.select-wrap::after {
content: "";
position: absolute;
top: 50%;
right: 16px;
width: 8px;
height: 8px;
border-right: 2px solid #555;
border-bottom: 2px solid #555;
transform: translateY(-70%) rotate(45deg);
pointer-events: none; /* 矢印の上をクリックしても select が開くようにする */
}
なお、select の高さをそろえたい場合は height ではなく padding と line-height で調整するほうが環境差が出にくくなります。標準描画を外していない状態だと内側の余白の扱いがブラウザーごとに違うためで、ここでも appearance: none が効いてきます。
iOS Safari で勝手に付く角丸や影を消す
iPhone で自分のサイトのフォームを開いて、「角が丸い」「入力欄の上辺に薄い影が入っている」「送信ボタンが妙に青い」と感じたことはないでしょうか。これも iOS の Safari がフォーム部品を OS 標準の見た目で描いているために起こります。パソコンでは正しく見えるのに実機だけ崩れる、という形で表面化しがちです。
対処はシンプルで、テキスト入力欄・テキストエリア・ボタンに appearance: none を当ててから、border-radius を自分で指定し直します。appearance: none だけでは角丸が残ることもあるので、border-radius: 0 を明示するのがコツです。
/* iOS Safari の標準装飾(角丸・内側の影・独自の配色)を消す */
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="search"],
textarea,
button,
input[type="submit"] {
-webkit-appearance: none;
appearance: none;
border-radius: 0; /* 角丸が残る場合があるので明示的に戻す */
border: 1px solid #b4bcc6;
background: #fff;
font: inherit; /* 標準の見た目と一緒に文字設定も引き継がれなくなる */
}
/* 入力欄の文字が 16px 未満だと、iOS はフォーカス時に画面を拡大する */
input,
textarea,
select {
font-size: 16px;
}
font: inherit を添えているのは、フォーム部品の文字がブラウザー既定のフォントとサイズのままになりやすいからです。appearance は文字設定までは面倒を見てくれないので、この 1 行で本文と揃えておきます。最後の font-size: 16px は appearance とは別の話ですが、iOS で入力欄をタップしたときに画面が勝手に拡大されるのを防ぐための定番の指定なので、フォームのリセットとセットで覚えておくと便利です。
標準デザインと一緒に失われる「操作の手がかり」を補う
appearance: none で消えるのは装飾だけではありません。ブラウザーが用意していた状態を伝えるための表示もまとめて消えます。チェックされているか、いまフォーカスが当たっているか、操作できるのかどうか——これらを自分で描き直さないと、見た目はきれいでも使えないフォームになってしまいます。
フォーカスの表示は :focus-visible で取り戻す
キーボードだけで操作している人にとって、フォーカスリングは「いま自分がどこにいるか」を知る唯一の手がかりです。appearance: none を当てた部品ではこの表示が弱くなったり、outline: none と併記して完全に消してしまったりしがちなので、必ず代わりのスタイルを用意します。
/* キーボード操作でフォーカスしたときだけリングを出す */
.check__input:focus-visible {
outline: 3px solid #9ec8ff;
outline-offset: 2px;
}
:focus ではなく :focus-visible を使うのがポイントです。:focus だとマウスでクリックしたときにもリングが残り、デザイン面で嫌われて結局 outline: none に逆戻りしがちでした。:focus-visible は「キーボード操作など、表示したほうがよいとブラウザーが判断したとき」だけ一致するので、見た目とアクセシビリティを両立できます。outline-offset を少し取ると、背景色を塗りつぶした部品でもリングが埋もれません。
チェック状態を色の違いだけで伝えない
自作のチェックボックスでよくあるのが、「オンのときは青、オフのときは白」と塗りの色だけで状態を分けてしまう設計です。色の見分けが難しい人には、どちらが選択済みなのか判断できません。先ほどのデモのようにチェックマークを重ねる、枠線の太さを変えるなど、色以外の違いも必ず持たせてください。
同じく :disabled の見た目も自分で書く必要があります。ブラウザー標準ならグレーアウトで一目瞭然だったものが、appearance: none のあとは有効な部品とまったく同じ見た目になってしまうからです。
強制カラーモードでの見え方を確認する
Windows のハイコントラストモードのような強制カラーモードでは、ページの色がシステム側の配色に置き換えられます。背景色の塗り分けだけで状態を表していると、その塗りが上書きされて区別が付かなくなることがあります。@media (forced-colors: active) で表示を確認し、必要なら境界線や記号で状態が分かるよう補っておくと安心です。
見た目が変わらない・標準デザインが外れないとき
appearance: none と書いたのに何も起きない、あるいは部品ごと消えてしまった、という相談にはいくつか決まった原因があります。順に確認していきましょう。
部品が消えたように見えるのは、サイズを指定していないから
チェックボックスやラジオボタンは、標準描画を外すと本当に何も描かれない空のボックスになります。もともと大きさもブラウザーが描画のために持っていたものなので、width と height、そして枠線や背景を指定しない限り、画面上からは見えなくなったように感じます。「消えてしまった」ときは、まず自分でサイズを与えているか確認してください。
古い Safari では接頭辞付きしか解釈されない
接頭辞の無い appearance に Safari が対応したのは 15.4 からです。それ以前のバージョンが残っている端末では -webkit-appearance しか解釈されないため、標準のプロパティだけを書いていると iPhone でだけ標準デザインのまま、という結果になります。前述のとおり接頭辞付きを先に書いて併記しておけば防げます。
もうひとつ見落としやすいのが、リセット CSS やフレームワークが後から appearance: auto を当て直しているケースです。ブラウザーの開発者ツールで対象の要素を選び、appearance の計算値が none になっているかを確認すると原因の切り分けが早く済みます。
option の見た目はほとんど変えられない
select 本体は自由にスタイルできても、クリックしたときに開く選択肢のリストは別です。あの部分は多くの環境でブラウザーではなく OS が描いているため、option に padding や border を指定してもほぼ無視されます。color や background-color が効く環境もありますが、macOS のように何も効かない環境もあり、統一はできません。
選択肢の中にアイコンを入れたい、複数行のラベルを見せたいといった要求がある場合は、select を使わずに <button> と <ul> でドロップダウンを自作することになります。ただしキーボード操作や支援技術への対応を自分で用意する必要があるので、要件が「配色を少し変えたい」程度なら select のまま妥協するほうが安全です。
input type=”range” は専用の擬似要素が必要
スライダーは、レールとつまみという 2 つのパーツで構成されています。input 本体に appearance: none を指定しただけでは形が崩れるだけで、パーツごとにブラウザー独自の擬似要素を使って作り直す必要があります。しかもつまみには改めて -webkit-appearance: none を指定しないと標準の描画が残ります。
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 240px;
background: transparent; /* Chrome では背景を透明にしておく */
}
/* レール(Chrome・Safari) */
input[type="range"]::-webkit-slider-runnable-track {
height: 6px;
border-radius: 3px;
background: #dde2e8;
}
/* つまみ(Chrome・Safari)。ここにも appearance: none が要る */
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
margin-top: -7px; /* レールの中央に合わせる */
border-radius: 50%;
background: #007bff;
}
/* Firefox 用は別のルールとして書く(まとめるとルールごと無効になる) */
input[type="range"]::-moz-range-track {
height: 6px;
border-radius: 3px;
background: #dde2e8;
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
border: none;
border-radius: 50%;
background: #007bff;
}
コメントにも書いたとおり、::-webkit-slider-thumb と ::-moz-range-thumb をカンマでつないで 1 つのルールにまとめてはいけません。セレクターリストの中に解釈できないものが 1 つでもあると、そのルール全体が無視されるのが CSS の決まりだからです。ブラウザー独自の擬似要素は必ず別々のルールに分けて書いてください。同じ考え方は検索欄の ::-webkit-search-cancel-button や日付入力の ::-webkit-calendar-picker-indicator にも当てはまります。
まとめ
appearance: none は、ブラウザーと OS に任せていたフォーム部品の描画を止めて、普通のボックスとして自分でデザインできるようにするプロパティです。指定できる値は実質 none と auto の 2 つだけで、古い Safari を考慮して -webkit-appearance を先に併記するのが定番の書き方になります。チェックボックスやラジオボタンは :checked と ::before で、セレクトボックスは背景画像か親要素の ::after で矢印を作り直すのが基本形です。
気を付けたいのは、消えるのが装飾だけではないという点です。チェックの有無、フォーカス、無効状態といった操作の手がかりも一緒に消えるので、:focus-visible や :disabled のスタイルを必ずセットで用意してください。option の中身は依然として OS の領分であること、input type="range" は専用の擬似要素が必要なことも押さえておけば、フォームのデザインで迷う場面はかなり減るはずです。