david-griffiths/nova-dark-theme
A dark theme for Laravel Nova that gives the admin panel a sleek, low-light look and reduces glare during long sessions. Easy to install and configure, it restyles Nova's UI for comfortable everyday use.
Confirm the Nova version first — before the Composer command. This package's last tag is 2019-02-16, which puts it firmly in the Nova 1.x/2.x era. If php artisan nova:version or composer show laravel/nova reports 4.x or 5.x, stop here; you are not the audience for this package and the Artisan scaffolding will either no-op or mangle templates. On a legacy panel still on that era, continue.
Install pinned, on a throwaway branch:
git checkout -b spike/nova-dark-theme
composer require david-griffiths/nova-dark-theme:1.0.0 # exact version, no caret
Pin exactly. With one release in 2019 and zero dependents, there is no ecosystem testing this against anything; a caret range buys you nothing but surprise.
Register the provider if auto-discovery doesn't fire (check composer show david-griffiths/nova-dark-theme for a laravel section in its extra; if absent, add it to config/app.php providers).
Find the actual commands before running anything. The v1.0 release notes promise "artisan commands to quickly add [the toggle] to your Nova templates," but don't hardcode names from memory:
php artisan list | grep -i -E 'dark|theme'
First use case: the toggle on a single internal panel. Run the scaffolding command, then immediately git status and git diff. What you're looking at is the whole integration — a Vue component dropped into a Nova template (typically a published partial under resources/views/vendor/nova/, or a custom header/user menu slot) plus a stylesheet. Read that diff line by line before you commit anything. If the diff is small and touches only views you already own, you're in good shape. If it reaches into vendor/ or rewrites a published Nova view you've previously customized, revert and reconsider.
Verify against reality, not the README: open the dashboard, a resource index, a detail form with modals and a date picker, and reload mid-page. Toggle off and on. Confirm the preference survives a reload (if it doesn't, see the persistence section below).
Snapshot the scaffold diff as a deliverable. The scaffolding commands mutate your application files, and there is no documented dry-run or uninstall. So make the diff your artifact:
php artisan <dark-theme-scaffold-command>
git diff > .spike/nova-dark-theme-scaffold.diff
Store that in the ticket. It is your rollback script, your audit record, and your conflict baseline when the next Nova upgrade touches the same templates.
Gate the toggle behind a config flag for one release cycle. Don't let a cosmetic spike become an unconditional admin change:
// config/nova-dark-theme.php (or config/nova.php if scaffolding lands there)
'enabled' => env('NOVA_DARK_THEME_ENABLED', false),
Then guard the toggle's render path (a @if(config(...)) around the partial, or a Nova tool registered conditionally in NovaServiceProvider::tools()). Kill switch without a redeploy is worth ten minutes of work.
Settle persistence before you demo. The single design decision this package forces is where the preference lives, and the release notes don't answer it. Probe it:
users, verify it's nullable with a sane default, and run it in isolation on a copy of production data before trusting it with real rows.If you need real per-user persistence and the package doesn't give it, the cheapest extension point is a dark_mode boolean on users plus a tiny Nova field or tool that flips it — but at that point you're writing half the feature and should re-read the build-vs-buy math.
Drive it from a Nova Tool or Card instead of patching published views. The scaffolding edits templates; you don't have to. If your Nova install already has a custom tool or a nova-components/ folder, registering the toggle there keeps the change inside code you own and out of published vendor views — which is exactly where Nova upgrades hurt. This is the extension point most likely to survive.
Add an OS-level default yourself. The package gives you an explicit switch; it doesn't give you prefers-color-scheme. If you want "dark by default at night," write the fallback that seeds the preference on first visit:
<script>
if (!localStorage.getItem('nova-theme')) {
localStorage.setItem('nova-theme',
matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
}
</script>
Put it inline in <head> (see the flash-of-wrong-theme gotcha) rather than in a bundled asset.
Rollout order: internal panel only → flag on for the team → one release cycle → decide adopt/fork/discard. Never bundle with a schema or auth change; keep the blast radius attributable to one ticket.
Fork trigger, written down. "Fork if the toggle breaks on the next Nova major." The CSS and the toggle are small enough that forking is a contained cost, not a crisis — decide it deliberately rather than in an incident.
Dormant ≠ broken, but it does mean you own it. One release, 2019, 71 stars, zero dependents. That's not a maintained dependency; it's a vendored snippet with a Composer wrapper. Budget a review at every Nova major upgrade. MIT clears licensing; nothing clears maintenance.
Nova is closed-source — you cannot patch around incompatibilities. If the stylesheet's selectors don't match current Nova markup, there is no upstream fix and no vendor-layer workaround. Your only moves are fork the CSS or drop the package.
Check the Vue major against your Nova build. The toggle ships as a Vue component written for the Vue version Nova bundled in 2019. Vue 2 → Vue 3 is a breaking change for component APIs and lifecycle hooks. If Nova has moved, the component will fail to mount, and the failure is silent (a blank slot, not an exception). Verify by inspecting the rendered DOM for the toggle node, not by eyeballing the page.
Flash of wrong theme is real. If the theme class is applied by a Vue component on mount, dark-mode users get a white flash on every full page load. Fix by applying the stored preference class in an inline <head> script before paint, and let the Vue toggle own only subsequent changes.
CSP will bite the inline script. If you run a strict Content-Security-Policy on the admin panel, the inline <head> snippet needs a nonce or hash. The package predates any CSP guidance; handle it yourself or skip the no-flash fix.
The switch's accessibility is your responsibility. Release notes say nothing about it. Check: keyboard focusable, visible focus ring, role="switch" with aria-checked, an actual text label (not just an icon), and that it isn't the only way to reach the preference. Fix in your fork if missing — this is the kind of thing that gets flagged in a UX review.
localStorage is per-browser, not per-user. Fine for a single-operator internal panel; a support ticket generator on shared workstations. State the behavior in the ticket so nobody "discovers" it later.
Scaffolding commands may clobber published views. If you've customized a published Nova view previously, the injector can overwrite or duplicate content. Always run on a clean branch, always diff. No dry-run flag exists — git is the dry-run.
Uninstall is manual. Write the steps down before you merge:
composer remove david-griffiths/nova-dark-theme
git apply -R .spike/nova-dark-theme-scaffold.diff # or git checkout -- on each touched file
php artisan view:clear && php artisan config:clear
If that reverse-apply doesn't come out clean, you don't have an uninstall path and you shouldn't merge.
Clear Nova's caches after every scaffolding run. php artisan nova:publish, view:clear, and config:clear — a stale compiled view makes it look like the toggle didn't install when it simply wasn't recompiled.
When the leverage collapses, say so. The only genuinely Nova-specific value here is the Artisan commands. If you're on Livewire, Inertia+React, plain Blade, or a Nova major past this era, a CSS-variable sheet plus localStorage and a fifty-line toggle is cheaper, fully owned, and reverses trivially. Forking the stylesheet is a legitimate — and often the correct — outcome of this spike.
How can I help you explore Laravel packages today?