anousss007/blatui
BlatUI brings shadcn/ui to the BLAT stack — Blade, Laravel, Alpine and Tailwind. A CLI copies 155 MIT-licensed, WCAG AA accessible components, 64 blocks and 70 charts straight into your project. You own the code, so edit freely. Livewire-ready, fully themeable, light and dark, no React.
The install is a three-part ritual: a CLI, peer dependencies, and two published foundation files. Do it in this order, and don't skip step 4 — blatui:init is the only thing that will tell you which of the moving parts you got wrong.
composer require anousss007/blatui
composer require gehrisandro/tailwind-merge-laravel mallardduck/blade-lucide-icons
npm install -D alpinejs @alpinejs/anchor @floating-ui/dom @alpinejs/collapse @alpinejs/focus
php artisan vendor:publish --tag=blatui-foundations
php artisan blatui:init
Then point your Vite entries at the published files — resources/css/app.css becomes @import "./blatui.css"; and resources/js/app.js becomes import "./blatui.js";. That single CSS import pulls in Tailwind v4, the oklch design tokens, and the @theme mapping; the JS import boots Alpine with its plugins and lazy-loads ApexCharts.
Where to look first: resources/css/blatui.css. Everything visual is a CSS variable there, so read it before you start overriding classes on components. Then php artisan blatui:list to see the 155 component families and php artisan blatui:list select to see a single family's variants and props — that's your real API reference, more current than any docs page.
First use case: build one real form, not a demo page. php artisan blatui:add button input label field card and wire up a login screen. It exercises the whole stack end to end — Tailwind v4 tokens rendering, Alpine focus management on inputs, the x-ui. namespace resolving, and tailwind-merge handling your class="w-full" overrides. If the login card looks right in both light and dark mode, your foundations are correctly wired. Only then reach for blocks.
Treat blatui:add as the only entry point for primitives. It resolves transitive component dependencies for you — adding date-picker silently pulls in calendar, popover, and button — and it prints the extra composer/npm peers a family needs (charts want apexcharts). Adding a component by copy-pasting from the docs site leaves you with broken includes.
Composition, not configuration. These are shadcn-style compound components; the sub-component tags are the API.
<x-ui.card class="max-w-sm">
<x-ui.card-header>
<x-ui.card-title>Welcome back</x-ui.card-title>
<x-ui.card-description>Sign in to continue.</x-ui.card-description>
</x-ui.card-header>
<x-ui.card-content class="space-y-3">
<x-ui.input type="email" placeholder="[email protected]" />
<x-ui.button class="w-full">Sign in</x-ui.button>
</x-ui.card-content>
</x-ui.card>
class is merged through tailwind-merge, so class="w-full" genuinely wins over the component's internal width rather than fighting it in the cascade. Lean on that instead of editing component files.
Livewire is the headline integration. Every form control accepts wire:model with full two-way binding — inputs, selects, checkboxes, switches, sliders, date/time pickers, file uploads. This is the honest reason to pick BlatUI over hand-rolling: it's a credible, you-own-the-code alternative to Flux Pro. The catch is entirely in the JavaScript bootstrap (see Gotchas), not in the Blade.
Blocks and charts are deliberately not CLI-installable. They're full-page compositions, so the flow is fetch-then-install:
curl https://blatui.remix-it.com/r/blocks/dashboard-01.json
# write each files[].content to its files[].target
php artisan blatui:add # for whatever the block's registryDependencies list
Budget a few minutes per block for this. Read registryDependencies out of the JSON before you write files, so the components exist when the block's Blade renders.
Upgrades are a reviewed diff, not a pull. Because you own the files, blatui:update treats a differing file as either your customization or a stale copy and refuses to guess. The day-to-day pattern that keeps this manageable:
php artisan blatui:update --dry-run --ignore-whitespace # what's actually different?
php artisan blatui:update --diff # inspect before deciding
php artisan blatui:update button card --force # batch, keeps .bak
Run the dry-run monthly, not per-release. Treat --force as the exception and the interactive per-file confirm as the default, especially once you've started editing components.
Theming is a single file, and that's the extension point. Change the oklch tokens in blatui.css for brand color, radius, and dark-mode values, rather than patching individual components. Per-component edits are fine and supported, but they're now yours forever and every future blatui:update will flag them.
AI-assisted work has a first-class path. php artisan blatui:mcp runs an MCP server over stdio so an editor can search and install components; there's also a skills/blatui-development skill file. If you're using an agent to scaffold UI, point it at that skill — it encodes the "check resources/views/components/ui/ before assuming a component exists" rule that agents otherwise get wrong.
The duplicate-Alpine trap is the #1 failure mode. If you're on Livewire or Flux, Livewire already bundles and starts Alpine. Do not npm install alpinejs and do not import blatui.js — instead install only the plugins and register into Livewire's instance:
import { registerBlatUI } from './blatui-core.js'
document.addEventListener('alpine:init', () => {
registerBlatUI(window.Alpine)
})
Two Alpines means components that render but never react, with no console error pointing at the cause. If you run your own Alpine (non-Livewire), call registerBlatUI(Alpine) before Alpine.start().
Flux dark-mode collision. BlatUI registers a theme store that toggles .dark. If Flux already drives dark mode, both will fight and you'll see flapping. Pass registerBlatUI(window.Alpine, { darkMode: false }).
Tailwind v4 is a hard requirement — @theme inline and oklch are v4-only. On v3, run npx @tailwindcss/upgrade first. blatui:init detects the version, so use it rather than guessing.
A component is only usable if the file exists. There's no runtime package fallback; <x-ui.foo> against a missing file throws a view-not-found with a somewhat unhelpful message. Glob resources/views/components/ui/ before writing markup, and don't assume a component you saw on the demo site is installed.
Formatters silently poison your update diffs. Pint or Prettier over resources/views rewrites the layout of every copied component, so they all read as "changed." BlatUI counts those separately as same content, reformatted, and --ignore-whitespace excludes them. Make --ignore-whitespace the default in your muscle memory, or exclude resources/views/components/ui from your formatter config.
Foundations are published, not copied — resync them differently. blatui:update won't touch blatui.css / blatui.js. Use vendor:publish --tag=blatui-foundations --force, and let blatui:init tell you when the engine has fallen behind. Remember that a forced republish clobbers your token edits — commit before you run it.
blatui:doctor catches the boring bugs. It scans Blade for typeless <button> elements inside forms (which submit unexpectedly) and leaked tags. Run it once before any PR that touches forms and overlays; it's cheaper than a QA pass. Give it a path argument to scope it to a feature directory.
Watch prop-level breakpoints in the sidebar. Release 1.24.1 fixed mobileBreakpoint only partially moving the breakpoint — JS honored it while three static md: classes stayed pinned at 768px, so between 768px and your configured value the trigger toggled state and nothing visibly happened. If you're on ≤1.24.0 and need a custom sidebar breakpoint, upgrade rather than work around it. Values other than md are now genuinely overridden by isMobile.
Maturity caveat worth stating plainly. This is a young, single-maintainer package (v1.28.x, 262 stars, zero dependents, non-standard license file). The rapid changelog cadence is a good sign for maintenance but means you should pin the version in composer.json and review upgrades deliberately — which, conveniently, is exactly the workflow blatui:update was designed around. The copy-paste ownership model is your risk mitigation: even if the package stalls, everything you've installed keeps working and is yours to maintain.
How can I help you explore Laravel packages today?