laravel-ux/dark-mode
Add dark mode to Laravel apps with a simple toggle UI and persistent user preference. laravel-ux/dark-mode offers a lightweight, Blade-friendly setup for theme switching, helping you deliver comfortable light and dark experiences without custom JavaScript.
Architecture fit. v0.1.0 is a narrowly scoped presentation-layer package: it owns the root dark class, a session-persisted Livewire toggle, an Alpine store bridge, and a browser event contract (dark-mode-toggled). That is a clean fit for Laravel apps already standardized on Blade + Livewire + Alpine + Tailwind's darkMode: 'class' strategy. It is a poor fit for apps using CSS prefers-color-scheme-only theming, non-Tailwind CSS stacks, or Vue/React frontends. The package deliberately spans three state channels — server session, Alpine store, and DOM class — which is the correct design for Livewire apps but makes it a cross-cutting concern rather than a drop-in widget.
Integration feasibility. High for greenfield Laravel apps: one Blade directive in the layout, one component in the nav, and a Tailwind config flag. The @darkModeScripts directive is the load-bearing piece — it must render before first paint to avoid a theme flash, which means placement in <head> relative to Livewire/Alpine script tags is an integration constraint, not a detail. Retrofitting into an app with an existing hand-rolled dark mode is materially harder: two writers to the same dark class and two listeners on the same storage key will fight, and the session-backed state may disagree with localStorage.
Technical risk. Moderate, concentrated in four areas:
v0.1.0 implies breaking changes in minor releases. The directive name, component alias ux.dark-mode::toggle, event name, and store key are all public contract with no stability guarantee.wire:navigate soft reloads create plausible desync paths (Alpine store reinitializing on navigate, session write racing the DOM toggle, cross-tab drift).0.1.x.Key questions.
@darkModeScripts emit a blocking inline script safe for first paint, and is it CSP-nonce aware?dark class server-rendered from session, client-rendered, or both? This determines full-page-cache viability and cache-vary requirements.wire:navigate — does the Alpine store and toggle state survive, or reinitialize?prefers-color-scheme on first visit before any user choice exists?aria-pressed/aria-checked, keyboard, focus ring, contrast)?Stack fit. Requires Laravel with Blade, Livewire (v3-era expected), Alpine, Tailwind with darkMode: 'class', and the Laravel UX UI + Icons components. Confirm each is already present or acceptable before adoption; the UX UI/Icons dependency is the most likely to be a blocker for teams with an established design system.
Migration path. Two distinct paths:
localStorage keys, media-query listeners, class writers, and any inline head scripts. Then migrate storage to whatever the package expects and reconcile any persisted user preference. Do not run both in parallel; the overlap window is where the bugs live.Compatibility. Pin an exact version (0.1.0, not ^0.1) — pre-1.0 caret ranges admit breaking changes. Verify the supported PHP/Laravel/Livewire matrix before committing. Check interaction with wire:navigate, any full-page cache layer, and CSP. Plan for a Tailwind config change and a layout template change, both of which touch shared surface area across every page.
Sequencing.
<head>, toggle in the nav, verify no flash and no CSP violation.wire:navigate persistence, and session behavior against the event contract.Maintenance. Low code volume, but non-zero attention: pre-1.0 means every minor release is a potential breaking change requiring review. Exact version pinning plus an owner for upgrades is mandatory. If the package goes quiet, the fallback is small — the directive is a few lines of inline script and the toggle is a Blade component — so the exit cost is low, which is the main mitigant.
Support. Documentation covers installation, usage, events, customization, and AI assistance, which is more than most 0.1.0 packages offer. The dark-mode-toggled event is the extension point; treat it as the supported customization seam and avoid depending on internals. No SLA, no LTS, likely single-maintainer — plan accordingly.
Scaling. Negligible runtime cost: one class write and an Alpine store update per toggle. Watch two things at scale: session writes per toggle (cheap, but non-zero under heavy interaction) and caching. If theme is server-rendered from session, full-page caches must vary on that state or the cache will serve the wrong theme; if it is client-rendered pre-paint, caching is unaffected. Confirm which before turning on any page cache.
Failure modes.
wire:navigate or cross-tab toggling.Ramp-up. Under a day for a developer comfortable with Blade, Livewire, and Alpine: the directive placement, the component tag, and the event name are the whole API. Expect a further half-day for a11y verification and CSP validation, and roughly a day of cleanup if replacing an existing dark mode implementation. The Boost skill lowers the floor for teams using AI assistance but does not substitute for reviewing the generated integration — mandate PR review of any AI-scaffolded output.
How can I help you explore Laravel packages today?