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

Laravel Themer Laravel Package

alizharb/laravel-themer

Laravel Themer is an enterprise-grade theme management package for Laravel and TALL stack apps. It offers per-theme Vite builds, NPM workspaces, theme-aware view overrides, Livewire 4 integration, interactive CLI wizards, safe theme cloning/activation, and a fast zero-IO production cache.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and bootstrap. The package targets Laravel 11/12 with Livewire 4 support and is zero-config by design, so the happy path is two commands:

composer require alizharb/laravel-themer
php artisan themer:install

themer:install is interactive and does three things worth knowing before you run it: publishes config/themer.php, creates the themes directory (default themes/), and optionally rewrites your vite.config.js to use the bundled themerLoader (via vite.themer.js). If you decline the Vite step, the command prints manual instructions — copy them down, because asset isolation is the whole point of the package and you will hit a wall later without it.

Where to look first. In order:

  1. config/themer.php — the active theme key, theme path, discovery rules. This is the file you will actually edit.
  2. themes/{slug}/theme.json — the manifest. This is the contract: name, slug, version, parent, tokens, screenshots, tags, removable, disableable, requires, conflicts, provides. Read the schema before hand-editing; validation is strict.
  3. themes/{slug}/resources/views/ — where overrides live. The directory layout mirrors a normal Laravel app so overrides are intuitive.
  4. The docs site (alizharb.github.io/laravel-themer) for anything beyond the README.

First useful use case. Don't start with a full redesign. Start with the narrowest thing that proves the cascade:

php artisan theme:make "Dark Theme"        # generates scaffold + theme.json
php artisan theme:activate dark-theme

Then copy a single existing view — resources/views/welcome.blade.php — into themes/dark-theme/resources/views/welcome.blade.php, change one line, and reload. You should see the theme version with zero application code changes. Once that works, you understand the resolution order (Active Theme → Parent → Application) and everything else is scale.

Only after the view cascade is verified should you touch theme:dev --theme=dark-theme and Vite, because asset piping introduces a second failure surface (workspace resolution + dev server ports) that will mask view-resolution mistakes.

Implementation Patterns

The override cascade is the primary mental model. Theme resolution goes Active → Parent → Parent's Parent → App. That means a theme should contain only deltas. The anti-pattern is copying the entire resources/views tree into a theme "to be safe" — you immediately fork every view, and upstream bug fixes never reach the theme. Keep themes thin: override the layout, override three partials, inherit the rest. Multi-level nesting is supported (v1.1.0+), so brand-a → base → app is a legitimate structure.

Per-route theming via middleware. Global activation is rare in practice. The SetTheme middleware (v1.1.0+) is how you actually ship this:

Route::middleware('theme:dark-theme')->group(function () {
    Route::get('/dashboard', Dashboard::class);
});

Use this for admin-vs-public branding, per-tenant subdomains, or a marketing section that diverges from the app shell. Keep it narrow — middleware-based theming is easy to reason about per group, but scattered theme:name middleware across a large routes/web.php makes debugging view resolution genuinely painful.

Ephemeral switching for non-HTTP contexts. Themer::forTheme() (v1.1.0+) is the tool for emails, PDFs, and queued jobs where a route middleware never fires:

$html = Themer::forTheme('invoice-brand', function () {
    return view('invoices.pdf', $data)->render();
});

Wrap it in a closure and let it unwind the switch. This is the single most-used pattern in real deployments because emails and PDFs are where brand consistency actually gets noticed — and where global activation would leak into the web request.

Design tokens instead of hardcoded colors. v1.4.0 added tokens in theme.json with theme_token(), @themeTokens, and theme:tokens. Define brand colors, radii, and spacing per theme, then emit them once in your layout:

@themeTokens

This gives you a single place to answer "what is the primary color in theme X" and, importantly, a machine-readable surface for theme:tokens --css. Prefer this over hardcoded hex values in Blade — it's the difference between a themeable app and an app with two hardcoded palettes.

Asset workflow runs per theme. Each theme gets its own package.json and vite.config.js, plus NPM workspaces for shared dependencies. The daily loop:

php artisan theme:npm --theme=dark-theme install tailwind-merge
php artisan theme:dev --theme=dark-theme      # dev server
php artisan theme:build --theme=dark-theme    # production build

Treat the theme's package.json as the source of truth for that theme's assets. Don't try to reach across themes for imports — the isolation is deliberate and fighting it produces builds that work locally and fail in CI.

Production discipline: cache + diagnostics. The zero-IO discovery engine caches resource existence, which is where the performance story comes from. The trade-off is freshness. The pattern that works:

php artisan theme:refresh      # after adding/removing theme views, providers, or Livewire components
php artisan theme:doctor       # CI gate
php artisan theme:status

And for the "why is this view rendering" question — which will come up — php artisan theme:why dark-theme and theme:debug dark-theme --json are the intended answers. Put theme:doctor in CI; it catches circular parents, missing parents, and asset health before a deploy does.

Previewing before activating. For QA of an inactive theme, theme:preview dark-theme --signed --expires=30 produces a URL that resolves a non-active theme for a time-boxed session. This is how you review work from a designer without flipping the live site — wire it into your review workflow rather than temporarily calling theme:activate on staging and forgetting to revert.

Livewire follows the same cascade. php artisan make:livewire Header --theme=dark-theme targets the theme's Livewire directory, and component resolution walks the inheritance chain. The v1.1.0 fix for grandparent component registration matters here: if you're on an older patch, deeply nested Livewire components silently resolve to the wrong class.

Gotchas and Tips

This is a very young package — treat adoption as a bet. Eight stars, zero dependents, and a first release a short time ago. That is a real risk factor, not a footnote. Concretely: pin the exact version in composer.json, don't use ^ on a pre-1.x-if-possible boundary, and read the changelogs for breaking behavior before upgrading. Every release so far has shipped behavioral changes (v1.1.0 alone changed theme:make output and the boot phase). Budget for reading diff notes on each bump.

Cache staleness will bite you. The zero-IO discovery engine is the main performance feature and the main source of "I added the view and nothing happened." If a new theme view, provider, or Livewire class isn't resolving, run theme:refresh or theme:cache before you start debugging your Blade syntax. Make this reflex #1 — it will save hours.

removable and disableable are not decorative. theme:delete respects the removable flag in theme.json. If a theme refuses to delete, the manifest is doing its job, not malfunctioning. Check the flag before filing a bug. Same for disableable — a disabled theme will not activate regardless of what theme:activate says.

Naming and slug drift causes duplicate-discovery gremlins. There's a documented v1.1.2 fix for themes registered under multiple keys (name, slug, path) producing duplicates in ThemeManager::all(). The practical lesson: pick one slug convention, use it consistently in theme:make, --theme=, middleware, and theme.json, and never rename a slug by hand. Renaming means updating every reference — the package identifies themes by multiple keys internally, so a partial rename can leave a theme listed twice or not at all.

View namespace casing has a history. v1.1.0 fixed inconsistent casing in view namespace resolution and v1.1.2 fixed layouts:: / pages:: prefixes not registering as Blade component prefixes (so <x-layouts::app> failed). If you're on an older version and component tags mysteriously don't resolve, that's this. On current versions, still prefer explicit lowercase slugs — don't rely on case-insensitive matching.

Modular discovery needs a second package. laravel-modular is listed as required for modular theme discovery, and theme:check verifies required modules declared under requires in theme.json. If you're not using modules, ignore this entirely. If you are, install laravel-modular first or theme:check will report failures you can't fix from the theme side.

Livewire support is Livewire 4, not 3. The README says Livewire 4 explicitly. Upgrading an existing TALL app on Livewire 3 and expecting the component redirection to work is a mismatch that won't surface until runtime. Verify your Livewire version before adopting.

Route middleware ordering matters. theme:name sets context for the request; if you stack it with middleware that render views or capture output earlier in the chain, the theme won't be applied. Put theme: early in the group. And remember it only affects the current request — it does nothing for queued jobs, which is exactly why Themer::forTheme() exists.

Vite setup is the most likely manual-intervention point. If you declined the automatic vite.config.js rewrite during themer:install, you must wire themerLoader from vite.themer.js yourself. Symptom of getting this wrong: theme:dev starts, but assets 404 or the root app build picks up theme files. Re-run the installer or copy the documented config verbatim rather than improvising.

theme:make no longer generates a layout. v1.1.0 removed automatic layout generation deliberately. Generate layouts explicitly with php artisan livewire:layout --theme=dark-theme or write them by hand. Expecting a working scaffold out of theme:make and finding an empty views directory is a common first-ten-minutes confusion.

Preview URLs are a security surface. theme:preview with --signed is the safe form. An unsigned preview URL exposes an inactive theme to anyone who guesses it; with --expires it's time-boxed. Use --signed in shared or production-adjacent environments, and don't leave long-lived preview endpoints open.

Extend via the manifest, not by patching the package. theme.json already supports provides, requires, conflicts, and tokens, and the lifecycle events for caching, refresh, and preview give you hooks. Reach for those before forking — with a package this young and this few dependents, a fork means you own the maintenance entirely. If a genuine gap exists, contributing upstream is the better bet than a vendored copy.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core