Making your module compatible with the Admin theme and dark mode

Last updated on
25 August 2026

Drupal core includes a new experimental administration theme named Admin (machine name: default_admin). The theme supports dark mode. The dark mode setting defaults to auto: users whose operating system is set to dark mode see the admin UI in dark mode without changing anything in Drupal.

This has a direct consequence for contributed modules. CSS that hard-codes light colors — white backgrounds, light gray borders, near-black text — renders unchanged in dark mode. The result is a white box on a dark page, or dark text on a dark background.

This page explains how to make your module's admin CSS work in the Admin theme (light and dark) while keeping it working in Claro and other legacy admin themes. In most cases the fix is a one-line change per declaration, with no theme detection and no second stylesheet.

How the Admin theme handles color

The Admin theme defines a set of semantic CSS custom properties (CSS variables). Every color in the theme's own CSS goes through these variables. They are defined in:

core/themes/default_admin/css/_variables/variables-color-semantic.css

That file is the canonical, commented list of the public color API. Each variable is defined with the CSS light-dark() function, so a single variable carries both its light and dark value:

--admin-color-bg-surface: light-dark(#fff, #2a2a2d);

What light-dark() is

light-dark() is a native CSS function (Baseline since mid-2024). It takes two color values — a light one and a dark one — and the browser resolves it to one of them based on the element's active color-scheme.

The Admin theme sets color-scheme from the user's dark mode setting:

  • Auto (default): color-scheme: light dark — the browser follows the OS preference.
  • Light / Dark: the scheme is forced, regardless of the OS.

Because the scheme is applied by the theme, all light-dark() values across the page flip together. Module maintainers normally do not need to call light-dark() themselves — using the semantic variables gets you dark mode support for free.

The pattern: variable with a fallback

Replace each hard-coded color in your admin-facing CSS with the matching Admin theme variable, keeping your current value as the fallback:


.my-module-panel { 
  background: var(--admin-color-bg-surface, #fff);
  color: var(--admin-color-text, #232429);
  border: 1px solid var(--admin-color-border, #d4d4d8);
}

What this does:

  • In the Admin theme, --admin-color-bg-surface is defined, so the browser uses it. In light mode it resolves to white; in dark mode it resolves to a dark surface color. Your module follows the theme automatically, including any future theme color changes.
  • In Claro, Seven, or any other theme, the variable is not defined, so the browser uses the second argument of var() — the fallback. Your module looks exactly as it does today.

The fallback should be the literal value your CSS uses now. Do not try to "match" the Admin theme in the fallback; the fallback only ever renders in themes that are not the Admin theme.

This is the entire compatibility story: one declaration serves the Admin theme (both modes) and every legacy theme.

Which variable to use

The tables below cover the variables contributed modules are most likely to need. The full list, with usage comments, is in variables-color-semantic.css (path above).

Backgrounds

Variable Use for Replaces values like
--admin-color-bg The page canvas behind everything #f3f4f9, #fff
--admin-color-bg-surface Cards, panes, tables, fieldsets — the default "white box" #fff
--admin-color-bg-surface-nested A panel recessed inside another surface #f0f0f0, #edeff5
--admin-color-bg-raised Elements floating above the page: dropdowns, tooltips, popovers #fff
--admin-color-bg-sunken Inset wells: code blocks, embedded/recessed regions #e2e5ec, #f5f5f5
--admin-color-bg-input Form control backgrounds #fff
--admin-color-bg-header Table header rows, section header tints #f3f4f9
--admin-color-bg-disabled Disabled control backgrounds #eaeaea

Text

Variable Use for Replaces values like
--admin-color-text Default body text #222330, #333
--admin-color-text-soft Secondary text: labels, descriptions, help text #55565b, #6b6b6b
--admin-color-text-loud Headings, titles, emphasized text #000, #222330
--admin-color-text-on-primary Text on a primary-colored fill (e.g. primary buttons) #fff
--admin-color-text-disabled Disabled text #82828c
--admin-color-icon Icon fills/strokes on neutral backgrounds #55565b

Borders

Variable Use for Replaces values like
--admin-color-border Default dividers and edges: cards, panes, controls #d4d4d8, #ccc
--admin-color-border-soft Subtle translucent hairlines rgba(0, 0, 0, 0.1)
--admin-color-border-loud Strongest dividers, e.g. under table headers #8e929c
--admin-color-border-input Form control borders #8e929c
--admin-color-border-table Thin table row dividers #d4d4d8
--admin-color-border-disabled Disabled control borders #bababf

Status colors

Each status family has three roles: the solid color (icons, borders, emphasis), the background tint (highlighted rows, message areas), and text that sits on that tint.

Variable Use for
--admin-color-error / --admin-color-bg-error / --admin-color-text-error Errors
--admin-color-warning / --admin-color-bg-warning / --admin-color-text-warning Warnings
--admin-color-success / --admin-color-bg-success / --admin-color-text-success Success

Interactive colors

Variable Use for
--admin-color-link / --admin-color-link-hover / --admin-color-link-active Links
--admin-color-primary / --admin-color-primary-hover / --admin-color-primary-active Primary action color (buttons, active states)
--admin-color-focus Focus ring color

Shadows

Variable Use for
--admin-shadow-1 Standard elevation (cards, dropdowns)
--admin-shadow-2 Higher elevation (dialogs, large overlays)

These are full box-shadow value lists, not colors:

box-shadow: var(--admin-shadow-1, 0 1px 4px rgba(0, 0, 0, 0.1));

Things to avoid

  • Do not use the primitive variables (--admin-color-gray-*, --admin-color-accent-*, --admin-color-white). They are internal building blocks: they do not adapt to dark mode on their own and are not part of the public API. Use the semantic variables above.
  • Do not write your own @media (prefers-color-scheme: dark) rules for admin UI. Users can force light or dark mode in the theme regardless of their OS setting, and legacy themes have no dark mode at all. A media query bypasses both. The semantic variables already resolve to the correct mode.
  • Do not target gin- class names or [data-gin-*] attributes. These are leftovers from the theme's origin and are being removed.
  • Check images. Icons and illustrations with baked-in white backgrounds stand out in dark mode. Prefer transparent backgrounds, and currentColor for SVG icons so they follow the text color.
  • Mind color-scheme side effects. In dark mode the browser also darkens UA-rendered pieces (scrollbars, unstyled form controls). If your module styles only part of a control (for example, a background but not the text color), set both from the variables so the pair stays consistent.

Using light-dark() directly

If your module needs a color the semantic set does not cover, you can use light-dark() yourself:


.my-module-badge {
  background: light-dark(#e7f1ff, #1d3a5f);
}

This is safe in legacy themes: when no color-scheme is set (or it is light), light-dark() resolves to its first argument, so Claro users see the light value. Note the browser support floor (Baseline mid-2024); if your module supports older browsers, provide a plain declaration before it as a fallback.

Prefer the semantic variables when one fits — they keep your module aligned if the theme's palette changes.

Testing your module

  1. On a Drupal site with the Admin theme installed, set it as the administration theme (/admin/appearance).
  2. Dark mode is auto by default. Switch your OS appearance to dark, or emulate it in browser DevTools (Rendering panel → "Emulate CSS media feature prefers-color-scheme").
  3. Walk through your module's admin screens in both modes. Look for: white boxes on dark backgrounds, dark-on-dark text, light borders that disappear, and images with white backgrounds.
  4. Re-test in Claro to confirm the fallbacks render your existing design unchanged.

Stability

The Admin theme is experimental (beta). Variable names can still change between releases without a deprecation cycle. Watch the theme's change records on drupal.org before each core minor release and update names as needed. The variables-color-semantic.css file in the release you target is always the authoritative list.


Generated with the help of an LLM.

Help improve this page

Page status: No known problems

You can: