awcodes/light-switch
Filament plugin that adds light, dark, and system theme switching to panel auth pages. Configure the switcher's position with Alignment enums and control which routes display it via enabledOn(). Supports Filament 3, 4, and 5.
Architecture fit. This is a presentation-layer plugin with an unusually small architectural footprint: one registration line in Panel::plugins(), one @source directive in the theme CSS, and two configuration methods. It introduces no middleware, no migrations, no service-provider side effects beyond view registration, no queue workers, and no environment variables. For a TPM, that means integration risk is almost entirely front-end build risk rather than application-architecture risk — and front-end build risk is cheap to contain with a feature flag and a one-line revert.
The value proposition is narrow and honest: Filament panels already handle light/dark for authenticated screens, so this closes a gap on the unauthenticated surface — login, register, password reset, email verification. Judge it accordingly. If Filament is a back-office tool used by a handful of internal staff, the payoff is cosmetic and the answer is probably "no, or not now." If Filament Panels is the customer-facing application, the login page is a daily first impression and the payoff is real, if still small. The package does not attempt theme persistence per user, custom palettes, per-panel token overrides, or translations, and those should be logged as explicit non-goals in the ticket before anyone asks for them mid-sprint.
Integration feasibility. The public API surface is two methods, both documented and both stable across the observed release history: position(Alignment::…) and enabledOn([...]). Route targeting uses Str::contains() against route names, which is a pragmatic choice that enables panel-scoped fragments like admin.auth in multi-panel installs — genuinely useful in a multi-tenant or split admin/customer-panel estate, and worth exploiting rather than treating as an implementation detail.
The one real prerequisite is a custom Filament theme. The @source '../../../../vendor/awcodes/light-switch/resources/**/*.blade.php' line only works if the project already builds its own Tailwind CSS. Teams that have never created a custom theme will find the plugin installs cleanly and then appears to do nothing — this is the classic silent-failure path and the single most important gate to check before committing engineering time. It is also the item most likely to burn a sprint day invisibly, so it belongs in the acceptance criteria, not the "notes" section.
Technical risk. Four categories, in descending order of concern:
defaultThemeMode instead of hardcoding a default, which is correct behavior but signals the semantics have shifted under users. Related: the default route list has changed repeatedly (auth.email removed in v1.0.2, profile view removed in v2.0.1). Never rely on the default route list — always set enabledOn() explicitly, because a silent upstream default change is a silent production behavior change. Separately, because the preference is client-side only, verify empirically whether there is a flash of the wrong theme on first paint over a throttled connection; the entire pitch is "no bright flash for dark-mode users," so a flash would undercut the reason for adopting it.Key questions for engineering before approval:
@source path resolve correctly in our Vite/Tailwind setup?defaultThemeMode in panel config, and does the switcher's initial state actually match it on first paint?Effort and decision frame. Size this as a half-day to one-day engineering task in the happy path (composer require, panel registration, @source line, one build, one visual QA pass per auth screen), plus whatever theme scaffolding costs if no custom theme exists — that can be the dominant term and should be estimated separately, because it is a permanent addition to the project's build surface, not a one-off. Recommend a spike-gated approval: timebox two hours to prove the @source path builds and the toggle renders on the login page, then commit. If the spike fails, the fallback is a handful of Blade + Tailwind in the auth layout, which is also hours-scale but carries no vendor dependency at all.
Stack fit. Designed for Laravel + Filament Panels, distributed via Composer, rendering Blade views styled with Tailwind. It fits any project already on Filament 3, 4, or 5 with a custom theme compiled through Vite. It does not fit cleanly if: the project relies solely on Filament's stock CSS with no custom theme build; the team is on Filament 2.x or earlier (no compatible line exists); or the front-end is a separate SPA consuming Filament only as a JSON API, in which case the auth surface is not Filament-rendered and the plugin is inert.
Migration path. Effectively non-existent — that is the point. There is no data to migrate, no schema change, no persisted user preference to backfill (the setting is client-side), and no API contract to version. Rollback is: remove the plugins() entry, remove the @source line, rebuild assets, deploy. That reversibility is the single strongest argument for trying it: the cost of being wrong is one deploy cycle.
The one genuine migration consideration is theme provenance. If the project has no custom Filament theme, "installing this plugin" actually means "introducing a custom theme build pipeline." Treat that as a separate, larger decision with its own justification — it changes how CSS is authored, compiled, and cached forever, and every future Filament upgrade now includes a theme rebuild step. Do not let a cosmetic switcher smuggle in a build-system change.
Compatibility. Pin the package major to the Filament major explicitly and record the pairing in the upgrade runbook:
| Filament | Package | Notes |
|---|---|---|
| 3.x | 1.x | Legacy line; no longer the head of development |
| 4.x | 2.x or 3.x | 3.x also covers 5.x — prefer 3.x to minimize future churn |
| 5.x | 3.x | Current combined line |
Two compatibility caveats to carry forward. First, the combined 4-and-5 line in 3.x means a single dependency constraint spans two Filament majors; verify the resolved constraint in composer.lock rather than trusting the README table, and re-verify after every composer update. Second, because the plugin ships raw Blade views consumed by your Tailwind build via @source, its CSS is compiled into your bundle — a bad vendor path or a Tailwind version mismatch surfaces as a build failure or missing utility classes, not as a runtime error. That failure mode is loud at build time (good) but silent if the @source line is omitted entirely (bad).
Sequencing. Five steps, ordered so the cheapest failure happens first:
@source path resolves from the theme CSS file's location. Stop here if any answer is no.composer require awcodes/light-switch, register the plugin, add @source, build, load the login page locally. Success criterion is a rendered switcher that actually flips the theme. If the box expires, abandon to the in-house fallback.enabledOn() explicitly with the full route list — do not accept defaults — and set position(Alignment::…) to whatever survives the existing auth layout.Scope guardrails to write into the ticket. Explicitly out of scope: per-user server-side theme persistence, custom color palettes, translation/localization of labels, changes to the authenticated panel's theme behavior, and support for Filament < 3. State these up front — every one of them is a plausible mid-sprint request that would convert a one-day task into a multi-week one.
Maintenance. Near-zero steady-state cost, with three scheduled touchpoints. (1) A pin-and-verify step on every Filament major upgrade — the version matrix guarantees a package bump will eventually be required. (2) A review of the resolved Composer constraint after any dependency update, since the 3.x line spans Filament 4 and 5. (3) A quick read of the changelog before each bump, because this package's defaults have historically changed between minor releases (auth.email dropped in v1.0.2, profile view dropped in v2.0.1). Explicit enabledOn() configuration neutralizes the third almost entirely — which is why it is a requirement, not a recommendation.
Ownership should sit with whoever owns the Filament front-end, not the platform team. The blast radius is a single Blade partial on unauthenticated pages; routing it through a platform review board would cost more than the change.
Support. First-line support burden is low but non-zero and predictable: "the toggle doesn't appear" (almost always a missing @source line or a stale asset build), "the toggle overlaps something" (z-index or alignment), and "my theme choice didn't stick" (client-side storage cleared, or a privacy mode blocking local storage). Document these three in the internal runbook; they cover the large majority of expected tickets. There is no vendor SLA and no paid support tier — escalation path is upstream GitHub issues, with forking as the guaranteed backstop. Because it is MIT and Blade-only, a fork is a realistic contingency rather than a theoretical one; note the fork owner in the runbook so the decision is not relitigated under pressure.
Scaling. The plugin scales trivially in the traffic sense — it is static markup and CSS with no server-side per-request work beyond view resolution, no database reads, no external calls, no caching layer to invalidate. Ten users or ten million, the marginal cost is indistinguishable from zero. The scaling concern that does exist is estate-wide: in a multi-panel installation, each panel's Panel provider needs its own registration and its own enabledOn() list, so N panels means N configuration points to keep consistent. Manage that with a shared config helper or a base panel builder rather than copy-paste, or the panels will drift and only some will show the switcher. There is also a small CDN/edge consideration: because the preference lives in browser storage, users on shared or rotating devices will see the system default rather than their own choice — acceptable for auth pages, but confirm it does not conflict with any accessibility or brand-standardization commitment.
Failure modes. Ranked by likelihood:
@source line omitted → assets build without the plugin's classes, widget renders unstyled or invisible. Highest-probability failure and the hardest to notice because nothing errors. Mitigation: the spike in step 2 and a rendering assertion in QA.@source added but the Tailwind build not re-run, or the browser serving a cached bundle. Mitigation: cache-busting is Vite's default; confirm the deploy pipeline rebuilds on dependency changes.position() before reaching for CSS overrides.enabledOn(), and read the changelog on every bump.Every one of these is contained to unauthenticated pages and has a one-commit rollback. Nothing here can take down the authenticated application.
Ramp-up. Negligible. A developer unfamiliar with the package reads the README in five minutes and ships in under an hour, provided the custom theme exists. The real ramp-up cost is the custom theme prerequisite, and it is not the plugin's cost to bear — it is a pre-existing project capability that this change happens to surface. If the team lacks that capability, budget the theme scaffolding as its own justified line item, or take the in-house fallback (a small Blade component plus a few Tailwind classes in the auth layout) which requires no new dependency and no theme pipeline change at all.
Bottom line for the go/no-go. Approve as a low-cost, low-risk, fully reversible enhancement if a custom Filament theme already exists and Filament Panels serves a customer-facing auth surface. Defer if the theme prerequisite is unmet, since the plugin then becomes a pretext for a build-system change that deserves its own decision. The feature is cosmetic, the dependency is small and forkable, and the cost of being wrong is a single revert — but it should be sold to stakeholders as a small polish item, not an accessibility or UX-critical fix, because client-side theme preference is not a substitute for prefers-color-scheme support and does not by itself satisfy any accessibility conformance requirement.
How can I help you explore Laravel packages today?