gregoriohc/laravel-nova-theme-classify
A custom theme for Laravel Nova that gives your admin panel a cleaner, more organized look. Drop-in install restyles navigation, cards, and resources with a classified, minimalist layout built for fast, readable dashboards.
Understand what you're actually installing. Despite the description ("custom theme package that gives your Laravel Nova admin panel a distinct, polished look"), the code is a theme classifier: it resolves which Nova theme is active and exposes it as a class you can scope CSS and Blade against. Do not expect a palette, typography, or spacing overhaul to appear after composer require. Read src/ and composer.json first — the whole package is tens of lines and one service provider, and the Composer constraints are your only truthful compatibility signal. There has been no release since 2019-06-08 and it targets the Nova 1.x/2.x era, so on Nova 4/5 or PHP 8.2+ assume it does not boot until you prove otherwise in a spike.
Spike on a throwaway branch, not in your app.
git checkout -b spike/nova-theme-classify
composer require gregoriohc/laravel-nova-theme-classify
Auto-discovery should register the provider. If discovery is disabled or you're on an older Laravel, add it manually in config/app.php:
Gregoriohc\LaravelNovaThemeClassify\LaravelNovaThemeClassifyServiceProvider::class,
Then confirm the provider boots and the admin panel still renders. A boot failure here takes down the entire Nova admin, so this is the first gate — not a formality.
Publish the stub, because the config is the API surface:
php artisan vendor:publish --provider="Gregoriohc\LaravelNovaThemeClassify\LaravelNovaThemeClassifyServiceProvider"
php artisan config:clear
Where to look first, in order:
composer.json — Nova/Laravel/PHP constraints against your pinned laravel/nova.src/ — the service provider plus any helper/facade. Five minutes of reading beats searching for issues on a 17-star repo.First use case: pick exactly one custom Nova card or tool that needs theme-aware styling. Inspect the DOM, verify the body class is present on that page, wire one scoped rule against it, and confirm it flips when you change the theme. Once that loop works, spread the pattern to other cards. Do not start by theming the whole admin panel.
If you're on Nova 4/5, skip the install and copy the pattern in-house: a config map of theme → classes, a resolver bound in the container, resolved once in a Nova::serving callback, emitted as a body class. That's an afternoon of work and it's yours to maintain.
Keep the mapping in config, not in Blade. The config file is the seam you'll actually touch day to day:
// config/theme-classify.php
return [
'default' => 'light',
'themes' => [
'light' => ['body' => 'theme-light', 'card' => 'bg-white'],
'dark' => ['body' => 'theme-dark', 'card' => 'bg-gray-900'],
],
];
When someone asks for a tenant-specific accent, this is the file you edit — one entry — rather than grepping views.
Resolve once per request, not per component. Do resolution in the service provider or a Nova::serving callback and emit the value a single time; read it back from a container binding or helper. Calling resolution inside every card view is how you get two different classes on the same page — and with a package this small there is no internal caching you can lean on.
Wrap the package behind your own helper. Even if you keep the dependency, add one indirection so a fork or reimplementation swaps a single file:
// app/Support/theme.php
function theme_body_class(): string { /* delegate to package */ }
function theme_class(string $key): string { /* theme lookup */ }
Then Blade and your tools call theme_class('card') and never reference the package namespace directly. This single decision is what makes vendoring cheap later.
Scope CSS to the emitted body class, and prefer custom properties over duplicated rules. Naive theming re-declares every component per theme; that's how you end up with hundreds of rules and a specificity problem. Instead:
body.theme-dark { --panel-bg: #111827; --panel-fg: #e5e7eb; }
body.theme-light { --panel-bg: #ffffff; --panel-fg: #111827; }
.card-panel { background: var(--panel-bg); color: var(--panel-fg); }
Adding a third (tenant/brand) theme is then a handful of variables, not a new rule set — which is the whole reason the classifier earns its keep.
Consume the same resolver inside custom tools and cards. Your custom Nova tool that renders its own wrapper should call theme_class(...), not hardcode classes. That guarantees the tool and the body agree, and it's the seam that makes per-tenant branding tractable.
Compose with Nova's own Tailwind classes; don't fight them. The package classifies and emits a class — it does not re-render Nova's shell. Apply scoped classes to your own cards, tools, and fields, and leave Nova's compiled styles alone.
Server-rendered theme selection is the clean path. If the active theme comes from config, a user preference, or a tenant, resolve it server-side so the body class is correct on first paint. Reach for a JS shim only if you genuinely need an instant client-side toggle without a round trip.
laravel/nova, Laravel, or PHP. Pin exact versions, keep the lockfile reviewed, and treat any conflict as an upgrade blocker — a cosmetic package that blocks a platform upgrade is a bad trade.composer remove + restore composer.lock) before this touches production.body.theme-* covers everything.!important-adjacent rules. Scope under your body class and use variables rather than escalating selector weight — otherwise a Nova minor bump silently breaks your theming.php artisan config:clear. Remember production caches config, so a deploy needs the same step.src/ — five minutes beats issue archaeology.Nova::serving callback, one body class.How can I help you explore Laravel packages today?