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.
config/admin.php| 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.
⚠️ Laravel 10 is past its security window. The package supports it for compatibility, but new projects should target L11+.
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:
--force to override (not recommended).--no-interaction with collisions present → aborts unless --force is set. Keeps CI safe.laravel new project with no auth starter. Zero conflicts, ~2 minutes to a working admin + auth UI.resources/views/auth/ first.routes/auth.php, app/Livewire/Forms/LoginForm.php (Breeze Livewire), and resources/views/livewire/pages/auth/*. Without --force Laravel 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.--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.
| 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 |
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.
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
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:
admin-middleware or impersonation):
// app/Models/User.php
use App\Models\Concerns\UiKitUser;
class User extends Authenticatable
{
use UiKitUser; // <-- add this line
// ... your existing traits
}
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>
// 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';
.env keys:
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
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).
// 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.
// 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.
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.
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
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.
1. Generate a Measurement ID.
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.
1. Grab your Project API Key.
phc_….https://us.i.posthog.com for PostHog Cloud US, https://eu.i.posthog.com for 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.
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.
php artisan ui-kit:install-module support-tickets
php artisan ui-kit:install-module analytics --providers=utm,posthog
php artisan ui-kit:list-modules
admin-middlewareShips 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-ticketsAdmin-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.
changelogAdmin CRUD + public feed helper. HTML sanitization via mews/purifier. Each entry has a status (published/draft) and a category.
contactsInbox 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.
analyticsThree providers — pick any combination at install time. See Environment & credentials above for the full GA4 / PostHog setup walkthroughs.
?utm_* → session, User model columns, and a Livewire-powered link builder at /admin/analytics/utm. No external service required.@include('partials.ga4') loader. Needs GOOGLE_ANALYTICS_ID.POSTHOG_PUBLIC_KEY (+ optional POSTHOG_HOST).Both GA4 and PostHog loaders gate on cookie_consent=accepted. Set that cookie from your consent banner (or your tests).
profileFour 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.
impersonationTwo 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-logPaginated 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-modeAlpine $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.
Volt::mount(). The service provider calls it automatically when it detects published Volt pages. EOL'd security support — bump to L11+ when you can.bootstrap/app.php. Post-install notes call out the relevant bootstrap/app.php vs Http/Kernel.php snippet so you know where to drop in the new middleware.composer.json locally if you want to try it early./login returns 500 with "Vite manifest not found" — run npm run dev or npm run build. Vite must emit a manifest before Blade's @vite directive can resolve it.MAIL_* in .env. In local dev, set MAIL_MAILER=log and tail storage/logs/laravel.log.cookie_consent=accepted is set. Both loaders are consent-gated by design.SidebarBadgeResolver; the default returns an empty array./admin — either the default fallback middleware is rejecting you (it requires $user->is_admin to be truthy) or you installed admin-middleware and haven't assigned the admin role yet.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.
MIT — see LICENSE.
How can I help you explore Laravel packages today?