チェックボックスやラジオボタンなどの標準的な HTML フォーム要素のカスタマイズは、これまで困難でした。デベロッパーは、ブラウザのデフォルトを使用するか、カスタム コンポーネントをゼロから構築するかの選択を迫られることがよくありました。カスタム コントロールの作成には時間がかかり、ユーザー補助の問題や状態の欠落(チェックボックスの不確定状態など)が発生しやすくなります。
CSS プロパティ accent-color を使用すると、アクセシビリティや組み込みのブラウザ機能を損なうことなく、1 行の CSS で組み込みの HTML フォーム入力にブランドカラーを簡単に適用できます。
実装方法
フォーム コントロールにブランドカラーを適用するには:
- ブランドカラーを特定する: ブランドを表す色を選択します。
accent-colorプロパティを適用する: CSS の要素またはコンテナ要素(bodyや特定のフォームなど)にaccent-colorを追加します。- ダークモードのサポート(任意、推奨):
color-schemeを使用して、サイトがダークモードをサポートしていることをブラウザに知らせ、必要に応じてaccent-colorを調整してコントラストを改善します。
コード例: ブランドに一貫したフォーム コントロール
:root {
--brand-color: #6200ee;
}
/* Apply accent-color to the body or a specific container */
body {
accent-color: var(--brand-color);
}
/* Optional: Adjust for dark mode if needed */
@media (prefers-color-scheme: dark) {
:root {
--brand-color: #bb86fc; /* A lighter shade for dark mode */
}
}
<form>
<!-- Checkbox -->
<label for="subscribe">
<input type="checkbox" id="subscribe" checked>
Subscribe to newsletter
</label>
<!-- Radio Buttons -->
<label for="plan-monthly">
<input type="radio" id="plan-monthly" name="plan" value="monthly">
Monthly
</label>
<label for="plan-yearly">
<input type="radio" id="plan-yearly" name="plan" value="yearly" checked>
Yearly
</label>
<!-- Range Slider -->
<label for="volume">Volume:</label>
<input type="range" id="volume" min="0" max="100" value="70">
<!-- Progress Bar -->
<label for="file">Upload Progress:</label>
<progress id="file" max="100" value="70">70%</progress>
</form>
戦略的な実装とベスト プラクティス
- 推奨:
accent-colorを使用して、ブランドに合わせてフォーム コントロールのテーマを簡単に設定する。 - コントラストの処理をブラウザに任せきりにしないでください。ブラウザは適切なコントラスト カラーを自動的に決定することになっていますが、Safari(WebKit バグ 244233)や Android Chrome(Chromium バグ 343503163)などの実装における既知のバグにより、チェックマークの色が反転しないことがあります。これにより、背景とのコントラストが不十分な色(ライトモードの明るい色、ダークモードの暗い色など)を使用すると、コントロールが表示されなかったり、見えにくくなったりすることがあります。
- 推奨:
accent-colorとcolor-scheme: light darkを組み合わせて、ライトモードとダークモードの両方でフォーム コントロールが適切に表示されるようにします。 - 背景色に近すぎる色は使用しないでください。ブラウザはコントラストを保証しようとしますが、ベースのコントラストが良好な色を使用することをおすすめします。
accent-colorがすべてのフォーム要素で機能するとは考えないでください。現在、checkbox、radio、range、progress要素のみに色を付けます。
代替戦略
accent-color をサポートしていないブラウザでは、フォーム コントロールはブラウザのデフォルトの外観にフォールバックします。すべての環境でブランドの一貫性と高い信頼性を確保するには、確立された「視覚的に非表示の入力」手法を使用して、カスタム フォールバック戦略を実装する必要があります。
@supports not を使用したプログレッシブ エンハンスメント
accent-color がサポートされていない場合にのみ、@supports not ルールを使用してカスタムの代替スタイルを適用する必要があります。これにより、最新のブラウザでは accent-color のシンプルさを活用し、古いブラウザでは一貫したブランド エクスペリエンスを保証できます。
1. HTML 構造
CSS で兄弟セレクタを使用できるように、ラベルが <span> 内のテキストをラップしていることを確認します。
<label for="subscribe-fallback">
<input type="checkbox" id="subscribe-fallback" class="visually-hidden" checked>
<span>Subscribe to newsletter</span>
</label>
2. CSS の代替
@supports not ブロック内でカスタム スタイルを適用します。
/* Fallback for older browsers without accent-color */
@supports not (accent-color: var(--brand-color)) {
/* Visually hide the native input using the canonical accessible recipe */
form input[type="checkbox"].visually-hidden {
position: absolute !important;
clip-path: inset(50%) !important;
overflow: hidden !important;
width: 1px !important;
height: 1px !important;
margin: -1px !important;
padding: 0 !important;
border: 0 !important;
white-space: nowrap !important;
}
/* Style the wrapper label */
label {
position: relative;
padding-left: 2rem;
cursor: pointer;
display: inline-flex;
align-items: center;
}
/* Custom box for checkbox */
input[type="checkbox"] + span::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 1.2rem;
height: 1.2rem;
border: 2px solid #ccc;
background: white;
border-radius: 4px;
box-sizing: border-box;
transition: all 0.2s ease;
}
/* Ensure custom checkbox shows focus for keyboard users */
input[type="checkbox"]:focus-visible + span::before {
outline: 2px solid #000;
outline-offset: 2px;
}
/* Checked State */
input[type="checkbox"]:checked + span::before {
background-color: var(--brand-color, #6200ee);
border-color: var(--brand-color, #6200ee);
}
/* Checkmark (Unicode) */
input[type="checkbox"]:checked + span::after {
content: "✓";
position: absolute;
left: 0.25rem;
top: 50%;
transform: translateY(-50%);
color: white;
font-weight: bold;
font-size: 0.9rem;
}
/* Fallback for Range Slider */
input[type="range"] {
-webkit-appearance: none;
appearance: none;
background: transparent;
}
/* Webkit (Chrome, Safari, Edge) */
input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
height: 8px;
/* Use gradient to show progress for a static value (e.g., 70%) or update with JS */
background: linear-gradient(to right, var(--brand-color, #6200ee) 70%, #ccc 70%);
border-radius: 4px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: var(--brand-color, #6200ee);
cursor: pointer;
margin-top: -4px; /* Center vertically */
}
/* Firefox */
input[type="range"]::-moz-range-track {
width: 100%;
height: 8px;
background: #ccc;
border-radius: 4px;
}
input[type="range"]::-moz-range-thumb {
height: 16px;
width: 16px;
border-radius: 50%;
background: var(--brand-color, #6200ee);
cursor: pointer;
}
/* Firefox specific progress bar */
input[type="range"]::-moz-range-progress {
background-color: var(--brand-color, #6200ee);
height: 8px;
border-radius: 4px;
}
/* Fallback for Progress Bar */
progress {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
background: #ccc;
height: 8px;
border-radius: 4px;
}
progress::-webkit-progress-bar {
background-color: #ccc;
border-radius: 4px;
}
progress::-webkit-progress-value {
background-color: var(--brand-color, #6200ee);
border-radius: 4px;
}
progress::-moz-progress-bar {
background-color: var(--brand-color, #6200ee);
border-radius: 4px;
}
}
Webkit フォールバックでのダイナミック レンジの進行状況
Webkit ブラウザで accent-color を使用せずに、範囲スライダーのつまみとともに進行状況の塗りつぶしを移動するには、CSS 変数と少量の JavaScript を使用します。
CSS を更新する: グラデーションの分岐点に CSS 変数を使用します。
input[type="range"]::-webkit-slider-runnable-track { background: linear-gradient(to right, var(--brand-color) var(--progress, 0%), #ccc var(--progress, 0%)); }JavaScript を追加する:
inputイベントで変数を更新します。
if (!CSS.supports('accent-color')) {
const slider = document.getElementById('volume');
slider.addEventListener('input', (e) => {
e.target.style.setProperty('--progress', `${e.target.value}%`);
});
}