appearance: none: Styling Native Controls Without a Reset Framework
A checkbox renders completely differently on macOS Safari versus Windows Chrome by default. appearance: none lets you target just the controls that need surgery, not a global reset.
Paul Radford — Full-Stack Developer & Editor. Paul is a full-stack developer and editor of template.tips. Articles here are AI-drafted and reviewed by Paul, who knows the code well enough to catch what's wrong and cut the hype.

appearance: none strips the operating system's default rendering from form controls like checkboxes, radio buttons, and select elements, letting you rebuild them with plain CSS. It does not remove behavior, accessibility semantics, or layout quirks, so you still need explicit styles for borders, backgrounds, and focus states once the native chrome is gone.
Why reach for appearance instead of a full reset library
Reset frameworks like normalize.css or a component library's form module exist because native controls render wildly differently across operating systems and browsers. A checkbox on macOS Safari looks nothing like one on Windows Chrome. That inconsistency used to force teams into all-or-nothing resets that zeroed out margins, paddings, and appearances across every element on the page, whether they needed it or not.
appearance: none lets you target only the controls that actually need surgery: checkboxes, radios, selects, search inputs, and range sliders. You leave everything else (text inputs, buttons, textareas) mostly alone, since those already inherit sane defaults from a good CSS baseline. This is the pragmatic middle ground: you get design control without pulling in a dependency or fighting a reset that undoes work you didn't ask it to undo.
The trade-off is that you take on more responsibility. Once you strip native appearance, you own the checkmark, the focus ring, the disabled state, and the indeterminate state. Skip any of those and you ship a control that looks fine but behaves like a black box to keyboard and screen reader users.
What does appearance: none actually remove?
It removes the platform-native visual styling, meaning the button-like shading, the OS-drawn checkmark, the dropdown arrow. It does not touch:
- Default sizing (a checkbox is still roughly 1em by 1em unless you set width and height)
- Keyboard interaction (space still toggles a checkbox, arrow keys still move a native radio group)
- Accessibility tree exposure (the control still reports as checkbox or radio to assistive tech, because that comes from the element type, not its CSS)
- Form submission behavior
That last point matters. A <select> with appearance: none still submits its selected value. You are only changing pixels, not semantics. This is the core reason appearance: none is safer than swapping controls for <div>-based fake widgets: you keep the browser's built-in accessibility and keyboard handling for free.
Styling a checkbox from scratch
Here is a checkbox rebuilt without any framework, using appearance: none plus a pseudo-element for the checkmark.
1/* Reset native appearance and give the box a fixed size */2input[type="checkbox"] {3 appearance: none;4 -webkit-appearance: none; /* Safari 15 and older still needs the prefix */5 width: 1.15em;6 height: 1.15em;7 border: 2px solid #555;8 border-radius: 3px;9 background-color: #fff;10 display: grid;11 place-content: center;12 margin: 0;13 cursor: pointer;14}1516/* Draw a checkmark using a scaled pseudo-element instead of an image */17input[type="checkbox"]: :before {18 content: "";19 width: 0.65em;20 height: 0.65em;21 transform: scale(0);22 transition: transform 0.1s ease-in-out;23 box-shadow: inset 1em 1em #2563eb;24 transform-origin: center;25 clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);26}2728input[type="checkbox"]: checked::before {29 transform: scale(1);30}3132/* Visible focus state, since removing native appearance can remove33 the default focus ring in some browsers */34input[type="checkbox"]: focus-visible {35 outline: 2px solid #2563eb;36 outline-offset: 2px;37}3839input[type="checkbox"]: disabled {40 border-color: #ccc;41 background-color: #f3f3f3;42 cursor: not-allowed;43}The clip-path checkmark avoids an extra image request and scales cleanly with em units, so it resizes with the parent font size. The :focus-visible rule is not optional decoration. Removing native appearance in Chrome and Firefox can also remove the default focus outline the OS would otherwise draw, so you need to replace it explicitly or you end up with a control that is invisible to keyboard users when focused.
How do you handle a custom select without hiding the native dropdown behavior?
This is where teams get into trouble. A <select> element's dropdown list (the popup with the options) is rendered by the operating system, not by your CSS, and appearance: none cannot restyle that popup. You can restyle the closed-state box, but the open list of options stays native looking in every browser except the ones that support the newer <selectedcontent> and customizable select APIs.
1/* Restyle the closed select box, remove the native arrow */2select {3 appearance: none;4 -webkit-appearance: none;5 -moz-appearance: none;6 background-color: #fff;7 border: 1px solid #999;8 border-radius: 4px;9 padding: 0.5em 2.2em 0.5em 0.75em;10 font-size: 1rem;11 cursor: pointer;1213 /* Draw a custom arrow with a background SVG since the native one is gone */14 background-image: url("data: image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23555'/%3E%3C/svg%3E");15 background-repeat: no-repeat;16 background-position: right 0.9em center;17}1819select: focus-visible {20 outline: 2px solid #2563eb;21 outline-offset: 1px;22}If you need the dropdown list itself to match your design system (custom option rows, icons, multi-column layouts), appearance: none on a native <select> cannot get you there. Your real choices are:
- Accept the native popup and only style the closed box. Fine for most admin dashboards and forms.
- Build a fully custom listbox with <div> and role="listbox" / role="option", reimplementing keyboard navigation, typeahead, and ARIA wiring yourself. This is a lot of work to get right and easy to get wrong.
- Use the emerging customizable select APIs (::picker(select) and <selectedcontent>), which as of early 2026 are supported in Chrome and Edge but not yet shipped in Firefox or Safari stable. Check current status on caniuse before relying on it in production, and ship a fallback for browsers that ignore the new pseudo-elements.
Option 3 is the direction the platform is heading, but it is not safe as your only implementation today. Option 2 is what most component libraries (Radix, Headless UI, react-select) actually do under the hood, which is worth knowing before you rebuild one from scratch for a single project.
Radio buttons and the indeterminate checkbox trap
Radios follow the same pattern as checkboxes, but there is one state that trips people up: the indeterminate checkbox. Setting element.indeterminate = true in JavaScript changes the visual rendering in the native widget without changing checked. Once you apply appearance: none and draw your own checkmark, that native indeterminate rendering disappears too, so you must add your own CSS hook:
1input[type="checkbox"]: indeterminate::before {2 transform: scale(1);3 clip-path: none;4 box-shadow: inset 1em 0.2em #2563eb; /* a horizontal dash instead of a check */5}Forget this and a "select all" checkbox in a table header will silently stop communicating its partial-selection state to sighted users, even though screen readers still announce it correctly through the accessibility tree.
Browser support notes worth knowing
appearance became a standardized CSS property (previously vendor-prefixed only) and is documented in the CSS Basic User Interface Module. Support for the unprefixed keyword is solid across current Chrome, Firefox, Safari, and Edge, but Safari required the -webkit-appearance prefix for form controls until relatively recently, and older Safari versions (15 and below) can ignore the unprefixed value silently, leaving native styling in place with no console warning. Always ship both the prefixed and unprefixed declarations for form controls if you support Safari versions from that era.
Range inputs (<input type="range">) are a separate headache: appearance: none removes the track and thumb styling, but you then need ::-webkit-slider-thumb, ::-moz-range-thumb, ::-webkit-slider-runnable-track, and ::-moz-range-track as four separate pseudo-elements to restyle the parts, because there is still no standardized single way to style a range slider's internals across engines.
Testing what you actually shipped
Do not just eyeball the checked state in one browser and call it done. Run through this before merging:
Tab to each control using only the keyboard and confirm a visible focus indicator appears. Toggle a checkbox with the space bar, not just a click, since click handlers sometimes get attached in ways that miss keyboard activation. Open the accessibility tree in your browser's dev tools (the Accessibility panel in Chrome or Firefox) and confirm the role and checked/pressed state still report correctly after your CSS changes, since that tree reflects the DOM element type and ARIA attributes, not your visual styling. Then test with the OS at 200% text zoom to confirm your fixed pixel dimensions on the checkbox or radio don't clip the checkmark or misalign with the label text.
If you are supporting older Safari or any Android WebView-based app shell, add those to your manual test pass too. Automated visual regression tools catch color and layout drift, but they will not catch a missing indeterminate state or a focus ring that only fails in Safari's older prefix handling, so budget real manual keyboard testing time before this ships.
Related Articles

Styling Form States: Beyond :valid and :invalid
A required email field showing an error state before anyone's typed a single character isn't a bug, it's a spec quirk. Here's the pseudo-class combination that fixes the timing problem.

field-sizing: content: Auto-Sizing Form Fields Natively
A hidden clone element measuring content on every keystroke causes real layout thrashing on large forms. This property lets the browser's own layout engine handle the resizing instead.

The :focus-visible Pseudo-Class: Better Focus States Without the Pain
outline: none with nothing to replace it made entire products unusable for keyboard and switch-device users for years. Here's the pseudo-class that finally fixed that false choice.