The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Set the control accent:root { accent-color: #2563eb; }View examples
Declare supported schemes:root { color-scheme: light dark; }View examples
Keep native appearanceselect { appearance: auto; }View examples
Remove native appearance.custom-check { appearance: none; }View examples
Restore checked state.custom-check:checked { background: currentColor; }View examples
Show keyboard focus:focus-visible { outline: 3px solid CanvasText; outline-offset: 3px; }View examples
Color the insertion caretinput, textarea { caret-color: #2563eb; }View examples
Signal link activationa[href] { cursor: pointer; }View examples
Signal unavailable actionbutton:disabled { cursor: not-allowed; }View examples
Use an image cursor safely.pan { cursor: url('/cursors/grab.cur') 8 8, grab; }View examples
Resize in the block axistextarea { resize: block; overflow: auto; }View examples
Resize both dimensions.editor { resize: both; overflow: auto; }View examples
Select a token atomically.copy-token { user-select: all; }View examples
Avoid accidental selection.drag-handle { user-select: none; }View examples
Ignore decorative hit testing.button-icon { pointer-events: none; }View examples
Adapt in forced colors@media (forced-colors: active) { .status { border-color: CanvasText; }; }View examples
Preserve essential color.color-swatch { forced-color-adjust: none; }View examples

Form controls sit at the boundary between document styling, browser behavior, and operating-system conventions. Preserve native semantics and keyboard operation, prefer light customization such as accent-color and color-scheme, and remove native appearance only when you can rebuild every visible state—including focus, checked, disabled, invalid, and high-contrast behavior. Cursor and selection styling should clarify interaction rather than contradict it.

Step by step

Detailed examples

01

Customize native widgets with the smallest effective surface

accent-color is inherited and influences supporting checkboxes, radios, ranges, and progress indicators without replacing their platform behavior. The browser may adjust the requested color to maintain legibility. Declare color-scheme so native widgets, scrollbars, and browser-provided surfaces can align with your supported themes; never assume every platform paints the same internal parts.

Theme native controls without rebuilding them
:root {
  color-scheme: light dark;
  accent-color: #2563eb;
}

fieldset {
  border: 1px solid color-mix(in srgb, CanvasText 35%, transparent);
}

input:disabled { opacity: .65; }
Back to quick reference ↑
02

Remove native appearance only with complete state styling

appearance:none suppresses a widget's decorative native rendering but does not change its HTML semantics or operating behavior. A checkbox may lose its visible check mark and a select may lose its arrow. If you opt out, restore recognizable geometry, focus, checked, disabled, invalid, and forced-color states. Prefer appearance:auto when platform consistency and accessibility matter more than exact branding.

Build a visible custom checkbox on the native input
.custom-check {
  appearance: none;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  margin: 0;
  border: 2px solid currentColor;
  border-radius: .25rem;
  color: #2563eb;
}
.custom-check:checked {
  background: currentColor;
  box-shadow: inset 0 0 0 3px Canvas;
}
.custom-check:disabled { opacity: .55; }
Back to quick reference ↑
03

Keep focus and text-entry feedback unmistakable

outline does not consume layout space and remains the most robust focus indicator. :focus-visible generally avoids rings for pointer focus while retaining them for keyboard and other modalities, but do not replace it with :focus { outline:none } without a proven alternative. caret-color affects the insertion caret in editable content and should contrast with the field background.

Use system-aware focus and a contrasting caret
:where(button, input, select, textarea, a[href]):focus-visible {
  outline: 3px solid Highlight;
  outline-offset: 3px;
}

input, textarea, [contenteditable] {
  caret-color: #2563eb;
}

@media (forced-colors: active) {
  :focus-visible { outline-color: Highlight; }
}
Back to quick reference ↑
04

Match cursor feedback to the real interaction

cursor keywords communicate an interaction but do not create one. Use pointer for actual links, not every button; use grab only where dragging is implemented; and retain native text cursors over selectable text. A custom cursor URL must end with a keyword fallback, may be size-limited by the user agent, and should not obscure the target or convey essential information alone.

Pair cursor states with implemented behavior
a[href] { cursor: pointer; }
.drag-handle { cursor: grab; }
.drag-handle[aria-pressed='true'] { cursor: grabbing; }
button:disabled { cursor: not-allowed; }
.text-region { cursor: text; }
.pan { cursor: url('/cursors/grab.cur') 8 8, grab; }
Back to quick reference ↑
05

Let users enlarge editors and long-form fields

resize offers a user-controlled resize affordance. Logical block and inline values follow the writing mode, while both permits two-axis resizing. For non-replaced containers, resize generally requires overflow other than visible or clip. Combine it with usable minimum and maximum sizes so controls remain discoverable without escaping their layout.

Create a bounded resizable editor
.editor {
  inline-size: min(100%, 42rem);
  min-inline-size: 16rem;
  min-block-size: 8rem;
  max-inline-size: 100%;
  max-block-size: 70vh;
  resize: both;
  overflow: auto;
}

textarea.comment { resize: block; }
Back to quick reference ↑
06

Constrain selection and hit testing narrowly

user-select:none is a convenience for controls such as drag handles, not copy protection; broad use frustrates copying, lookup, and translation. user-select:all is useful for short tokens, though a dedicated copy button should still report success. pointer-events:none removes an element itself from hit testing but does not necessarily prevent keyboard focus or all events involving descendants, so it is not a disabling mechanism.

Tune a copy field and decorative icon
.copy-token { user-select: all; }
.drag-handle { user-select: none; touch-action: none; }
.button-icon { pointer-events: none; }

/* Disable real controls with the HTML disabled attribute,
   not pointer-events alone. */
button:disabled { opacity: .55; }
Back to quick reference ↑
07

Survive forced colors and user contrast choices

In forced-colors mode, user agents may replace authored colors and other paint effects with a limited system palette. Preserve meaning with text, borders, and native states rather than color alone. System color keywords such as Canvas, CanvasText, and Highlight follow the active environment. forced-color-adjust:none is an exceptional escape hatch for truly color-dependent content such as a swatch, and shifts contrast responsibility back to the author.

Retain control boundaries in a forced palette
@media (forced-colors: active) {
  .field, .status {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
  }
  .status[aria-invalid='true'] {
    border-inline-start: .35rem solid Mark;
  }
}

.color-swatch { forced-color-adjust: none; }
Back to quick reference ↑

Local code tester

Theme controls without discarding their semantics

Try native accent styling, a carefully rebuilt checkbox, visible focus, and a resizable text area.

Runs in your browser
Preview

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. World Wide Web ConsortiumCSS Basic User Interface Module Level 4w3.org
  2. World Wide Web ConsortiumCSS Color Adjustment Module Level 1w3.org
  3. World Wide Web ConsortiumPointer Events Level 3w3.org
  4. WHATWGHTML Living Standard: Form controlshtml.spec.whatwg.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback