jcmccoders/laratheme
Laratheme adds multi-theme support to Laravel 11/12: switch active theme via config/env, auto-register view namespaces, generate new themes with make:theme, and resolve theme views and public assets (CSS/JS/images) from dedicated theme folders.
Installation:
composer require jcmccoders/laratheme
php artisan vendor:publish --tag=theme-config --tag=theme-stubs
config/theme.php and resources/themes/stubs are published.Create Your First Theme:
php artisan make:theme mytheme
resources/themes/mytheme/ with:
views/ (for Blade templates)assets/ (for CSS/JS/images)stubs/ (optional custom stubs)Activate a Theme:
THEME_ACTIVE=mytheme in .env or update config/theme.php:
'active' => 'mytheme',
Use the Active Theme in Views:
resources/themes/{active}/views/ (e.g., @extends('mytheme.layouts.app')).Workflow:
users table with theme column).// Middleware: Set theme based on user preference
public function handle(Request $request, Closure $next) {
config(['theme.active' => auth()->user()->theme ?? 'default']);
return $next($request);
}
Integration with Blade:
@php $activeTheme = config('theme.active'); @endphp
<div class="theme-badge">{{ $activeTheme }}</div>
Organize Assets:
public/themes/{theme}/assets/.theme() helper:
<link rel="stylesheet" href="{{ theme('assets/css/style.css') }}">
mix-manifest to version assets dynamically:
// In a service provider
$mix = app()->make('mix');
$mix->setPublicPath(public_path("themes/{$theme}/assets"));
Fallback Assets:
config/theme.php:
'fallback' => 'default',
Namespace Views:
@extends('themes.' . config('theme.active') . '.layouts.app')
@section('sidebar')) in child views.Stub Customization:
make:theme stubs by publishing and modifying:
php artisan vendor:publish --tag=theme-stubs --force
webpack.mix.js stub for theme-specific asset compilation.public function show() {
$theme = config('theme.active');
if ($theme === 'dark') {
return view('themes.dark.pages.home');
}
return view('themes.light.pages.home');
}
return view('themes.' . $theme . '.pages.home', [
'theme_vars' => $theme === 'dark' ? ['bg_color' => '#121212'] : [],
]);
Accept-Language header):
public function handle($request, Closure $next) {
$theme = $request->header('X-Theme') ?? 'default';
config(['theme.active' => $theme]);
return $next($request);
}
return response()->json([
'data' => $data,
'theme' => config('theme.active'),
]);
View Resolution Order:
resources/views/{theme}/{view}.blade.phpresources/views/{view}.blade.php (fallback).@extends('layouts.app') instead of @extends('themes.dark.layouts.app')) will fail silently.Asset Paths:
<link href="/css/style.css">) breaks when switching themes.theme() helper:
<link href="{{ theme('assets/css/style.css') }}" rel="stylesheet">
Caching Issues:
php artisan view:clear
php artisan config:clear
Middleware Conflicts:
app/Http/Kernel.php.Database Migrations:
Schema::table('users', function (Blueprint $table) {
$table->string('theme')->default('default');
});
Verify Active Theme:
@dump(config('theme.active'))
Check File Existence:
ls resources/themes/{theme}/views/
ls public/themes/{theme}/assets/
Artisan Commands:
php artisan theme:list
php artisan theme:clear-cache
Custom Theme Providers:
class ThemeServiceProvider extends ServiceProvider {
public function register() {
$this->app->singleton('theme', function () {
return new CustomThemeManager();
});
}
}
ThemeManager class to add logic (e.g., theme validation, dynamic loading).Dynamic Theme Loading:
ThemeRepository:
class RemoteThemeRepository implements ThemeRepository {
public function getViewsPath(string $theme): string {
return storage_path("app/themes/{$theme}/views");
}
}
Theme Events:
ThemeSwitched):
// In the ThemeManager
event(new ThemeSwitched($oldTheme, $newTheme));
ThemeSwitched::listen(function ($event) {
Log::info("Switched from {$event->oldTheme} to {$event->newTheme}");
});
Theme Validation:
public function isValidTheme(string $theme): bool {
return File::exists(resource_path("themes/{$theme}/views/app.blade.php"));
}
Cache Theme Configuration:
$theme = session('theme') ?? config('theme.active');
Lazy-Load Assets:
@if(config('theme.active') === 'dark')
<link rel="stylesheet" href="{{ theme('assets/css/dark.css') }}" media="print" onload="this.media='all'">
@endif
Theme-Specific Routes:
php artisan route:cache
How can I help you explore Laravel packages today?