← blog
CSSSeptember 7, 2026 · 4 min read

One CSS Property Replaces Your Checkbox Hack

Recoloring a checkbox to match the brand usually means appearance: none, a hand-drawn checkmark, and a rebuilt focus ring. accent-color does the same job in one line — and knowing where it stops matters as much as knowing that it works.

Parsa Jiravand · Frontend engineer · building bestpractic
One CSS Property Replaces Your Checkbox Hack

Someone on the design side wants the checkboxes to match the brand's indigo instead of the browser's default blue. Reasonable ask. So you open the CSS file and start down the well-worn path:

CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
input[type="checkbox"] { appearance: none; width: 18px; height: 18px; border: 2px solid #6366f1; border-radius: 4px; position: relative; } input[type="checkbox"]:checked { background: #6366f1; } input[type="checkbox"]:checked::after { content: ""; position: absolute; inset: 3px; background: url("data:image/svg+xml,...") no-repeat center; } input[type="checkbox"]:focus-visible { outline: 2px solid #6366f1; outline-offset: 2px; }

That compiles, it looks right in the design review, and it's already an hour of work for one control. Now do the radio buttons. Now do the range slider — no ::after trick works there, you're overriding ::-webkit-slider-thumb and ::-moz-range-thumb separately because no shorthand covers both engines. Now someone files a bug: the checkbox lost its indeterminate state, because you rebuilt :checked and forgot :indeterminate existed.

You didn't reskin a checkbox. You reimplemented one, badly, one state at a time.

CSS
1
2
3
:root { accent-color: #6366f1; }

One declaration, set once, at the root. Every checkbox, radio button, range slider, and progress bar under it now tints indigo — no appearance: none, no hand-drawn checkmark, no rebuilt focus ring, because you never took the native control apart in the first place. accent-color is an inherited property, which is exactly why setting it at :root is enough; it flows down to every form control in the document the same way color flows down to every piece of text.

The browser keeps drawing the control the way it always has — rounded on iOS, square-ish on Windows, whatever the platform's native checkbox looks like — and just swaps the accent hue: the check itself, the radio dot, the slider's thumb and filled track, the progress bar's value fill. You get a colored control that still behaves like a real one, with every state you didn't have to write: :hover, :disabled, :indeterminate, keyboard focus, screen-reader semantics, all still wired up because the browser is still the one doing the wiring.

Try that against your own instinct for a second: pick a color, and watch which controls actually pick it up.

Runs right in your browser — poke at it and watch the concept react live.

accent-color reaches four things: checkbox, radio, range, and progress. Notably absent: <meter>. Despite looking like a close cousin of <progress>, meter colors its value bar through separate, less standardized hooks — dropping accent-color on it does nothing, and that's spec behavior, not a bug in your stylesheet.

And here's the part that trips people up after they've already adopted it: if you've already gone the appearance: none route on a control — drawn your own box, your own checkmark, your own thumb — accent-color has nothing left to tint. It only recolors the browser's own native rendering path. Take that rendering away, and you're back to owning every pixel and every state yourself, by hand, same as before. accent-color isn't a way to further customize a control you've already rebuilt; it's a reason to not rebuild it in the first place.

One more honest caveat, and it's a feature, not a limitation: on a system running a high-contrast mode — Windows' forced-colors, for instance — the OS is allowed to override accent-color along with most other author-specified colors, to guarantee the contrast the user configured actually holds. Your indigo can vanish there on purpose. That's the platform doing its job, not the property failing at its own one purpose.

Back to that hour spent on one checkbox: subtract the appearance: none, the pseudo-element checkmark, the two vendor-prefixed thumb selectors, the rebuilt focus ring — and what's left to write by hand is the indeterminate state, because that one's still yours regardless. Everything else was already built. accent-color was just never told to use it.

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.

Next time someone asks for brand-colored checkboxes, how much of that request can you answer with a single line at :root — and how much of your existing form CSS was solving a problem this property already solves for free?


🚀 Want more like this? Every guide, playground, and quiz lives on bestpractic.org — open it and sign up free so the next one finds you.

Thanks for reading! Let's stay connected:

Keep reading

One post a day, in your inbox

Each one with a runnable playground and a quiz. No pitch, no digest, unsubscribe in one click.

0 comments

Sign in to join the discussion, like comments, and save articles for later.