blatui
Get started

Installation

Add BlatUI to any Laravel app in a few minutes. You install the foundations once, then pull in components on demand — and you own every line.

Laravel 11+ PHP 8.2+ Tailwind CSS v4 Alpine.js 3 Node 18+

TL;DR — already on Laravel + Tailwind v4?

Install, publish the foundations, wire two entrypoints, then start building.

Terminal
composer require anousss007/blatui gehrisandro/tailwind-merge-laravel mallardduck/blade-lucide-icons
npm install -D alpinejs @alpinejs/anchor @alpinejs/collapse @alpinejs/focus
php artisan vendor:publish --tag=blatui-foundations

Point your two Vite entrypoints at the published foundations (replace each file's contents):

resources/css/app.css
@import "./blatui.css";
resources/js/app.js
import "./blatui.js";

Verify, then add your first components:

Terminal
php artisan blatui:init
php artisan blatui:add button card input

Charts are opt-in. ApexCharts (~140kb) isn't installed by default — only when you add a chart:

Terminal
php artisan blatui:add chart
npm install -D apexcharts
php artisan vendor:publish --tag=blatui-charts

Then wire it in app.js (swap the one-line bootstrap for the explicit form):

resources/js/app.js
import Alpine from 'alpinejs';
import { registerBlatUI } from './blatui-core.js';
import { registerCharts } from './blatui-charts.js';

registerBlatUI(Alpine);
registerCharts(Alpine);
window.Alpine = Alpine;
Alpine.start();
Using an AI editor? Connect the BlatUI MCP server so your agent can search, read & install components — plus Laravel Boost integration.

Step-by-step

1

Install the package

Pull BlatUI in via Composer. It ships the Artisan commands and the component registry.

Terminal
composer require anousss007/blatui
2

Install the peer dependencies

Two Composer packages power the components — twMerge() (the cn() equivalent) and Lucide icons:

Terminal
composer require gehrisandro/tailwind-merge-laravel mallardduck/blade-lucide-icons

Then the front-end packages — Alpine, its plugins, and ApexCharts (for the charts):

Terminal
npm install -D alpinejs @alpinejs/anchor @alpinejs/collapse @alpinejs/focus apexcharts
3

Publish & import the foundations

Publish the theme CSS and the Alpine/chart/calendar engine. They land in resources/css/blatui.css and resources/js/blatui.js — and they're yours to edit.

Terminal
php artisan vendor:publish --tag=blatui-foundations

Then point your two Vite entrypoints at them — replace the contents of each file:

resources/css/app.css
@import "./blatui.css";
resources/js/app.js
import "./blatui.js";
blatui.css bundles Tailwind, the design tokens and the @theme mapping; blatui.js boots Alpine + its plugins and lazy-loads ApexCharts. Want a custom palette? Open Customize (top-right), tune it, and hit Copy theme CSS for a complete, ready-to-paste app.css.
4

Verify your setup

Run the doctor. It checks every package, the theme tokens and the Alpine bootstrap — and that the foundations are actually imported — then tells you exactly what's missing.

Terminal
php artisan blatui:init
5

Add components

Copy components — and their dependencies — straight into resources/views/components/ui. They're yours now: edit freely.

Terminal
php artisan blatui:add button card input

# browse everything that's available
php artisan blatui:list
6

Use them

Every component is a Blade tag under the ui namespace. Compose away:

resources/views/welcome.blade.php
<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>

Renders

Welcome back
Sign in to continue.

Installing into an existing project

Already have a Laravel app with Tailwind set up? Everything is additive — you don't replace your files.

Tailwind v4 is required. BlatUI relies on v4-only features (@theme inline, oklch tokens). If you're still on Tailwind v3, migrate first:
Terminal
npx @tailwindcss/upgrade

CSS — add, don't replace

Append the import to your existing app.css (below your @import "tailwindcss"). The foundations layer on top of your own styles.

resources/css/app.css
@import "tailwindcss";
/* …your existing styles… */
@import "./blatui.css";

JS — already running Alpine?

Don't import blatui.js (it would boot a second Alpine). Register BlatUI into your Alpine instance instead, before you start it:

resources/js/app.js
import Alpine from 'alpinejs'
import { registerBlatUI } from './blatui-core.js'

registerBlatUI(Alpine)   // plugins + theme store + chart/calendar engines

window.Alpine = Alpine
Alpine.start()

JS — using Livewire or Flux?

Livewire bundles and starts Alpine for you, so don't npm install alpinejs (that's the duplicate-Alpine trap) and don't import blatui.js. Install only the plugins — npm install -D @alpinejs/anchor @alpinejs/collapse @alpinejs/focus (add apexcharts only if you use charts) — and register BlatUI onto Livewire's Alpine via the alpine:init hook:

resources/js/app.js
import { registerBlatUI } from './blatui-core.js'

document.addEventListener('alpine:init', () => {
    registerBlatUI(window.Alpine)
})

BlatUI registers a theme store for dark mode. If Flux already drives dark mode, pass registerBlatUI(window.Alpine, { darkMode: false }) so the two don't both toggle .dark.

Server-rendered forms & the foundations utilities

Building a reusable form-field DX layer (a wrapper that re-emits {{ $slot }})? Reach for the foundations utilities on raw elements rather than nesting <x-ui.*> form controls inside that slot.

Footgun: <x-ui.*> as slot content of an @aware anonymous component may not compile.

When a <x-ui.input/.textarea/.select> is passed as the slot content of an anonymous wrapper that re-wraps the slot through another anonymous component using @aware, the inner tag is left literal in the output. A content component leaves an orphan @endifParseError (500); a self-closing one renders an unknown <x-ui.input> element that produces no <input> at all — the field is silently absent.

⚠️ This passes every render test. A GETassertOk() still returns 200, and POST-based feature tests still pass — only a browser test that actually fills the field catches it. (Direct, un-wrapped use compiles fine; the bug only appears through the @aware slot layer.)

The fix: in any DX layer that re-wraps a slot, render raw elements styled by the foundations utilities — zero <x-ui.*> in the slot, and it's just as “native”. The utilities are the twin of the components' class-strings, shipped in blatui.css:

.blat-input .blat-textarea .blat-select .blat-checkbox .blat-radio .blat-label
resources/views/components/has-field.blade.php
<label class="blat-label">{{ $label }}</label>
<input name="{{ $name }}" class="blat-input" />
<textarea name="{{ $name }}" class="blat-textarea"></textarea>
<select name="{{ $name }}" class="blat-select">...</select>
php artisan blatui:doctor flags this for you — it scans your compiled views for any literal <x-ui.*> tag that leaked into the HTML (a tag that failed to compile).