shipbytes / laravel-ui-kit
Admin panel + auth UI kit for Laravel 12+ with Livewire 3, Volt, and Tailwind 4. Modular installers for optional features.
Requires
- php: ^8.2
- illuminate/console: ^12.0 || ^13.0
- illuminate/routing: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- illuminate/view: ^12.0 || ^13.0
- laravel/fortify: ^1.25
- laravel/prompts: ^0.3.0
- livewire/livewire: ^3.6
- livewire/volt: ^1.7
- propaganistas/laravel-disposable-email: ^2.4
Requires (Dev)
- larastan/larastan: ^3.10
- laravel/pint: ^1.30
- orchestra/testbench: ^10.0 || ^11.0
- phpunit/phpunit: ^11.5 || ^12.0
Suggests
- lab404/laravel-impersonate: Required when installing the impersonation module
- spatie/laravel-activitylog: Required when installing the activity-log module
- spatie/laravel-permission: Required when installing the admin-middleware module
This package is auto-updated.
Last update: 2026-08-20 23:15:13 UTC
README
Admin panel + auth UI scaffolding for Laravel 12+ with Livewire 3, Volt, Fortify, and Tailwind CSS v4.
Core + 7 optional modules, installed by a turnkey php artisan ui-kit:install. CI covers PHP 8.2–8.4 on Laravel 12 (plus an experimental Laravel 13 leg) and runs a full fresh-app install → build → route:cache → HTTP smoke on every push. See CHANGELOG.md for release notes.
What you get
Always installed (core)
- Auth pages — login, register, forgot/reset password, email verification, confirm password, and a two-factor challenge page (Livewire Volt + Fortify), all with dark-mode variants
- Working 2FA — the login flow stages a TOTP/recovery-code challenge for users who enabled two-factor auth (enable/confirm UI ships with the
profilemodule) - Admin shell — collapsible sidebar, mobile bottom nav, theme toggle, current-user footer with profile link + logout; nav is config-driven from
config/admin.php - User shell — a light authenticated layout (
layouts/user-shell) used by user-facing pages like/profile - Dashboard stub + Users list/detail
- Tailwind v4 theme — CSS-first
@themetokens (brand palette, Inter/Poppins/Montserrat font utilities) and a class-strategydark:variant - Alpine stores — sidebar collapse (localStorage-persisted), theme toggle with no-flash snippet
ui-kit:doctor— a health check that verifies the install is fully wired
Optional modules
| Slug | What it adds | Composer deps |
|---|---|---|
admin-middleware |
Spatie Permissions wiring (falls back to is_admin boolean if skipped) |
spatie/laravel-permission:^6.10 |
support-tickets |
Admin ticket queue + replies (Mailables left to you) | — |
changelog |
Admin-authored changelog with public feed helper | mews/purifier:^3.4 |
contacts |
Contact-form submission inbox | — |
profile |
Self-service name/email/password/avatar + full 2FA management | — (optional: intervention/image) |
impersonation |
Login-as-user with exit ribbon + button partial | lab404/laravel-impersonate:^1.7.5 |
activity-log |
Spatie activity log + filterable admin viewer | spatie/laravel-activitylog:^4.10 |
Modules that ship models also ship factories, so SupportTicket::factory() works in your tests out of the box.
Requirements
- PHP 8.2+
- Laravel 12 (Laravel 13 supported in composer constraints; CI leg is experimental)
- Livewire 3.6+ / Volt 1.7+
- Tailwind CSS v4 (the default in fresh Laravel 12 apps)
- Node 18+ (for the Vite build)
Before you install — fresh vs. existing app
The kit is designed for fresh Laravel installs (no auth scaffolding yet). Running it on top of Breeze, Jetstream, or a custom auth setup will collide.
Creating the fresh app — recommended choices
laravel new my-app # or: composer create-project laravel/laravel my-app
When the Laravel installer prompts you:
| Prompt | Pick | Why |
|---|---|---|
| Starter kit | None | The kit is the auth/admin scaffolding — every starter kit (Breeze-style Livewire, React, Vue) collides with it. |
| Frontend stack | Blade | Gives you plain Vite + Tailwind CSS v4, which the kit's @theme file plugs straight into. Livewire 3 + Volt arrive via Composer with the kit itself. |
| Testing framework | Pest or PHPUnit | No interaction with the kit — pick your preference. |
| Database | Any (the SQLite default is fine) | The kit's migrations run on anything Laravel supports. |
Run npm install / migrations |
Yes | The kit's Vite wiring expects resources/css/app.css and resources/js/app.js to exist, and its installer migrates on top of the base tables. |
Heads-up: the stock Laravel welcome page shows a Dashboard button to logged-in users that links to
/dashboard— a route neither Laravel nor the kit registers, so it 404s. The kit's panel lives at/admin(admin users only). Point that button at/adminor replace the welcome page after installing.
The preflight check
The installer runs a preflight check: it reads composer.lock for laravel/breeze / laravel/jetstream and scans for colliding files (routes/auth.php, app/Livewire/Forms/LoginForm.php, auth page views). Behaviour:
- Jetstream detected → aborts. Pass
--forceto override (not recommended). - Breeze detected (or stray auth files) → warns, lists the collisions, and prompts to confirm.
- A previous kit install detected (via the
ui-kit:managedheader) → switches to update mode and re-runs cleanly, including with--no-interaction. --no-interactionwith foreign collisions present → aborts unless--forceis set. Keeps CI safe.
The kit only auto-loads route files that carry its // ui-kit:managed header — a pre-existing routes/auth.php from Breeze or your own code is never hijacked or double-registered.
If you must install over Breeze, remove it first:
composer remove laravel/breeze
rm -rf app/Http/Controllers/Auth resources/views/auth routes/auth.php
rm -f app/Livewire/Forms/LoginForm.php app/Livewire/Actions/Logout.php
rm -rf resources/views/livewire/pages/auth resources/views/components/{input-error,input-label,primary-button,text-input}.blade.php
Jetstream has no clean migration path — start from a fresh app.
Install
composer require shipbytes/laravel-ui-kit php artisan ui-kit:install
The installer walks you through a module picker. Non-interactive variants:
php artisan ui-kit:install --modules=admin-middleware,profile php artisan ui-kit:install --modules=all
Installing straight from GitHub or a local path (contributors)
From GitHub (VCS repository):
composer config repositories.laravel-ui-kit vcs https://github.com/shipbytes/laravel-ui-kit composer config minimum-stability dev composer config prefer-stable true composer require "shipbytes/laravel-ui-kit:dev-master"
From a local checkout (path repository) — symlinks the source into vendor/ so edits are live:
composer config repositories.laravel-ui-kit path /absolute/path/to/laravel-ui-kit
composer require "shipbytes/laravel-ui-kit:*"
If symlinking causes trouble (e.g. WSL file-permission quirks), disable it:
composer config repositories.laravel-ui-kit '{"type":"path","url":"/absolute/path/to/laravel-ui-kit","options":{"symlink":false}}'
composer update shipbytes/laravel-ui-kit
What the installer automates
- Publishes every kit + dependency config, view, class, route file, and migration
- Patches
config/fortify.php(views=false,home=/, passkeys feature off) and publishes Fortify's 2FA columns migration - Patches
config/admin.phpnav androutes/admin.php/routes/ui-kit-user.phpbetween/* ui-kit:* */markers — idempotently, and without wiping other modules' entries - Wires your Vite entrypoints: adds
@import './ui-kit.css';toresources/css/app.cssandimport './ui-kit';toresources/js/app.js - Runs
vendor:publishfor every dependent package, onephp artisan migrate, seeds the admin role, runsstorage:link - Generates
app/Models/Concerns/UiKitUser.phpbundling exactly the traits your chosen modules need (SpatieHasRoles, lab404Impersonate, FortifyTwoFactorAuthenticatable) — and applies it toapp/Models/User.phpautomatically (the patch is lint-checked and reverted if it would break the file; you only edit by hand if the installer says so) - Auto-loads
routes/auth.php,routes/admin.php,routes/ui-kit-user.phpfrom the service provider — nobootstrap/app.phpedit - Registers sane
login/two-factorrate limiters unless your app already defines them
Finish wiring (the irreducible checklist)
-
Set
.envmail keys (password reset / verification emails):MAIL_MAILER=log # 'smtp'/'mailgun'/etc. for production MAIL_FROM_ADDRESS="noreply@example.com" MAIL_FROM_NAME="${APP_NAME}"
-
Build assets, then make your first user admin (register through the UI first):
npm install && npm run dev php artisan ui-kit:make-admin you@example.com # no email → promotes the first user
ui-kit:make-adminassigns the Spatieadminrole when theadmin-middlewaremodule is installed and sets theis_adminflag when the column exists — whichever mechanisms are present. -
Verify:
php artisan ui-kit:doctorprints a ✓/✗ table (published files, Fortify flags, Vite imports, trait applied, storage link, 2FA columns, duplicate route names, mail config).
The
UiKitUsertrait used to be a manual step here — the installer now applies it toapp/Models/User.phpitself and only asks you to do it by hand if the patch fails (custom model location, unrecognized class layout).
If your app has its own master layout for public pages, drop the kit's head/banner components into it so dark-mode no-flash and the impersonation ribbon work there too:
<head> <x-ui-kit::head /> </head> <body> <x-ui-kit::banners />
(The kit's own layouts — guest, admin, user shell — already include them.)
Configuration
Brand
// config/ui-kit.php 'brand' => [ 'name' => env('UI_KIT_BRAND_NAME', config('app.name')), 'logo' => env('UI_KIT_BRAND_LOGO', '/images/logo.png'), 'home_route' => env('UI_KIT_HOME_ROUTE', 'home'), ],
Drop a logo at public/images/logo.png (or point UI_KIT_BRAND_LOGO anywhere, including a full URL). Until a logo exists the layouts render an initial badge, so nothing looks broken on a fresh install. home_route names the route used for "back to site" links and post-login redirects — when the route doesn't exist the kit falls back to /, so fresh apps work without defining one.
Tailwind theme
The design tokens live in resources/css/ui-kit-theme.css (Tailwind v4 @theme):
@theme { --color-brand-500: #6366f1; /* bg-brand-500, text-brand-600, … */ --font-boldtext: 'Poppins', ui-sans-serif, system-ui, sans-serif; }
Override any token in your own CSS after the import — last definition wins. Dark mode uses a dark class on <html> (toggled by <x-theme-toggle />, persisted to localStorage, no-flash on load).
Sidebar navigation
// config/admin.php 'nav' => [ ['label' => 'Dashboard', 'route' => 'admin.dashboard', 'icon' => 'grid'], ['label' => 'Users', 'route' => 'admin.users.index', 'icon' => 'users'], ['section' => 'Support'], ['label' => 'Tickets', 'route' => 'admin.support.index', 'icon' => 'ticket', 'badge' => 'open_tickets'], ],
Modules append their own entries automatically between the /* ui-kit:nav-* */ markers.
Sidebar badges
Bind your own resolver so counters (e.g. "open tickets: 12") reflect your data:
// In a service provider $this->app->bind( \Shipbytes\UiKit\Contracts\SidebarBadgeResolver::class, \App\Support\AdminBadgeResolver::class, );
The resolver returns ['open_tickets' => 12, 'unread_contacts' => 3] — keys match the badge field on nav items.
Two-factor authentication
Fortify's twoFactorAuthentication feature is enabled by default. With the profile module installed (which adds TwoFactorAuthenticatable to the generated trait and ensures the columns exist), users get the full flow:
- Enable on
/profile→ scan the QR code → (when Fortify'sconfirmoption is on) enter a code to confirm → save recovery codes. - Login validates credentials first, then redirects 2FA users to
/two-factor-challengefor a TOTP or recovery code. Both steps are rate limited. - Disable / regenerate recovery codes any time from the profile page.
Without the profile module (or without the trait applied), login simply never challenges — nothing breaks.
Installing modules later
php artisan ui-kit:install-module support-tickets php artisan ui-kit:install-module changelog contacts profile # several at once php artisan ui-kit:install-module changelog,contacts,profile # commas work too php artisan ui-kit:list-modules php artisan ui-kit:doctor
A batch is validated up front — one unknown slug aborts the whole run before anything is installed. When a batch includes admin-middleware, impersonation, or profile, the UiKitUser trait is regenerated and re-applied to your User model automatically at the end of the run. (ui-kit:install-modules works as an alias.)
Re-running ui-kit:install is safe: it detects the existing install, keeps your files (pass --force to overwrite), and never duplicates nav entries, routes, or migrations.
Module deep-dives
admin-middleware
Ships EnsureUserIsAdmin (Spatie role check) + AdminRoleSeeder. The installer publishes Spatie's config/migrations, migrates, seeds the admin/user roles, and swaps the middleware in config/admin.php from the is_admin fallback to the role check. The UiKitUser trait (with HasRoles) is applied to your User model automatically; grant the role with php artisan ui-kit:make-admin you@example.com.
support-tickets
Admin-only queue (the public form is yours to build). Search by name/email, filter by status/priority/category, inline replies, open-count badge. Status/priority changes are validated server-side. Mailables are intentionally omitted so you plug in your own notification flow.
changelog
Admin CRUD + ChangelogEntry::published() scope for a public feed. HTML is sanitized via mews/purifier. For a public page, add a route rendering your own view over the published() scope (the module ships only the admin side).
contacts
Inbox for a public contact form that writes to contact_submissions. Read/unread, archive, bulk actions, reply drafts (bring your own Mailable). When support-tickets is also installed, a Copy to Ticket button appears automatically.
profile
Cards under /profile (rendered in the user shell, not the admin shell): update info + avatar, update password, two-factor authentication, delete account. Avatars are stored under a content-derived extension and resized to 200×200 when intervention/image is installed.
impersonation
Two Blade partials over lab404/laravel-impersonate. <x-ui-kit::banners /> shows the exit ribbon automatically; @include('partials.impersonation-button', ['user' => $user]) renders the login-as button. The generated UiKitUser trait provides canImpersonate() / canBeImpersonated().
activity-log
Paginated admin viewer over spatie/laravel-activitylog. Filters: log stream, causer email, date range. Add the LogsActivity trait to models you want logged (see Spatie's docs).
Environment reference
# --- Branding (all optional; sensible defaults) -------------------------- UI_KIT_BRAND_NAME="Acme" UI_KIT_BRAND_LOGO="/images/logo.png" UI_KIT_HOME_ROUTE="home" # --- Mail (required for password reset, email verification) -------------- MAIL_MAILER=smtp MAIL_HOST=smtp.mailgun.org MAIL_PORT=587 MAIL_USERNAME=postmaster@mg.example.com MAIL_PASSWORD=your-smtp-password MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS="noreply@example.com" MAIL_FROM_NAME="${APP_NAME}"
In local dev, MAIL_MAILER=log works — mail lands in storage/logs/laravel.log.
Troubleshooting
Run php artisan ui-kit:doctor first — it catches the common ones and prints the fix.
/loginreturns 500 with "Vite manifest not found" — runnpm run devornpm run build.- Password reset / verification emails never arrive — check
MAIL_*in.env. - "Unauthorized" on
/admin— the fallback middleware requires$user->is_adminto be truthy; withadmin-middlewareinstalled you need theadminrole assigned. - Sidebar badges show 0 / blank — bind your own
SidebarBadgeResolver; the default returns an empty array. route:cachefails with a duplicate name — the doctor lists the duplicates; usually a route file was copied without itsui-kit:managedheader and loaded twice by hand.- The welcome page's "Dashboard" button 404s — that button is stock Laravel and links to
/dashboard, which nothing registers. The kit's panel is at/admin; editresources/views/welcome.blade.phpto point there (or replace the page).
Testing the package
composer install
vendor/bin/phpunit # includes an end-to-end installer test + HTTP render tests
vendor/bin/pint --test
vendor/bin/phpstan analyse
CI additionally builds a fresh Laravel 12 app, installs every module, builds assets, caches routes, runs the doctor, and smoke-tests the HTTP pages.
License
MIT — see LICENSE.