Skip to main content
  • Created:
  • Updated:
  • Author:
    Takeshi Takatsudo

Form Control Styling

The Problem

Form elements — checkboxes, radio buttons, range sliders, textareas — have historically been the hardest parts of the web to style. Their rendering is deeply tied to the operating system, and browsers give CSS limited control over their appearance. As a result, developers reach for JavaScript libraries, custom toggle components built from <div> elements, or complex CSS hacks involving hidden inputs with sibling selectors. These workarounds bloat the bundle, break native accessibility, and diverge from the underlying HTML semantics.

Modern CSS now provides native properties to brand and customize form controls without replacing them.

The Solution

Four modern CSS properties replace most JavaScript-based form customization:

  • accent-color — Themes native checkboxes, radios, range sliders, and progress bars with a single declaration. The browser handles contrast automatically.
  • appearance: none — Strips the OS-native rendering of a form control, giving you a blank canvas to style from scratch with CSS.
  • field-sizing: content — Makes textareas and inputs automatically size to their content, eliminating the need for JavaScript auto-resize scripts.
  • caret-color — Changes the color of the blinking text cursor in inputs and textareas, providing a subtle branding touch.

Core Principles

Use accent-color as your first tool — it requires zero custom CSS beyond a single property and preserves full accessibility. Only reach for appearance: none when you need a design that goes beyond what accent-color can express. When you do use appearance: none, always restore it inside @media (forced-colors: active) so the control remains visible in Windows High Contrast mode.

accent-color on Native Controls

One line of CSS — accent-color: hsl(262 80% 50%) — themes every native control in the container. The browser automatically adjusts the checkmark and radio dot colors for contrast against the accent background.

Custom Checkbox with appearance: none

The appearance: none declaration strips the native checkbox rendering, then CSS rebuilds it: a border for the box, a clip-path polygon for the checkmark on ::before, and transform: scale() for the animation between unchecked and checked states. The @media (forced-colors: active) block restores appearance: auto so the checkbox remains functional in Windows High Contrast mode.

Custom Radio Buttons with Smooth Transitions

Custom radios follow the same pattern as checkboxes: appearance: none strips the native rendering, a ::before pseudo-element draws the inner dot, and transform: scale() smoothly animates the selection. The circular border-radius: 50% and smaller inner dot preserve the conventional radio button visual language so users immediately recognize the control.

Auto-Expanding Textarea with field-sizing

The field-sizing: content property eliminates the need for JavaScript auto-resize libraries. The textarea expands as the user types and shrinks when content is removed. Use min-height and max-height to set bounds so the textarea does not collapse to a single line or grow to fill the entire page.

Browser support note: field-sizing is supported in Chrome 123+ and Edge 123+ (released March 2024). Firefox and Safari do not yet support it. Use it as a progressive enhancement — the textarea falls back to its default fixed size in unsupported browsers.

Coordinated Form Theme

This form combines all four properties into a cohesive branded experience: accent-color themes the native checkboxes, caret-color matches the blinking cursor to the brand purple, and custom focus rings using box-shadow provide a consistent focus treatment across text inputs and textareas. The submit button follows the same color palette with proper :hover, :focus-visible, and :active states.

Code Examples

Quick Branding with accent-color

form {
accent-color: hsl(262 80% 50%);
}

This single line themes every checkbox, radio, range slider, and progress bar inside the form. No additional selectors needed.

Custom Checkbox from Scratch

.checkbox {
appearance: none;
width: 1.25rem;
height: 1.25rem;
border: 2px solid hsl(215 20% 70%);
border-radius: 0.25rem;
display: grid;
place-content: center;
cursor: pointer;
}

.checkbox::before {
content: "";
width: 0.65rem;
height: 0.65rem;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
background: white;
transform: scale(0);
transition: transform 0.12s ease-in-out;
}

.checkbox:checked {
background: hsl(262 80% 50%);
border-color: hsl(262 80% 50%);
}

.checkbox:checked::before {
transform: scale(1);
}

/* Restore native appearance in Windows High Contrast mode */
@media (forced-colors: active) {
.checkbox {
appearance: auto;
}
}

Auto-Sizing Textarea

.auto-textarea {
field-sizing: content;
min-height: 3rem;
max-height: 20rem;
}

Coordinated Form Variables

.form {
--accent: hsl(262 80% 50%);
--accent-ring: hsl(262 80% 50% / 0.15);
accent-color: var(--accent);
caret-color: var(--accent);
}

.form input:focus,
.form textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-ring);
outline: 2px solid transparent;
}

Common AI Mistakes

  • Replacing native controls with <div> elements: Building fake checkboxes and radios from <div> and <span> instead of using <input> with appearance: none. The fake controls lack keyboard navigation, form submission, and screen reader semantics.
  • Forgetting @media (forced-colors: active): Custom checkboxes and radios built with appearance: none become invisible in Windows High Contrast mode. Always restore appearance: auto inside a forced-colors media query.
  • Using JavaScript for auto-expanding textareas: Adding resize observers or input event listeners when field-sizing: content handles it natively (with graceful degradation).
  • Overriding all form styles when accent-color suffices: Writing dozens of lines of custom checkbox CSS when the design only needs a brand color applied to the native control.
  • Not providing focus indicators on custom controls: Stripping appearance without adding :focus-visible styles, leaving keyboard users with no visible focus state.
  • Hardcoding colors instead of using CSS custom properties: Making form theming inflexible by scattering color values throughout selectors instead of defining a single set of variables.

When to Use

  • accent-color: When you need to brand native form controls and the design does not require a custom shape or layout. This is the right default choice.
  • appearance: none: When the design demands a fully custom checkbox, radio, or select appearance that accent-color cannot express. Always pair with forced-colors restoration.
  • field-sizing: content: When textareas or text inputs should grow with their content. Use as progressive enhancement with min-height / max-height bounds.
  • caret-color: When the blinking cursor should match the brand color in text inputs and textareas. A small detail that signals a polished design.

References