jeremykenedy/laravel-darkmode-toggle
jeremykenedy/laravel-darkmode-toggle provides a simple, ready-to-use dark mode toggle for Laravel applications. Add a Blade component, configure its appearance, and let users switch between light and dark themes with their preference persisted across visits. Lightweight and easy to integrate.
Architecture fit
This is a presentation-layer package: a Blade component, a Livewire component, and framework-specific JS components (Vue, React, Svelte) that sit inside an existing layout and read/write a single user preference. It does not touch domain models, routing, queues, or the database beyond one configured persistence column (persist_field). That makes it low-coupling and easy to reason about architecturally — the main integration surface is the layout template plus one config file. The v2.0.0 release strengthens fit in two areas that mattered for adoption: true Livewire view parity across Tailwind/Bootstrap 4/Bootstrap 5, and env-var support for every config key, which removes the usual per-environment config-file drift.
Integration feasibility
Feasible for any Laravel 12/13 application on PHP 8.2+. Install is Composer + publish tags + component tag in the layout; no service provider wiring beyond discovery. The public surface is explicitly pinned by a backwards-compatibility contract test, and all new config options ship disabled, so an upgrade should be a version bump rather than a code change. The one genuine gate is the platform floor: composer.json still declares Laravel 10/11 support, but Composer will not resolve those branches because of CVE-2026-48019 with no patched release on either branch. Practically, this package now requires a Laravel 12+ application, and the major version number reflects that, not an API break.
Technical risk
role="menu", aria-checked, focus management, aria-hidden icons) changes output on all three CSS frameworks and on Livewire. Any app-level CSS overrides, snapshot tests, or E2E selectors keyed to the old structure will need review.persist_to_server instead of always writing to the profile; the toggle now reads persist_field instead of a hardcoded dark_mode column. Both are correct, but applications that silently depended on the old write path will see different data. Similarly, darkmode:switch/darkmode:update now write DARKMODE_* keys when those take precedence, so .env diffs will appear in environments that previously reported success while doing nothing.Key questions
^12.0|^13.0 so failure is explicit rather than a resolution error?.env write changes?Stack fit
Target stack is Laravel 12 or 13 on PHP 8.2–8.5, with Blade or Livewire rendering and Tailwind, Bootstrap 4, or Bootstrap 5 as the CSS framework. Vue, React, and Svelte consumers are supported through the newly published darkmode-js tag, which finally puts the components at the import path the README documents — previously nothing published them, so JS-framework consumers were effectively unsupported. Bootstrap 5.3 projects should evaluate data_attribute: data-bs-theme; projects wanting native controls and scrollbars to match should evaluate color_scheme. Both are opt-in.
Migration path
v-click-outside directive to work around the Vue mount bug: remove the workaround, since the undefined directive is gone.DARKMODE_CSS/DARKMODE_FRONTEND: re-run darkmode:switch once and confirm the framework actually changes, because the commands previously wrote the wrong keys and reported success.Compatibility
Public PHP/JS API, config keys, view names, component aliases, publish tags, and command signatures are unchanged and contract-tested — that claim is credible given the test expansion from 105 to 355 tests. Compatibility gaps are in three places the contract does not cover: rendered markup (ARIA rework), .env write targets, and Livewire persistence behavior. Treat those as the real regression surface.
Sequencing
persist_field, persist_to_server).data_attribute, color_scheme, sync_across_tabs) one at a time, each behind a release, so any regression is attributable..env command behavior change into the next config-management change window rather than bundling it with the version bump.Maintenance The maintenance burden drops relative to 1.x: the accessibility work, i18n via language files (previously shipped but unused while labels were hardcoded English), and the storage/markup guards remove the class of local patches teams typically carry. The countervailing cost is that any app that had forked or patched the Blade views to add ARIA support or translations now owns a merge. Budget a one-time reconciliation for forks.
Support
Support surface shifts toward configuration rather than code: env-var precedence (DARKMODE_* over UI_KIT_*), the frontend option inheriting from ui-kit.frontend, and the opt-in flags. The substring-matching fix for .env keys removes a class of silent no-op failures that would previously have generated hard-to-diagnose tickets — a net support win, but expect a short spike in "why did my .env change" questions. Translation coverage becomes an app responsibility if the default English labels are overridden.
Scaling
Runtime cost is negligible: one component render, one small init script, and a localStorage read. sync_across_tabs adds a storage event listener per open tab — fine at normal tab counts, worth a note for kiosk or dashboard-style deployments that hold many tabs open. No server-side scaling implications and no additional queries unless an app persists to the server.
Failure modes
The release closes most of the sharp ones: unguarded localStorage (previously threw on page load in storage-blocked browsers), empty class_name producing classList.toggle(''), out-of-range stored or default preferences causing a paint-then-switch flash, and the Vue directive that threw on mount. Remaining failure modes are configuration-shaped: an unsupported css_framework now fails validation instead of writing a broken config (note that this changes --no-interaction behavior — scripts relying on the old silent pass will now see a non-zero exit), and .env writes still depend on precedence being understood by whoever runs the command.
Ramp-up Low for a developer adding the toggle to a layout; moderate for a team upgrading across a framework version. The README is now accurate where it previously documented env vars that did not work — that documentation debt is paid, which shortens onboarding. The real ramp-up cost is accessibility literacy: teams should know what the ARIA menu-button pattern guarantees and how to verify it, rather than assuming the package's conformance claim covers their customized CSS. Plan a half-day of accessibility QA and a half-day of markup-diff review per application, plus the framework upgrade estimate where Laravel 10/11 is in play.
How can I help you explore Laravel packages today?