# Admin theming — the --cb- token layer, light / dark / auto

> > Scope: how the CBX backend decides its colour scheme and where its colours come from — the > data-bs-theme anchor, getAdminColorScheme per platform, the --…

Source: CBX documentation, version 3.x (released). Canonical page: https://docs.configbox.at/docs/technical/admin_theming. Last updated 2026-08-25.

---
> **Scope:** how the CBX backend decides its colour scheme and where its colours come from — the
> `data-bs-theme` anchor, `getAdminColorScheme()` per platform, the `--cb-*` token set, the vendor
> overrides, and the rules custom admin CSS has to follow · **Last reviewed:** 2026-08-25

The CBX backend follows a **light / dark / auto** colour scheme, and every colour in it comes from one
token layer: `assets/css/admin-theme.css`. This page is the reference for that layer. For the *other*
admin stylesheet mechanism — the per-host anti-bleed files that adapt CBX to each host's chrome — see
[`com_configbox_host_stylesheets.md`](https://docs.configbox.at/docs/technical/host_stylesheets); host stylesheets **consume**
these tokens and do not define them.

---

## 1. The anchor: `data-bs-theme`

CBX's namespaced Bootstrap 5.3 uses the standard **`data-bs-theme="light|dark"`** colour-mode
attribute and defines its `--bs-*` variables per scheme. The `--cb-*` tokens hang off **the same
attribute**, so CBX's colours and Bootstrap's flip together rather than drifting apart.

There is no JavaScript in the decision. Three cases cover everything:

| Situation | What sets the scheme |
|---|---|
| An ancestor carries `data-bs-theme="dark"` | The explicit-dark block applies |
| An ancestor carries `data-bs-theme="light"` | Nothing overrides the light defaults — and the OS fallback is suppressed |
| No attribute at all (**auto**) | `@media (prefers-color-scheme: dark)` applies the dark tokens |

The auto block is guarded as `:root:not([data-bs-theme="light"]) …:not([data-bs-theme="light"])`, so an
explicit light choice beats a dark OS. That mirrors Bootstrap's own media-query colour-mode strategy.

## 2. How each platform resolves it — `getAdminColorScheme()`

`KenedoPlatform::getAdminColorScheme()` is the platform contract, and the answers differ because the
hosts differ:

| Host | Returns | Why |
|---|---|---|
| **Joomla** | the resolved scheme | The Atum admin template already stamps `data-bs-theme` on `<html>` from its own light/dark/auto setting, and CBX renders inside it — so it inherits for free. The method still reports the resolved value (an explicit `userColorScheme` cookie first, then the user's `colorScheme` profile parameter) for callers that need it server-side. |
| **WordPress** | `'light'` | The WP backend is light-only and gives no light/dark signal. Its admin colour schemes only restyle the menu, toolbar and accents; the content area CBX renders into stays light in all of them. |
| **Magento 2** | `'light'` | The Magento backend theme is light-only and has no dark mode. |
| **standalone** | `'auto'` | No host chrome and no host signal, so CBX follows the OS. |

**Why the two light-only hosts pin rather than report `auto`:** reporting `auto` would let
`prefers-color-scheme` darken *CBX alone*, leaving a dark CBX panel sitting inside permanently light
host chrome. Pinning light keeps the two in step. On those hosts
[`KenedoView`](https://docs.configbox.at/docs/technical/kenedo_view#51-the-wrapper-carries-the-manifest--getviewattributes-906)
stamps `data-bs-theme="light"` on the admin view wrapper, which also suppresses the OS fallback in
`admin-theme.css`.

> If WordPress or Magento ever ships an admin dark mode, that platform's `getAdminColorScheme()` is the
> one place to read its signal.

## 3. The tokens

Tokens are defined on **four roots**, not one: `.cb-content` (every CBX view wrapper, and where the
namespaced Bootstrap is scoped), `body.admin.com_configbox` and `.configbox-admin-index` (host page
wrappers that `admin.css` paints with `--cb-body-bg`, and which sit *above* `.cb-content`), and
`.kenedo-popup`.

**`.kenedo-popup` is a token root of its own for a reason.** `KenedoPopup.open()` clones the popup out
of the view wrapper and appends it to `<body>`, so it cannot inherit tokens from `.cb-content` — and on
a host where the body itself carries tokens, it would otherwise pick up the body's scheme instead of the
one its origin resolved to. `kenedo.js` stamps the origin's effective `data-bs-theme` onto the clone;
unstamped means auto, and the media block takes over.

The set, by group:

| Group | Tokens |
|---|---|
| **Surfaces** | `--cb-body-bg` (tracks Joomla Atum's grey via `--template-bg-light`), `--cb-surface`, `--cb-surface-2`, `--cb-surface-3`, `--cb-overlay` |
| **Text** | `--cb-text`, `--cb-text-strong`, `--cb-text-muted`, `--cb-text-faint`, `--cb-text-inverse` |
| **Borders** | `--cb-border`, `--cb-border-strong`, `--cb-border-subtle` |
| **Brand & links** | `--cb-brand`, `--cb-brand-contrast`, `--cb-link`, `--cb-link-hover`, `--cb-accent` |
| **Semantic** | `--cb-success`, `--cb-warning`, `--cb-danger`, `--cb-info`, `--cb-required`, each with a `-bg` twin |
| **Form fields** | `--cb-input-bg`, `--cb-input-text`, `--cb-input-border`, `--cb-input-focus-border` |
| **Buttons** | `--cb-primary` (+ `-border`, `-hover`, `-hover-border`), `--cb-btn-bg`, `--cb-btn-text`, `--cb-btn-border`, `--cb-btn-hover-bg`, `--cb-btn-hover-border` |
| **Elevation** | `--cb-shadow-sm`, `--cb-shadow`, `--cb-inset-shadow` |
| **Editor palette** | node/token fills for the calc, rule and matrix editors, tuned to stay legible on both schemes |

One token is not a colour and catches people out: **`--cb-select-caret`** is Tom Select's
single-select caret as a `background-image` SVG with the stroke colour baked into the data URI — it
cannot reference a variable, so it is swapped wholesale per scheme. Its stroke mirrors
`--cb-text-muted`; change one and change the other.

## 4. Vendor chrome

Two vendored widgets do not read CSS variables and need explicit per-scheme rules, which
`admin-theme.css` carries for both the explicit-dark and the auto-dark cases: **Tom Select** (the
enhanced dropdowns — control, dropdown, options, multi-select items, the remove button) and
**jQuery UI** (the availability datepicker and any sortable/dialog chrome).

Also here: the CBX heading logo is dark artwork, so it gets a legibility treatment on dark surfaces.

## 5. Rules for custom admin CSS

1. **Reference the tokens, never hardcode.** A hex value in custom admin CSS is a light-mode bug
   waiting for someone to switch to dark. Use `--cb-*` (and `--bs-*` where Bootstrap owns the
   component).
2. **Do not restyle in a `[data-bs-theme]` or media block.** Define your component once against the
   tokens; the tokens already flip. A colour whose only definition sits inside a dark block will not
   apply in the unstamped auto state.
3. **Edit values here, not in the consuming file.** `admin-theme.css` is the single source of colour;
   `admin.css`, `general.css`, the calc/rule/matrix editors and the per-property CSS all consume it.
4. **Check both schemes before calling it done.** Everything in the admin can be viewed light and dark;
   an interactive state that was only ever seen in one is only half-checked.
5. `.min.css` twins are build artifacts — regenerated by the packager, not edited.

- [Adding your own admin CSS and JS](https://docs.configbox.at/docs/customization/assets_and_amd)
- [Overriding views and templates](https://docs.configbox.at/docs/customization/overriding_views_and_templates)
- [Adding a custom property type](https://docs.configbox.at/docs/customization/custom_properties) — including its
  per-property stylesheet

## 6. See also

- [`com_configbox_host_stylesheets.md`](https://docs.configbox.at/docs/technical/host_stylesheets) — the per-host anti-bleed
  stylesheets, which consume these tokens
- [`com_configbox_kenedo_view.md`](https://docs.configbox.at/docs/technical/kenedo_view#51-the-wrapper-carries-the-manifest--getviewattributes-906)
  — the view wrapper that stamps `data-bs-theme` where the host does not
- `assets/css/admin-theme.css` — the token layer itself, documented inline
