ph7jack/wireui
WireUI is a Laravel Livewire component library built on Tailwind CSS, offering ready-made forms, modals, notifications, dialogs, and other customizable UI elements to speed up building modern, accessible web interfaces.
composer require ph7jack/wireui — pair the major version with your Livewire major (WireUI 2.x ⇄ Livewire 3, WireUI 1.x ⇄ Livewire 2). Check the constraint before you run it, not after a broken wire:model.php artisan wireui:install publishes config/wireui.php, adds the WireUI preset to tailwind.config.js, and registers component aliases (x-button, x-input, x-modal, …).content array must include ./vendor/ph7jack/wireui/resources/**/*.blade.php and ./vendor/ph7jack/wireui/src/View/**/*.php. The installer usually appends these; re-check if you use a custom preset or Tailwind v4's CSS-first config.@wireuiScripts before </body> (after @vite/@livewireScripts), plus <x-notifications /> — and <x-dialog /> on versions where the confirm dialog needs a mount point.<x-input>, <x-select>, and <x-button>, then fire a success toast from the Livewire action. That two-hour change is the honest measure of whether the package earns its place.Where to look first: config/wireui.php (defaults, aliases, notification position), vendor/ph7jack/wireui/resources/views/components/ (the actual markup you'll be overriding), then the docs site.
use WireUi\Traits\WireUiActions;
class CreateUser extends Component
{
use WireUiActions;
public string $name = '';
public function save(): void
{
$this->validate(['name' => 'required|min:3']);
User::create(['name' => $this->name]);
$this->notification()->success(
title: 'Saved',
description: 'The user was created.'
);
}
}
Forms. Bind wire:model straight onto WireUI inputs; they forward unknown attributes to the inner <input>. Reach for x-select with option-key/option-value instead of hand-rolling an Eloquent loop, x-errors for validation bags, and x-toggle/x-datetime-picker/x-currency where you'd otherwise be pasting a JS library.
<x-input wire:model="name" label="Name" hint="At least 3 characters" />
<x-errors name="name" />
<x-select
wire:model="role"
label="Role"
:options="$roles"
option-value="id"
option-key="name"
/>
Modals as Livewire state. Let a boolean property own visibility and let the component emit close; don't manage modals with ad-hoc JS classes. This keeps the create/edit flow testable and survives re-renders.
<x-modal-card wire:model="showForm" title="New user">
<x-input wire:model="name" label="Name" />
<x-slot name="footer">
<x-button flat label="Cancel" x-on:click="close" />
<x-button primary label="Save" wire:click="save" spinner="save" />
</x-slot>
</x-modal-card>
Notifications replace session()->flash. One-liner success/error/info/warning calls from any Livewire action, with the toast rendering handled globally instead of per-view. This is the highest-leverage feature in the package — it deletes flash-partial boilerplate from every controller and layout.
Confirm before destructive work without writing JS.
$this->dialog()->confirm([
'title' => 'Delete user?',
'description' => 'This cannot be undone.',
'accept' => ['label' => 'Delete', 'method' => 'destroy', 'params' => $userId],
]);
Wrap, don't sprinkle. Build one local <x-form.input> around x-input that standardises label/hint/error layout, and use it everywhere. When you later need to change how errors render, you change one file rather than 200 call sites — and you keep a seam for swapping WireUI out.
Theme once in config. Set brand colours and default variants in config/wireui.php (style.color, etc.) rather than passing color="primary" at every call site; use the class passthrough only for genuine one-offs.
Testing. Livewire::test() still exercises the PHP side fully. Assert the business effect (record deleted, user created) and, if you want the UI contract covered, assert the specific Livewire events the trait dispatches — read the trait source for exact event names rather than guessing.
content paths, not a broken install. Second most common cause: Tailwind v4 ignoring tailwind.config.js, where the shipped preset never gets applied.import Alpine and Alpine.start() yourself if @wireuiScripts already handles it.<x-notifications /> / <x-dialog /> before debugging the component.config/wireui.php drives component alias registration at boot; run php artisan config:clear (and remember CI's config:cache) after editing aliases.<x-button>, use the namespaced form (<x-wireui::button>) or rename via the alias config rather than deleting your own component.x-select with an Eloquent collection without option-value/option-key yields "Array to string conversion" — set them explicitly.x-icon renders empty for a nonexistent name; confirm the configured icon style (heroicons vs. fontawesome) matches the names you're using.wire:navigate. If navigation leaves overflow-hidden on <body> or an orphaned overlay, clean up on the livewire:navigated event.composer.json, and read the changelog before crossing a major (props have been renamed between majors).content globs to the components you actually use (button, input, modal) measurably speeds up npm run dev on a large project.How can I help you explore Laravel packages today?