shipbytes / laravel-ui-kit
Admin panel + auth UI kit for Laravel with Livewire 3, Volt, and Tailwind. Modular installers for optional features.
Package info
github.com/shipbytes/laravel-ui-kit
Language:Blade
pkg:composer/shipbytes/laravel-ui-kit
Requires
- php: ^8.1
- illuminate/console: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/routing: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/view: ^10.0 || ^11.0 || ^12.0 || ^13.0
- laravel/fortify: ^1.21
- laravel/prompts: ^0.1.15 || ^0.2.0 || ^0.3.0
- livewire/livewire: ^3.0
- livewire/volt: ^1.0
- propaganistas/laravel-disposable-email: ^2.0
Requires (Dev)
- orchestra/testbench: ^8.0 || ^9.0 || ^10.0
- phpunit/phpunit: ^10.5 || ^11.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-04-25 12:16:31 UTC
README
Admin panel + auth UI scaffolding for Laravel 10/11/12/13 with Livewire 3, Volt, Fortify, and Tailwind 3.
Core + 9 optional modules, installed interactively via php artisan ui-kit:install. CI covers PHP 8.1–8.4 × Laravel 10/11/12. See CHANGELOG.md for release notes.
What you get
Always installed (core)
- Auth pages — login, register, forgot/reset password, email verification, confirm password (Livewire Volt + Fortify)
- Admin shell — collapsible sidebar, mobile bottom nav, nav-config-driven from
config/admin.php - Dashboard stub + Users CRUD
- Tailwind preset — fonts (Inter/Poppins/Montserrat), brand palette, dark-mode class strategy
- Alpine stores — sidebar collapse (localStorage-persisted), theme toggle
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.0 |
support-tickets |
Admin ticket queue + replies (Mailables left to you) | — |
changelog |
Admin-authored changelog with public feed | mews/purifier:^3.4 |
contacts |
Contact-form submission inbox | — |
analytics |
UTM tracking, GA4, PostHog (pick any combination) | — |
profile |
Self-service name/email/password/avatar + 2FA toggle | — (optional: intervention/image) |
impersonation |
Login-as-user with exit ribbon + button partial | lab404/laravel-impersonate:^1.7 |
activity-log |
Spatie activity log + filterable admin viewer | spatie/laravel-activitylog:^4.8 |
dark-mode |
<x-theme-toggle /> component + no-flash inline snippet |
— |
Every module prints a numbered Next steps checklist after ui-kit:install-module — we don't auto-patch your route files or config/admin.php.
Requirements
- PHP 8.1+
- Laravel 10, 11, 12, or 13
- Livewire 3 + Volt 1.x
- Node 18+ (for Vite / Tailwind build)
⚠️ Laravel 10 is past its security window. The package supports it for compatibility, but new projects should target L11+.
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.
The installer does a preflight check for you — it reads your composer.lock for laravel/breeze / laravel/jetstream and scans for colliding file paths (routes/auth.php, app/Livewire/Forms/LoginForm.php, resources/views/livewire/pages/auth/*). Behaviour:
- Jetstream detected → aborts. Pass
--forceto override (not recommended). - Breeze detected (or stray auth files) → warns, lists the collisions, and prompts you to confirm.
- Running
--no-interactionwith collisions present → aborts unless--forceis set. Keeps CI safe.
✅ Do install on
- A fresh
laravel newproject with no auth starter. Zero conflicts, ~2 minutes to a working admin + auth UI. - An existing app that has no auth UI yet (e.g. an API-only app you're now adding an admin panel to). You'll still want to uninstall any partial auth views from
resources/views/auth/first.
⚠️ Avoid installing on top of
- Breeze (any stack) — collides on
routes/auth.php,app/Livewire/Forms/LoginForm.php(Breeze Livewire), andresources/views/livewire/pages/auth/*. Without--forceLaravel silently skips them, leaving you with Breeze's code running under this kit's layouts — usually broken. With--force, Breeze is overwritten and may leave orphaned files behind. - Jetstream — heavy footprint (teams, API tokens, Sanctum-based auth) that this kit doesn't understand. Do not combine.
- An app with customized Fortify views/actions — your customizations will either be skipped or overwritten depending on
--force.
If you must use the kit on top of an existing auth setup, remove the starter first:
# Breeze — no official uninstaller, remove by hand: 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 — no uninstaller, migration is significant. Start from a fresh app.
Then run the kit installer. Review the generated files before committing — merge anything you wanted to keep from your old setup by hand.
What the installer publishes (so you can eyeball conflicts)
| Destination | From |
|---|---|
config/ui-kit.php, config/admin.php |
kit-specific, safe |
resources/views/layouts/*, components/auth-session-status.blade.php |
collides with Breeze |
resources/views/livewire/pages/auth/* |
collides with Breeze Livewire |
resources/views/livewire/admin/* |
kit-specific, safe |
app/Livewire/Admin/*, app/Livewire/Forms/LoginForm.php |
LoginForm collides with Breeze Livewire |
routes/auth.php |
collides with Breeze |
routes/admin.php |
kit-specific, safe |
resources/js/ui-kit.js, resources/css/ui-kit.css |
kit-specific, safe |
database/migrations/..._add_is_admin_to_users_table.php |
kit-specific, timestamped, safe |
Install
composer require shipbytes/laravel-ui-kit php artisan ui-kit:install
The installer walks you through an interactive module picker. Run php artisan ui-kit:install --modules=admin-middleware,profile to skip the prompt.
Installing before a Packagist release (or straight from GitHub / a local path)
Until a version is tagged and submitted to Packagist, or if you're hacking on the kit locally, point Composer at the source directly.
From GitHub (VCS repository) — good for tracking main:
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-main"
From a local checkout (path repository) — good for contributors; 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
Finish wiring
The installer does almost everything — publishes assets/configs/migrations, patches config/fortify.php and config/admin.php (middleware + nav), appends module routes, runs vendor:publish for every dependent package, runs one php artisan migrate, seeds the admin role, runs storage:link, installs npm packages where required, and generates app/Models/Concerns/UiKitUser.php based on which modules you picked.
That leaves a small irreducible checklist you do once:
- Add the kit's User trait (only if you picked
admin-middlewareorimpersonation):// app/Models/User.php use App\Models\Concerns\UiKitUser; class User extends Authenticatable { use UiKitUser; // <-- add this line // ... your existing traits }
- Add the kit's component tags to your master layout (
resources/views/layouts/app.blade.php):<head> <x-ui-kit::head /> {{-- analytics + dark-mode no-flash --}} </head> <body> <x-ui-kit::banners /> {{-- impersonation ribbon (auto-hides) --}} {{ $slot ?? '' }} </body>
- Add the Tailwind preset and import the kit's JS/CSS into your Vite bundles:
// tailwind.config.js module.exports = { presets: [require('shipbytes/laravel-ui-kit/tailwind-preset')], content: [ './resources/**/*.blade.php', './resources/**/*.js', './app/**/*.php', ], };
// resources/js/app.js import './ui-kit';
/* resources/css/app.css */ @import './ui-kit.css';
- Set required
.envkeys:MAIL_MAILER=log # use 'smtp'/'mailgun'/etc. for production MAIL_FROM_ADDRESS="noreply@example.com" MAIL_FROM_NAME="${APP_NAME}" GOOGLE_ANALYTICS_ID= # optional: only if you picked analytics:ga4 POSTHOG_PUBLIC_KEY= # optional: only if you picked analytics:posthog
- Build assets, then make a user admin:
npm install && npm run dev # Promote your test account to admin (only if admin-middleware is installed) php artisan tinker --execute="App\Models\User::find(1)->assignRole('admin');"
That's it. /register, /login, /admin, /profile, and every module's admin page should work.
What the installer handled for you
- Published every kit + dependency config (Fortify, Spatie Permission, mews/purifier, lab404/impersonate, Spatie Activitylog).
- Patched
config/fortify.php(views=false),config/admin.php(middleware swap + nav entries between/* ui-kit:nav-* */markers), androutes/admin.php(module routes between/* ui-kit:admin-routes-* */markers).- Auto-loads
routes/auth.php,routes/admin.php, androutes/ui-kit-user.phpfrom the service provider — nobootstrap/app.phpedit required.- Pushed the UTM-capture middleware into the
webgroup at runtime (whenanalytics:utmis installed), so you don't have to register it manually.- Reads
GOOGLE_ANALYTICS_ID/POSTHOG_PUBLIC_KEY/POSTHOG_HOSTdirectly from.envintoservices.google.*andservices.posthog.*at boot — noconfig/services.phpedit needed.- Generated
app/Models/Concerns/UiKitUser.phpbundling whatever traits and methods your installed modules require.- All patches are idempotent — re-running
ui-kit:install(or installing more modules later) won't duplicate nav entries or routes.- On Windows cmd / Laragon / some WSL emulators, the picker uses a plain numbered-list prompt instead of Laravel Prompts' alt-screen rendering. Override with
UI_KIT_PROMPTS_FALLBACK=0(force fancy) or=1(force plain).
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 your logo PNG/SVG at public/images/logo.png (or override UI_KIT_BRAND_LOGO to point anywhere else). home_route is the route name used by the "back to site" link in the admin shell.
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.tickets.index', 'icon' => 'ticket', 'badge' => 'open_tickets'], ],
Each installed module's Next steps checklist tells you the exact nav entry to paste.
Sidebar badges
Bind your own resolver so sidebar 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.
Environment & credentials
This section is a single place to see every .env key the kit can read, with links to where to generate the values. Only the Mail block is required for a production-ready install; everything else depends on which modules you enable.
.env reference
# --- Branding (all optional; sensible defaults if unset) ----------------- 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}" # --- Analytics module: GA4 (only if you installed analytics+ga4) --------- GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX # --- Analytics module: PostHog (only if you installed analytics+posthog) - POSTHOG_PUBLIC_KEY=phc_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX POSTHOG_HOST=https://us.i.posthog.com # or https://eu.i.posthog.com
Mail (for auth emails)
Fortify sends password-reset and email-verification messages through Laravel's mailer. Out of the box, MAIL_MAILER=log works for local dev (mail goes to storage/logs/laravel.log). For production, pick any supported driver — Mailgun, Postmark, SES, Resend, or SMTP. See the Laravel mail docs for driver-specific setup.
If you don't configure mail, the UI will appear to work but users will never receive verification or reset emails.
GA4 (Google Analytics 4)
1. Generate a Measurement ID.
- Go to analytics.google.com.
- Admin (gear icon, bottom-left) → Create → Property (or pick an existing one).
- Inside the property: Data streams → Add stream → Web → enter your site URL.
- Copy the Measurement ID. It looks like
G-XXXXXXXXXX.
2. Paste it into .env.
GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX
3. Register the config key in config/services.php (add this once):
'google' => [ 'analytics_id' => env('GOOGLE_ANALYTICS_ID'), ],
4. Include the loader in your app layout, just before </head>:
@include('partials.ga4')
5. Consent gating. The loader only fires once a cookie_consent=accepted cookie is present. Set that cookie from your consent banner (or manually in dev via DevTools) — otherwise the GA4 script never runs. This is intentional so you're GDPR/CCPA-ready.
Verify it's working: open your site, accept the cookie banner, and watch Realtime in the GA4 UI. You should see yourself within ~30 seconds.
PostHog
1. Grab your Project API Key.
- Sign up / log in at posthog.com (or run self-hosted).
- Pick the project you want to track into.
- Settings (gear icon, bottom-left) → Project → General → copy Project API Key. It starts with
phc_…. - Note your host:
https://us.i.posthog.comfor PostHog Cloud US,https://eu.i.posthog.comfor EU, or your own URL for self-hosted.
2. Paste into .env.
POSTHOG_PUBLIC_KEY=phc_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX POSTHOG_HOST=https://us.i.posthog.com
3. Register config keys in config/services.php:
'posthog' => [ 'public_key' => env('POSTHOG_PUBLIC_KEY'), 'host' => env('POSTHOG_HOST', 'https://us.i.posthog.com'), ],
4. Include the loader in your app layout, just before </head>:
@include('partials.posthog')
5. Install the JS SDK + bridge so server-side Livewire events can capture PostHog events:
npm install posthog-js
// resources/js/app.js import './posthog-bridge';
6. Capture events from Livewire:
$this->dispatch('posthog-capture', event: 'ticket_replied', properties: [ 'ticket_id' => $ticket->id, ]);
7. Consent gating. Same as GA4 — the loader waits for cookie_consent=accepted. Verify in the PostHog Live events tab.
⚠️ Only use your public project key (
phc_…). The personal / private API key should never land in frontend code.
UTM tracking (analytics module, UTM provider)
No external service or key needed. Once you register the middleware, anyone who hits your site with ?utm_source=…&utm_medium=…&utm_campaign=… on the URL gets the values stashed in their session (and attached to the User model on signup). The UTM Link Builder page (/admin/analytics/utm) generates tagged URLs for your campaigns.
Installing modules later
php artisan ui-kit:install-module support-tickets php artisan ui-kit:install-module analytics --providers=utm,posthog php artisan ui-kit:list-modules
Module deep-dives
admin-middleware
Ships EnsureUserIsAdmin (Spatie role check) + AdminRoleSeeder. The installer publishes Spatie's config/migrations, runs migrate and db:seed --class=AdminRoleSeeder, swaps the middleware in config/admin.php from the fallback to App\Http\Middleware\EnsureUserIsAdmin::class, and generates App\Models\Concerns\UiKitUser bundling Spatie's HasRoles trait. You add use UiKitUser; to your User model and assignRole('admin') to one user.
support-tickets
Admin-only queue (public form is yours to build). Search by name/email, filter by status/priority, inline replies. Mailables are intentionally omitted so you plug in your own notification flow.
changelog
Admin CRUD + public feed helper. HTML sanitization via mews/purifier. Each entry has a status (published/draft) and a category.
contacts
Inbox for a public contact form that writes to contact_submissions. When the support-tickets module is also installed, a Copy to Ticket button auto-appears — no config needed.
analytics
Three providers — pick any combination at install time. See Environment & credentials above for the full GA4 / PostHog setup walkthroughs.
- UTM — middleware captures
?utm_*→ session, User model columns, and a Livewire-powered link builder at/admin/analytics/utm. No external service required. - GA4 — consent-gated
@include('partials.ga4')loader. NeedsGOOGLE_ANALYTICS_ID. - PostHog — consent-gated loader + Livewire→PostHog JS bridge. Needs
POSTHOG_PUBLIC_KEY(+ optionalPOSTHOG_HOST).
Both GA4 and PostHog loaders gate on cookie_consent=accepted. Set that cookie from your consent banner (or your tests).
profile
Four Livewire/Volt cards under a single ProfilePage: update info + avatar, update password, 2FA (Fortify, auto-hidden if not installed), delete account. Ships x-modal and x-action-message components. Resizes avatars to 200×200 if intervention/image is installed, otherwise stores the raw upload. Don't forget php artisan storage:link so /storage/avatars/... is publicly reachable.
impersonation
Two Blade partials (impersonation-banner, impersonation-button) over lab404/laravel-impersonate. The package auto-registers routes; you just @include the banner in your layout and the button in the user detail view. Requires canImpersonate() + canBeImpersonated() methods on your User model.
activity-log
Paginated admin viewer over spatie/laravel-activitylog's activity_log table. Filters: log stream, causer email, date range. Add the LogsActivity trait on your models per Spatie's README.
dark-mode
Alpine $store.theme ships in core/ui-kit.js. Drop <x-theme-toggle /> anywhere and inline the no-flash snippet before </head>. Every core view and every shipped module has dark: variants already.
Laravel version caveats
- L10: Volt routes require explicit
Volt::mount(). The service provider calls it automatically when it detects published Volt pages. EOL'd security support — bump to L11+ when you can. - L11: middleware registration moved to
bootstrap/app.php. Post-install notes call out the relevantbootstrap/app.phpvsHttp/Kernel.phpsnippet so you know where to drop in the new middleware. - L12: current LTS-ish target — the default for new projects using this kit.
- L13: newly released (per PHP/Fortify/Spatie peer-dep readiness). CI runs 10/11/12 until the ecosystem catches up; bump
composer.jsonlocally if you want to try it early.
Troubleshooting
/loginreturns 500 with "Vite manifest not found" — runnpm run devornpm run build. Vite must emit a manifest before Blade's@vitedirective can resolve it.- Password reset / verification emails never arrive — check
MAIL_*in.env. In local dev, setMAIL_MAILER=logand tailstorage/logs/laravel.log. - GA4 / PostHog not firing — open DevTools → Application → Cookies and confirm
cookie_consent=acceptedis set. Both loaders are consent-gated by design. - Sidebar badges show 0 / blank — bind your own
SidebarBadgeResolver; the default returns an empty array. - "Unauthorized" on
/admin— either the default fallback middleware is rejecting you (it requires$user->is_adminto be truthy) or you installedadmin-middlewareand haven't assigned theadminrole yet.
Testing the package
composer install vendor/bin/phpunit
Tests run against Orchestra Testbench. CI (.github/workflows/tests.yml) matrixes PHP 8.1–8.4 × Laravel 10/11/12.
License
MIT — see LICENSE.