Frontend
Start the CSS compiler beside the application server while you work:
maat tailwind --watchA new application ships with Tailwind CSS v4 and Alpine.js already wired up, and no Node anywhere in the stack. Tailwind’s utility classes style the starter kit’s pages; Alpine adds the small interactive bits — the mobile sidebar, the account dropdown — without a build step of its own.
No npm. Laravel compiles CSS with Vite and requires Node. Tailwind ships a self-contained binary, so Maat drives that instead: there is no
package.json, nonode_modules, and no build step other thanmaat tailwind.
resources/css/app.css
@import "tailwindcss" source(none);@source "../views";@plugin "@tailwindcss/forms";@import "tailwindcss" source(none)pulls in Tailwind’s base styles, utilities and variants.source(none)turns off Tailwind v4’s automatic content detection. Without it, Tailwind walks up from this file looking for a project root and scans every file it finds there for class names — which, in this monorepo, means every package underpackages/andexamples/.source(none)says “scan nothing automatically”;@sourcenames what to scan instead.@source "../views"is resolved relative toapp.cssitself, not to the working directory:resources/css/app.css→../viewsisresources/views. Every.khnum.htmlunder there is scanned for class names.@plugin "@tailwindcss/forms"loads the official forms plugin, which gives unstyled<input>,<select>and<textarea>elements a sane baseline instead of the browser’s native chrome.
Add more @source lines if you keep classes outside resources/views — a shared component library, a Dart file that builds class strings dynamically.
maat tailwind
maat tailwind # compile oncemaat tailwind --watch # recompile on every savemaat tailwind --minify # compile for productionmaat tailwind --input=resources/css/admin.css --output=public/css/admin.cssThis is Maat’s answer to npm run dev / npm run build. It reads resources/css/app.css and writes public/css/app.css by default; both are overridable with --input and --output, or by editing config/view.dart’s tailwind block.
Run it alongside maat serve, in a second terminal:
# terminal 1maat serve
# terminal 2maat tailwind --watchThey stay separate on purpose. Laravel keeps maat serve and npm run dev as two processes too; folding a CSS watcher into the application’s dev server would tangle two independent restart loops — one for Dart code, one for a completely different toolchain — into one.
A missing CSS entrypoint is a hard failure, not a silent no-op:
No CSS entrypoint at /path/to/app/resources/css/app.css. Create it with`@import "tailwindcss";`, or pass --input=<path>.The binary cache
The first run of maat tailwind on a machine downloads the platform-appropriate Tailwind standalone CLI — about 80 MB — into ~/.maat/bin/. It is cached per machine, not per project: every Maat application on the box shares the same download, keyed by version. maat new does not download it; the first maat tailwind in any project does.
| Setting | Env var | Config | Default |
|---|---|---|---|
| Cache directory | MAAT_HOME |
— | $HOME (~/.maat/bin/) |
| Tailwind version | TAILWIND_VERSION |
view.tailwind.version |
4.3.3 |
Set MAAT_HOME to redirect the cache — useful in CI, where you want the download counted and cached by your pipeline rather than repeated on every run.
Alpine.js
Alpine is loaded from a CDN with a pinned version and a Subresource Integrity
hash in resources/views/layouts/app.khnum.html:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.17.1/dist/cdn.min.js" integrity="sha384-x6MR287ZDHWauvRTQIBc3b1F++s/03J/rBHbpRGwBoYbrqIN+3wRx4EXbU1u9NGO" crossorigin="anonymous"></script>The browser refuses the script if its bytes do not match the SHA-384 hash.
When upgrading Alpine, update both the pinned version and the integrity value.
The [x-cloak] rule in resources/css/app.css keeps Alpine-controlled elements
hidden until Alpine initializes them.
The starter kit
A new application (not --api) ships:
- Layouts —
resources/views/layouts/app.khnum.html(authenticated shell: sidebar, mobile off-canvas nav, account menu) andlayouts/guest.khnum.html(centered card, for the welcome page and anything unauthenticated). - Pages —
welcome.khnum.htmlanddashboard.khnum.html, extending those layouts. - Components —
resources/views/components/:alert,badge,button,card,input,label,nav-link. Small, unopinionated wrappers around the same Tailwind classes used throughout the pages.
None of it is meant to survive contact with your actual application. Edit or delete any of it — the layouts, the pages, the components — the same way you would gut a fresh Laravel Breeze install. --api skips all of it: no resources/, no public/, no khnum_maat dependency, because a JSON API serves no HTML.
Serving the Compiled CSS
Link to the output with {{ asset('css/app.css') }}. Maat serves the resulting
file from public/ and can point the same helper at a CDN. See
Static Files and Assets for URL versioning, caching, and traversal
protection.
Deployment
Compiled CSS is not committed by default (public/css/ ships only a .gitkeep), so build it as part of your deploy:
maat tailwind --minifypublic/ has to ship with the compiled application — the native server reads
public/css/app.css off disk at request time, exactly like it reads
resources/views/*.khnum.html. See Deployment
for the Docker specifics.
See also
- Views & Templates — Khnum syntax, layouts, components.
- Static Files & Assets —
PublicFilesinternals, path-traversal handling,asset(). examples/todo— a complete application already on this stack:resources/css/app.css,resources/views/, and a compiledpublic/css/app.csschecked in.