alizharb/laravel-themer
Enterprise-grade theme management for Laravel. Create, clone, activate, and safely delete themes with per-theme Vite builds, NPM workspaces, asset shortcuts, view overrides, and Livewire 4 support. Includes metadata, wizards, and fast production caching.
Laravel Themer integrates seamlessly with Vite for modern asset compilation with hot module replacement.
Each theme gets its own vite.config.js:
themes/mytheme/vite.config.js:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: [
'resources/assets/css/app.css',
'resources/assets/js/app.js',
],
publicDirectory: '../../public',
buildDirectory: 'themes/mytheme',
}),
],
});
Laravel Themer automatically configures NPM workspaces in your root package.json:
{
"workspaces": [
"themes/*"
]
}
This allows you to manage all theme dependencies from the root:
npm install
php artisan theme:dev mytheme
This runs npm run dev in the theme's directory with hot module replacement.
php artisan theme:build mytheme
This compiles and minifies assets for production.
[@vite](https://github.com/vite)(['resources/assets/css/app.css', 'resources/assets/js/app.js'], 'themes/mytheme')
The second parameter specifies the theme's build directory.
For non-Vite assets (images, fonts, etc.):
<img src="{{ theme_asset('images/logo.png') }}" alt="Logo">
<link rel="stylesheet" href="{{ theme_asset('css/custom.css') }}">
<script src="{{ theme_asset('js/custom.js') }}"></script>
resources/assets/
├── css/
│ ├── app.css # Main entry point
│ ├── components/
│ │ ├── buttons.css
│ │ ├── forms.css
│ │ └── cards.css
│ ├── layouts/
│ │ ├── header.css
│ │ ├── footer.css
│ │ └── sidebar.css
│ └── utilities/
│ ├── colors.css
│ └── spacing.css
├── js/
│ ├── app.js # Main entry point
│ ├── components/
│ │ ├── dropdown.js
│ │ ├── modal.js
│ │ └── tabs.js
│ └── utils/
│ └── helpers.js
├── images/
│ ├── logo.png
│ ├── hero.jpg
│ └── icons/
└── fonts/
├── inter-regular.woff2
└── inter-bold.woff2
php artisan theme:npm mytheme "add -D tailwindcss postcss autoprefixer"
php artisan theme:npm mytheme "exec tailwindcss init -p"
themes/mytheme/tailwind.config.js:
/** [@type](https://github.com/type) {import('tailwindcss').Config} */
export default {
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
"./app/Livewire/**/*.php",
],
theme: {
extend: {
colors: {
primary: '#6366f1',
secondary: '#8b5cf6',
},
},
},
plugins: [],
}
themes/mytheme/resources/assets/css/app.css:
[@tailwind](https://github.com/tailwind) base;
[@tailwind](https://github.com/tailwind) components;
[@tailwind](https://github.com/tailwind) utilities;
[@layer](https://github.com/layer) components {
.btn-primary {
[@apply](https://github.com/apply) bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90;
}
}
php artisan theme:npm mytheme "add alpinejs"
themes/mytheme/resources/assets/js/app.js:
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();
// Custom Alpine components
Alpine.data('dropdown', () => ({
open: false,
toggle() {
this.open = !this.open;
}
}));
<div x-data="dropdown">
<button [@click](https://github.com/click)="toggle">Toggle</button>
<div x-show="open" x-cloak>
Dropdown content
</div>
</div>
By default, assets are published when a theme is activated:
php artisan theme:activate mytheme
# Publish specific theme
php artisan theme:publish mytheme
# Publish all themes
php artisan theme:publish
Configure in config/themer.php:
'assets' => [
'symlink' => env('THEMER_SYMLINK', true),
],
Symlink (Development):
Copy (Production):
Child themes automatically inherit parent theme assets:
base-theme/resources/assets/
└── css/variables.css
corporate-theme/resources/assets/
└── css/app.css (can import ../../../base-theme/resources/assets/css/variables.css)
In CSS:
[@import](https://github.com/import) '../../../base-theme/resources/assets/css/variables.css';
:root {
--primary-color: var(--base-primary);
}
In JavaScript:
import { helper } from '../../../base-theme/resources/assets/js/utils/helpers.js';
helper.doSomething();
resources/assets/
├── images/
│ ├── logo.png
│ ├── hero.jpg
│ └── icons/
│ ├── home.svg
│ └── user.svg
└── fonts/
├── inter-regular.woff2
└── inter-bold.woff2
[@font-face](https://github.com/font-face) {
font-family: 'Inter';
src: url('../fonts/inter-regular.woff2') format('woff2');
font-weight: 400;
}
.hero {
background-image: url('../images/hero.jpg');
}
<img src="{{ theme_asset('images/logo.png') }}" alt="Logo">
php artisan theme:build mytheme
This:
Vite automatically handles cache busting via content hashing:
<link rel="stylesheet" href="/themes/mytheme/assets/app-abc123.css">
Add preload hints for critical assets:
[@vite](https://github.com/vite)(['resources/assets/css/app.css'], 'themes/mytheme')
<link rel="preload" href="{{ theme_asset('fonts/inter-regular.woff2') }}" as="font" type="font/woff2" crossorigin>
Always compile CSS/JS through Vite, not direct linking.
Group related styles and scripts:
css/
├── components/
│ └── button.css
js/
├── components/
│ └── button.js
Import only what you need:
import { debounce } from 'lodash-es';
Use modern formats (WebP, AVIF) and responsive images:
<picture>
<source srcset="{{ theme_asset('images/hero.avif') }}" type="image/avif">
<source srcset="{{ theme_asset('images/hero.webp') }}" type="image/webp">
<img src="{{ theme_asset('images/hero.jpg') }}" alt="Hero">
</picture>
Define theme colors and spacing as CSS variables:
:root {
--color-primary: #6366f1;
--color-secondary: #8b5cf6;
--spacing-unit: 0.25rem;
}
How can I help you explore Laravel packages today?