optimistdigital/nova-tailwind
optimistdigital/nova-tailwind integrates Tailwind CSS with Laravel Nova, giving admin panel developers a modern utility-first styling layer for customizing Nova resources, dashboards, forms, and UI components. Build cleaner, more consistent Nova interfaces faster.
This is a build-time asset helper for Nova component authors, not something you install in the host app to "make Tailwind work in Nova." Nova ships its own pre-purged Tailwind build, so any utility classes you write inside a custom Nova card, tool, field, or resource view simply won't exist in the browser unless your build emits them. This package hands you the shared baseline — a Tailwind preset, an entry CSS, and a small Composer service provider for publishing — so the utilities you author match Nova's own look.
Where to look first
tailwind.config.js / preset. This is the real value; everything else is a thin wrapper.vendor:publish (it exposes the config and CSS entry).vendor/laravel/nova/public — the baseline you're trying to match, and the thing you'll be tempted to fight.Minimal steps (Nova 3-era, laravel-mix stack)
Install it in the component package that defines the Nova component — never in the host app's root composer.json, where it does nothing:
composer require outl1ne/nova-tailwind --dev
The package was originally published as optimistdigital/nova-tailwind and later republished under outl1ne/*. Confirm which name your lockfile actually resolves and be consistent with it.
Publish the config/CSS entry, then pick the nova-tailwind entry from the list:
php artisan vendor:publish
Verify the exact publish tag/provider name against the package's service provider — this is the step most likely to differ from the docs.
Extend the preset from your component's own tailwind.config.js rather than copying it outright:
// tailwind.config.js
module.exports = {
presets: [require('./vendor/outl1ne/nova-tailwind/tailwind.config.js')],
content: [
'./resources/js/**/*.vue',
'./resources/js/**/*.js',
'./resources/views/**/*.blade.php',
],
};
Point laravel-mix at the published CSS entry, build, and register the output with Nova in your package's service provider:
Nova::style('my-nova-card', __DIR__.'/../dist/css/card.css');
First use case: a KPI card. Build one small Vue card with a handful of spacing/color/typography utilities, confirm the classes appear in dist/css/card.css, then confirm they render inside the Nova dashboard. That single loop exercises the whole pipeline — purge globs, preset, build, and Nova registration — in about an hour.
Preset + per-package content globs. The canonical shape is: one shared preset (this package, or your forked copy of it) and each component package declaring its own content globs. The preset gives you theme agreement; the globs keep purge honest per package. Don't let each package grow its own theme.
Register CSS per component, not globally. A card package should ship its own compiled CSS and call Nova::style() for it. This keeps the blast radius of a build failure to one component and lets a broken package be disabled without touching the whole admin panel.
Build inside the component package. Nova tools scaffolded with nova.mix.js get their own webpack pipeline; cards and fields often need one added. Whichever path you take, keep the Tailwind build inside the package's own package.json/webpack.mix.js so composer install && npm install && npm run prod reproduces it from a clean clone.
Safelist anything constructed dynamically. Vue components that compose class names ('text-' + tone + '-500') will be silently purged. Either write full literal class strings or use the safelist/purge options explicitly:
safelist: ['text-red-500', 'text-green-500', 'text-yellow-500'],
Prefer Tailwind utilities over hand-written CSS for layout, but leave Nova's chrome alone. Style inside your card's root element. Reaching out and restyling Nova's sidebar or resource tables with utilities is where preflight and specificity collisions start.
For multi-package monorepos, promote the preset to shared infrastructure. Once more than one component package depends on this thing, extract the preset into an internal package (or a top-level tailwind-preset.js) with a named owner and a version policy. That internal preset is the durable asset; the Composer wrapper is not.
Pair every component build with a visual check. A handful of screenshot diffs of a page hosting your custom card is enough to catch a purge regression that would otherwise reach an operator before it reaches CI.
It's archived — treat it as a vendored fork, not a dependency. Last release was 2021-11-12, the repo is archived, and there are zero downstream dependents, which means nobody is discovering incompatibilities for you. Pin the exact version, commit the lockfile, and vendor the resolved Tailwind config into your repository so a future composer update can't break a build mid-quarter.
The vendor rename is a real bookkeeping trap. optimistdigital/* → outl1ne/* means your lockfile, SBOM, and any security-scanner allowlists may disagree with the repo URL in your docs. Reconcile it deliberately; auditors will ask.
Purge fails open and silently. Missing classes produce no error, no warning, no log line — the component just renders unstyled. When something looks wrong, the first debugging step is always:
grep -o 'your-class-name' dist/css/*.css
If it's absent, the problem is content globs or a dynamically built class name, not Nova. Check glob resolution relative to the config file's location, which in a package build is often not the path you assumed.
content vs purge and JIT defaults are the likely Tailwind-version breakpoints. Anything written against a Tailwind 2-era config will not silently work under Tailwind 3+. Decide whether you're holding the toolchain at the 2021 envelope (Node 14–16, laravel-mix 6, PostCSS 8) or porting the preset to a modern chain, and pin whichever you choose — don't leave it to float.
Preflight collisions are subtle, not loud. The published CSS entry likely includes @tailwind base, and Nova ships its own reset. Double-reset shows up as slightly-off spacing, unexpected borders, or heading sizes that don't match Nova's chrome. If your card looks "almost right," try dropping @tailwind base from your entry and keeping only components and utilities.
On Nova 4+ this package is redundant. Nova's first-party nova.mix.js tooling and modern Vite builds make the functional equivalent roughly 20–40 lines of tailwind.config.js you own outright. If you're starting there, skip this package entirely and write the preset yourself — it's cheaper than adopting an archived dependency and unwinding it later.
Don't install it in the host app and expect Nova's own stylesheet to change. It only affects assets your build compiles. Nova's compiled CSS is not something this package rewrites.
Every Nova upgrade is an internal investigation. The package encodes assumptions about Nova's internals that expired around its archive date. When you bump Nova, budget time to verify your custom components still render — and check that the compiled CSS is still being registered, since Nova's asset APIs have moved across majors.
Known-good extension point: if you need to change anything, fork the preset, not the wrapper. It's the only part carrying real information, and lifting it lets you drop the Composer dependency entirely once Nova's native tooling covers the use case.
How can I help you explore Laravel packages today?