🎨CSS Native Element Theming

css

Modern CSS properties for theming native HTML elements — inputs, checkboxes, scrollbars, carets, and more — without reaching for custom components.

accent-color

Tint native form controls (checkboxes, radio buttons, range sliders, progress bars) with a single property. The browser automatically handles contrast for check marks and labels.

html {
  accent-color: orangered;
}

Target individual elements:

input[type='checkbox'] {
  accent-color: royalblue;
}

input[type='radio'] {
  accent-color: rebeccapurple;
}

input[type='range'] {
  accent-color: coral;
}

progress {
  accent-color: mediumseagreen;
}

Pair with color-scheme to let the browser adapt the control’s background and unchecked state:

html {
  accent-color: hotpink;
  color-scheme: light dark;
}

caret-color and caret-shape

Style the text cursor in editable fields.

input,
textarea,
[contenteditable] {
  caret-color: orangered;
}

Change the caret shape (support is still limited):

input {
  /* bar (default) | block | underscore */
  caret-shape: block;
}

/* Shorthand — colour and shape together */
input {
  caret: orangered block;
}

Use transparent to hide the caret entirely (useful for custom cursor implementations):

.custom-input {
  caret-color: transparent;
}

Adapt to theme:

:root {
  color-scheme: light dark;
}

input {
  caret-color: light-dark(royalblue, cornflowerblue);
}

color-scheme

Tells the browser which colour schemes your page supports. This affects all native UI: form controls, scrollbars, system colours, and the default page background.

/* Support both — browser follows OS preference */
:root {
  color-scheme: light dark;
}

/* Force a specific scheme */
:root {
  color-scheme: dark;
}

Apply per-element to create mixed-scheme UIs:

body {
  color-scheme: light dark;
}

/* Force this section to always be dark */
.dark-panel {
  color-scheme: dark;
}

/* Force this widget to always be light */
.light-widget {
  color-scheme: light;
}

Via HTML for the earliest possible application (before CSS loads):

<meta name="color-scheme" content="light dark" />

light-dark()

Returns one of two values depending on the active colour scheme. Requires color-scheme to be set.

:root {
  color-scheme: light dark;
}

body {
  background: light-dark(#ffffff, #1a1a1a);
  color: light-dark(#111, #eee);
}

a {
  color: light-dark(royalblue, cornflowerblue);
}

.card {
  background: light-dark(#f9f9f9, #2a2a2a);
  border: 1px solid light-dark(#ddd, #444);
  box-shadow: 0 2px 8px light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.4));
}

hr {
  border-color: light-dark(#eee, #333);
}

Build a full set of design tokens with light-dark():

:root {
  color-scheme: light dark;

  --text-primary: light-dark(#111, #eee);
  --text-secondary: light-dark(#555, #aaa);
  --bg-page: light-dark(#fff, #111);
  --bg-surface: light-dark(#f5f5f5, #222);
  --bg-elevated: light-dark(#fff, #2a2a2a);
  --border: light-dark(#ddd, #444);
  --accent: light-dark(royalblue, cornflowerblue);
}

Putting it all together

A minimal theme with all native elements styled:

:root {
  color-scheme: light dark;

  --accent: light-dark(royalblue, cornflowerblue);
  --text: light-dark(#111, #eee);
  --surface: light-dark(#fff, #1a1a1a);
  --border: light-dark(#ddd, #444);
}

html {
  accent-color: var(--accent);
  color: var(--text);
  background: var(--surface);
}

input,
textarea,
select {
  caret-color: var(--accent);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}

::selection {
  background: var(--accent);
  color: white;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}