Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Light Switch Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Check the prerequisite before anything else. The plugin only works if your panel already builds its own Tailwind theme. If you have never run php artisan filament:theme (or hand-created resources/css/filament/{panel}/theme.css and registered it with ->viteTheme(...)), stop and do that first. Installing Light Switch on a stock, non-custom-theme panel succeeds silently and renders nothing useful.

2. Install and pin the major that matches your Filament version.

composer require awcodes/light-switch
Package Filament
1.x 3.x
2.x 4.x
3.x 4.x & 5.x

Composer will usually resolve correctly, but assert it in composer.json if you run Filament 4 or 5 in production — a stray ^1.0 constraint against a Filament 4 app is an avoidable afternoon.

3. Add the views to your theme CSS. This is the step people skip:

/* resources/css/filament/admin/theme.css */
@source '../../../../vendor/awcodes/light-switch/resources/**/*.blade.php';

4. Register the plugin in the panel provider:

use Awcodes\LightSwitch\LightSwitchPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugins([
            LightSwitchPlugin::make(),
        ]);
}

5. Build and clear.

npm run build
php artisan filament:assets
php artisan optimize:clear

6. First use case: verify on /admin/login. Open the login page in a private window, toggle dark, hard-reload, and confirm the choice sticks. Then log in and confirm the authenticated panel agrees with the choice you made pre-login. Those two checks cover ~90% of the integration.

Where to look first when something is off:

  • vendor/awcodes/light-switch/resources/views/ — the actual Blade markup; if your HTML doesn't contain it, the plugin isn't rendering at all (gating problem, not styling problem).
  • vendor/awcodes/light-switch/src/Enums/Alignment.php — the authoritative list of positions; only Alignment::BottomCenter is documented by name, so read the enum rather than guessing case names.
  • vendor/awcodes/light-switch/src/LightSwitchPlugin.php — the whole public API is two methods; reading it takes two minutes and removes all ambiguity.
  • php artisan route:list --name=auth — the exact route names your enabledOn() strings must match.

Implementation Patterns

Explicit route scoping, always

The default route list has changed repeatedly across releases (auth.email removed in v1.0.2, the profile view removed in v2.0.1). Treat the default as an undocumented implementation detail and set it yourself:

LightSwitchPlugin::make()
    ->enabledOn([
        'auth.login',
        'auth.password',
        'auth.register',
    ]),

Note that enabledOn() replaces the defaults rather than extending them. Anything you want must be listed.

Multi-panel installs

Route matching uses Str::contains() against the current route name, so partial fragments are the idiomatic way to scope per panel:

// Admin panel provider
LightSwitchPlugin::make()->enabledOn(['admin.auth.login', 'admin.auth.password']),

// Customer panel provider
LightSwitchPlugin::make()->enabledOn(['app.auth.login', 'app.auth.register', 'app.auth.password']),

Each panel's plugin instance is independent, so position and scoping can differ between the back office and the customer-facing app. This is where the package earns its keep — a customer-facing login page is a first impression; an internal admin login is not.

Position tuning as a layout decision

Default is top-right. When your auth layout is heavily customized (MFA steps, SSO button blocks, legal footers), reposition rather than fight the default:

use Awcodes\LightSwitch\Enums\Alignment;

LightSwitchPlugin::make()->position(Alignment::BottomCenter),

Treat the position as part of the auth design review, not as a preference — check it at mobile and desktop breakpoints on every auth screen you enabled, not just login.

Align with defaultThemeMode

The switcher reads Filament's defaultThemeMode for its initial state (fixed in v2.0.2). If you set ->defaultThemeMode(ThemeMode::Dark) on the panel, verify the switcher actually reflects that on first paint — otherwise you get a visible flip on load, which defeats the entire purpose.

Local development workflow

Because the plugin ships Blade views that are Tailwind-scanned at build time, any change to your theme CSS or to a published/overridden view requires a rebuild. Keep npm run dev running alongside php artisan serve, and remember that php artisan serve alone will not pick up CSS changes.

Rollout pattern

Ship it behind your normal release process, not a separate one — the revert is removing one line from plugins() plus a rebuild. If you want a staged rollout, gate the plugin registration on a config/env flag at the panel-provider level:

->plugins(array_filter([
    config('features.auth_theme_switch') ? LightSwitchPlugin::make()->enabledOn([...]) : null,
]))

Gotchas and Tips

Silent failure without a custom theme. This is the number one trap. Install + register + no @source line = no error, no switcher. Before you accept a "doesn't work" bug report, grep your theme CSS for the vendor path.

@source is Tailwind v4 syntax. Filament 4/5 themes use it. If you are on Filament 3 (package 1.x) with a Tailwind v3 theme, the equivalent is adding the vendor glob to your tailwind.config.js content array instead. Confirm which Tailwind major your theme compiles with before pasting the README snippet verbatim.

enabledOn() replaces, it does not merge. Passing ['auth.login'] removes password reset and registration from the switcher. If the switcher "disappeared from the register page," this is why.

Str::contains() over-matches. 'auth.login' will match any route name containing that substring. Broad strings like 'auth' will light up every auth page in the panel including ones you didn't consider (2FA challenge, email verification, SSO callbacks). Verify with php artisan route:list --name=auth and prefer the most specific fragment that still achieves your intent.

Upstream default drift is a silent production change. Because defaults have changed between patch/minor releases, a composer update can quietly alter which pages show the switcher. Always set enabledOn() explicitly, and read the changelog on every bump.

Z-index collisions recur. v1.0.3 dropped the switcher to z-index: 40 specifically to stop it covering Filament notifications. That class of bug comes back the moment someone customizes the auth layout. If you see the toggle covering a toast, a validation banner, or an MFA prompt, this is the cause — and the fix belongs in your theme CSS override, not in a fork if you can help it.

Theme flash is the whole point. The pitch is "no bright flash for dark-mode users." Test on a throttled connection, cold cache, with dark mode preferred at the OS level. If you see a white flash before the theme applies, you have undercut the reason for installing the package — investigate whether the switcher's inline init runs before Filament's own theme boot.

Preference persistence is client-side only. There is no per-user database column, no migration, and no server-side preference. Scope and limitations follow from that: a user on a different browser or device starts fresh, and clearing storage resets them. Do not file a bug for it, and do not promise per-user persistence in a spec.

Check whether the preference carries across the login boundary. It may or may not, depending on how Filament's own panel theme bootstrapping runs. Test it explicitly — a flip right after sign-in is jarring and is the second most common complaint after the missing-custom-theme issue.

No translations, no custom palettes, no per-panel token overrides. The package is intentionally narrow. If a stakeholder asks for branded theme colors or localized labels mid-sprint, that is a scope change, not a config flag — log it as an explicit non-goal up front.

Extension points to check rather than assume. If the service provider registers publish tags, php artisan vendor:publish --provider="Awcodes\LightSwitch\LightSwitchServiceProvider" will let you override the Blade views. Read src/LightSwitchServiceProvider.php first; if there are no publish tags, override by CSS or fork. The codebase is small enough that a fork-and-patch is an hours-scale cost, which is the real reason a 48-star, single-maintainer dependency is acceptable here.

Debugging checklist, in order:

  1. View source on the auth page — is the switcher markup present? No → plugin registration, panel, or enabledOn() gating.
  2. Markup present but unstyled/invisible → missing @source line or stale asset build.
  3. Styled but wrong position → position() and layout/z-index conflicts.
  4. Works but wrong initial state → defaultThemeMode mismatch.
  5. Works in dev, not prod → npm run build wasn't run, or assets cached (php artisan filament:assets, optimize:clear).

Upgrade runbook entry. Every Filament major bump likely requires a Light Switch major bump. Add it to the panel upgrade checklist alongside filament/filament so it isn't discovered by a broken login page.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin