Making your module compatible with the Admin theme and dark mode
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.cssThat 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-surfaceis 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
currentColorfor SVG icons so they follow the text color. - Mind
color-schemeside 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
- On a Drupal site with the Admin theme installed, set it as the administration theme (
/admin/appearance). - 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"). - 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.
- 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
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion