Add Bootstrap 5.3 color mode (data-bs-theme) support#435
Conversation
A new ColorModeHelper provides the two pieces that have been
missing for color-mode adoption since the BS5.3 migration:
- `script()` emits an inline IIFE that reads the user's stored
preference from localStorage (falling back to the OS preference
for `auto`), applies `data-bs-theme` to the configured target
(default `<html>`), re-resolves on `prefers-color-scheme`
changes while in auto mode, and exposes a tiny public API
(`window.BootstrapUIColorMode.{get,set}` plus a
`bs-theme-changed` DOM event). Placed in `<head>` it prevents
the flash of unstyled theme.
- `toggle()` renders a Bootstrap button group with one button per
configured mode. Clicks call `BootstrapUIColorMode.set()`,
update the active state, and announce the change via the event.
Configurable: storageKey, default, target, modes, labels (i18n
hook via `__()`), ariaLabel, wrapperClass, buttonClass,
activeClass.
The helper is auto-loaded by `UIViewTrait` as `$this->ColorMode`.
README has a new "Color modes" section under Usage.
Tests cover default and custom-config rendering for both methods
and verify user-supplied labels are HTML-escaped.
Per review: extract the three string constants (MODE_LIGHT, MODE_DARK, MODE_AUTO) into a BootstrapUI\View\Helper\ColorMode enum. The helper's `default` and `modes` config keys now accept either enum cases or plain strings interchangeably, so existing apps that pass `'light'` / `'dark'` / `'auto'` keep working while new code can use the typed API. A small `_modeValue()` helper coerces a value to its string form for places that render or serialize. Updates the README example to use the enum and notes that strings are still accepted. Adds tests covering enum usage on both `modes` and `default`.
|
Addressed in 2112231. Extracted the three string constants into a use BootstrapUI\View\Helper\ColorMode;
echo $this->ColorMode->toggle([
'modes' => [ColorMode::Light, ColorMode::Dark],
'default' => ColorMode::Dark,
]);A small |
ColorMode now implements Cake's EnumLabelInterface and provides its own translated label() — matches FormHelper's enum-label convention and removes the now-redundant 'labels' config on the helper. Custom string modes still fall back to ucfirst().
The previous default literally duplicated ColorMode::cases(). Use null as the sentinel for "all cases in declaration order" and keep the config key for apps that want to reorder, subset, or add custom theme strings.
There was a problem hiding this comment.
Pull request overview
Adds first-class Bootstrap 5.3 color mode (data-bs-theme) support to BootstrapUI by introducing a new ColorModeHelper (inline bootstrapper script + toggle UI), wiring it into UIViewTrait auto-loading, and documenting/covering it with tests.
Changes:
- Added
ColorModeHelperto emit a page-load script that applies the persisted/resolved theme and to render a button-group toggle. - Added a
ColorModeenum (implements CakePHP’sEnumLabelInterface) to provide translated labels and typed mode values. - Auto-loaded the helper via
UIViewTrait, updatedUIViewdocblock, added README docs, and added PHPUnit coverage.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/View/Helper/ColorModeHelper.php |
New helper implementing the inline theme-applier script and the toggle widget. |
src/View/Helper/Enum/ColorMode.php |
New enum for light/dark/auto with translatable labels. |
src/View/UIViewTrait.php |
Auto-loads the new helper as ColorMode. |
src/View/UIView.php |
Adds @property for the new helper. |
tests/TestCase/View/UIViewTest.php |
Verifies the helper is auto-loaded on UIView. |
tests/TestCase/View/Helper/ColorModeHelperTest.php |
Covers default/custom rendering of script() and toggle() and enum label behavior. |
README.md |
Documents usage, JS API, event, and configuration keys for color modes. |
Comments suppressed due to low confidence (1)
src/View/Helper/ColorModeHelper.php:128
- The inline JS in
toggle()also embeds config-derived strings viajson_encode()(default mode /activeClass) inside a<script>tag. For the same reason as inscript(), this should use safe encoding flags (egJSON_HEX_TAG | ...) to avoid</script>-based breakouts if these values contain</>.
. 'var active=(window.BootstrapUIColorMode&&BootstrapUIColorMode.get())||'
. json_encode($this->_modeValue($config['default']))
. ';'
. 'var ac=' . json_encode($config['activeClass']) . ';'
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…alStorage guards. - Toggle click handler now syncs aria-pressed alongside the active class via a shared sync() helper, so assistive tech sees the same state as visual users. - Config values inlined into <script> are encoded with JSON_HEX_TAG + HEX_AMP/APOS/QUOT (centralized in _jsonValue()) so a stray </script> in storageKey/target/activeClass cannot terminate the script tag. - localStorage.getItem/setItem calls wrapped in try/catch — when storage is blocked (private mode, security policy) the script still applies the theme rather than throwing and leaving an unstyled flash. - _modeValue() docblock no longer overpromises lower-casing; raw strings keep their casing so custom-theme registrations are preserved.
Summary
Adds Bootstrap 5.3 color mode (
data-bs-theme) support — the flagship BS5.3 feature that the plugin currently has zero coverage for (agrep -rn 'data-bs-theme\|color-mode' src/ templates/ webroot/againstmasterreturns nothing).The new
ColorModeHelperprovides the two pieces an app needs to actually use color modes without writing custom JS:$this->ColorMode->script()— applies the stored theme on page loadEmits a small inline IIFE that:
localStorage(default key:bs-theme),autoagainstwindow.matchMedia('(prefers-color-scheme: dark)'),data-bs-themeon the configured target (default:<html>),automode,window.BootstrapUIColorMode.{get, set}(mode)for app code,bs-theme-changedDOM event whenever the resolved theme changes.Placed near the top of
<head>, it prevents the flash of unstyled theme that every other "add dark mode in the layout" approach has.$this->ColorMode->toggle()— ready-made switcher widgetRenders a Bootstrap
btn-groupwith one button per configured mode (default: light / dark / auto). Clicking a button callsBootstrapUIColorMode.set(), togglesaria-pressedand the active class, and fires the event. ARIA-labelled, escapes user-supplied labels.Usage
Config knobs (per-call or via
loadHelper):storageKey,default,target,modes,labels,ariaLabel,wrapperClass,buttonClass,activeClass. Labels go through__()if you pass them through; the helper itself escapes them.Wiring
src/View/Helper/ColorModeHelper.phpUIViewTraitas$this->ColorMode.@propertyadded toUIViewdocblock.Notes
bg-light/text-darkin places; those are not touched here. A follow-up could audittemplates/layout/examples/*andFlashHelper.phpfor hardcoded neutrals if you want full dark-mode parity in the bundled layouts.