r0073rr0r / laravel-theme-switcher
Laravel Jestream Livewire Theme (Dark/Light mode) Switcher Component
Package info
github.com/r0073rr0r/laravel-theme-switcher
pkg:composer/r0073rr0r/laravel-theme-switcher
Requires
- php: ^8.2
- laravel/framework: ^12.0
- laravel/jetstream: ^5.0
- livewire/livewire: ^3.0 || ^4.0
Requires (Dev)
- laravel/pint: ^1.25
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
README
A Laravel package for Jetstream and Livewire applications that provides a polished light, dark, and system theme switcher with persistence, package assets, and a Jetstream appearance form.
โจ Features
- ๐ Reusable Livewire theme toggle with
light,dark, andsystemmodes - ๐ค Optional persistence through cookies and the authenticated user's
theme_preference - ๐งฉ Jetstream-ready appearance form component for profile settings
- ๐จ Publishable package views, translations, CSS, and JavaScript
- โก Early
<head>bootstrap script to prevent theme flash before page paint - ๐ ๏ธ Configurable toggle order, animations, tooltip behavior, sizing, and rounding
Table of Contents
- Requirements
- Installation
- Updating
- Setup
- Usage
- Theme Toggle
- Appearance Form
- Translations
- Customization
- Notes
- License
- Contributing
๐ Requirements
- PHP 8.2+
- Laravel 12.x
- Livewire 3.x or 4.x
- Jetstream 5.x
๐ Installation
Install the package via Composer:
composer require r0073rr0r/laravel-theme-switcher
Publish the package config:
php artisan vendor:publish --tag=theme-switcher
Publish the package views if you want to override them:
php artisan vendor:publish --tag=theme-switcher-views
Publish the package translations if you want to customize the text:
php artisan vendor:publish --tag=theme-switcher-translations
Publish the package CSS:
php artisan vendor:publish --tag=theme-switcher-css
Publish the package JavaScript:
php artisan vendor:publish --tag=theme-switcher-assets
Run the migration so the package can persist the user's theme_preference value on the users table:
php artisan migrate
๐ Updating
When updating the package, republish the resources you actually use so your application gets the latest views, translations, and frontend files:
composer update r0073rr0r/laravel-theme-switcher php artisan vendor:publish --tag=theme-switcher-views --force php artisan vendor:publish --tag=theme-switcher-translations --force php artisan vendor:publish --tag=theme-switcher-css --force php artisan vendor:publish --tag=theme-switcher-assets --force
If you have customized any published files, review the changes before overwriting them.
โ๏ธ Setup
1. Import the package CSS
Import the published stylesheet into your application's main stylesheet, for example in resources/css/app.css:
@import './vendor/theme-switcher.css';
If your application uses the package views directly, Tailwind also needs to scan them so utility classes such as h-10, w-10, and rounded-full are generated.
For Tailwind CSS v4, add this to resources/css/app.css:
@source "../../vendor/r0073rr0r/laravel-theme-switcher/resources/views/**/*.blade.php";
If you published the package views instead, point Tailwind to your published copies:
@source "../views/vendor/theme-switcher/**/*.blade.php";
For Tailwind CSS v3, add the same paths to the content array in tailwind.config.js.
2. Import the package JavaScript
Import the published JavaScript into your application's main JS entry file, for example in resources/js/app.js:
import './vendor/theme-switcher';
The package JavaScript expects a global theme manager at window.masonTheme with methods compatible with:
window.masonTheme.getResolvedTheme() window.masonTheme.applyPreference(preference, { persist: true })
This keeps the Livewire component state synchronized with the browser theme state.
3. Add the startup script to your layout <head>
To apply the theme before the page paints, add the package head component inside your main layout <head>, for example in resources/views/layouts/app.blade.php:
<x-theme-switcher-head />
This is the recommended integration point because it renders the inline startup script with the current server-side preference and updates the dark class before the UI flashes.
If you prefer a plain include instead of a Blade component, you can use:
@include('theme-switcher::components.head')
๐ง Configuration
After publishing the package config, you can control the switcher's default behavior without editing package files:
return [ 'default_preference' => 'system', 'allowed_preferences' => ['light', 'dark', 'system'], 'cycle_order' => ['light', 'dark', 'system'], 'animations' => [ 'enabled' => true, 'duration' => 300, 'icon_transition' => true, 'hover_effects' => true, 'respect_reduced_motion' => true, ], 'persistence' => [ 'cookie_enabled' => true, 'cookie_name_preference' => 'theme_preference', 'cookie_name_theme' => 'theme', 'cookie_minutes' => 60 * 24 * 365, 'database_enabled' => true, ], 'ui' => [ 'show_system_option' => true, 'button_size' => 'md', 'rounded' => 'full', 'show_tooltip' => true, ], 'events' => [ 'dispatch_theme_changed' => true, 'dispatch_preference_updated' => true, ], ];
What these options are for:
default_preference: fallback preference when no authenticated or cookie state exists.allowed_preferences: restrict the package tolight,dark, orsystem.cycle_order: controls the order used by the header toggle button.animations.*: enables or disables hover and icon transitions, with reduced-motion support.persistence.*: decides whether the package writes cookies, updates the authenticated user, or both.ui.*: controls whethersystemis shown, the button size, shape, and tooltip behavior.events.*: lets you disable the package browser events if you want to manage sync manually.
Examples:
['light', 'dark']removes the system option from the toggle and the profile appearance form.- Setting
animations.enabledtofalserenders the same UI without motion transitions. - Setting
persistence.database_enabledtofalsekeeps the switcher cookie-based only.
๐งช Usage
Theme Toggle
Render the Livewire component wherever you want the theme toggle button:
<livewire:theme-switcher />
Or:
@livewire('theme-switcher')
The component:
- Reads the current theme preference from the authenticated user when available
- Falls back to cookies when no authenticated preference exists
- Cycles through the preferences defined in
theme-switcher.cycle_order - Persists the selected preference according to the
persistenceconfig - Updates the authenticated user's
theme_preferencecolumn when database persistence is enabled
Appearance Form
The package also registers a Livewire component alias for Jetstream profile settings:
@livewire('profile.update-appearance-form')
If you want to show it on the default Jetstream profile page, open:
resources/views/profile/show.blade.php
Then add this block near the top of the main content area, for example after the profile information form:
<div class="mt-10 sm:mt-0"> @livewire('profile.update-appearance-form') </div> <x-section-border />
If you prefer to reference the class directly, you can also use:
@livewire(\r0073rr0r\ThemeSwitcher\Livewire\Profile\UpdateAppearanceForm::class)
The form view rendered by that component comes from the package:
resources/views/profile/update-appearance-form.blade.php
If you publish the package views, you can override that file and adapt it to your own account settings flow.
๐ Translations
The package loads its own translations automatically through the theme-switcher namespace, so publishing translations is optional.
Example keys:
__('theme-switcher::theme-switcher.appearance') __('theme-switcher::theme-switcher.save')
How translation fallback works:
- If the application does not publish translations, Laravel uses the package translation files directly
- If translations are published, Laravel will prefer
lang/vendor/theme-switcher - If the current locale does not exist, Laravel will try the application's
fallback_locale
This package already includes:
resources/lang/en/theme-switcher.phpresources/lang/sr/theme-switcher.php
That means a typical application with fallback_locale=en will still show English text even when the active locale has no dedicated theme-switcher translation yet.
๐จ Customization
After publishing, you can customize these package resources:
- Views:
resources/views/vendor/theme-switcher - Translations:
lang/vendor/theme-switcher - CSS:
resources/css/vendor/theme-switcher.css - JavaScript:
resources/js/vendor/theme-switcher.js
The startup head script is provided by the package view component:
- Component:
theme-switcher::components.head - Include/partial:
theme-switcher::components.head
๐ Notes
- The package CSS is intended to be included in the consumer application's build pipeline
- The package JavaScript is intended to be imported into the consumer application's JS entry file
- The startup head script should be rendered from Blade, not bundled as a plain JavaScript file, because it depends on server-side preference state
- The package no longer publishes unrelated third-party assets
- The authenticated user model is expected to support a
theme_preferencecolumn if you want per-user persistence - The service provider registers both
theme-switcherandprofile.update-appearance-formLivewire aliases - When
animations.respect_reduced_motionis enabled, motion utility classes are reduced for users who prefer less animation
โ Testing
The package test suite is written with Pest and covers configuration helpers, Livewire components, head bootstrap rendering, and persistence behavior.
Run the tests locally with:
php vendor/bin/pest
๐ License
This package is open-sourced software licensed under the MIT license.
๐ค Contributing
Pull requests are welcome. For larger changes, open an issue first so the scope is clear before implementation.