The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Set the control accent | :root { accent-color: #2563eb; } | View examples |
| Declare supported schemes | :root { color-scheme: light dark; } | View examples |
| Keep native appearance | select { 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 caret | input, textarea { caret-color: #2563eb; } | View examples |
| Signal link activation | a[href] { cursor: pointer; } | View examples |
| Signal unavailable action | button: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 axis | textarea { 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
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.
:root {
color-scheme: light dark;
accent-color: #2563eb;
}
fieldset {
border: 1px solid color-mix(in srgb, CanvasText 35%, transparent);
}
input:disabled { opacity: .65; } 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.
.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; } 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.
: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; }
} 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.
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; } 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.
.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; } 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.
.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; } 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.
@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; } Local code tester
Theme controls without discarding their semantics
Try native accent styling, a carefully rebuilt checkbox, visible focus, and a resizable text area.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



