Core
Everything on this page is installed by ui-kit:install before it asks about modules.
Auth pages
Seven pages as Livewire Volt single-file components over Fortify's actions, published to resources/views/livewire/pages/auth/ and routed from routes/auth.php:
| Route | Name | Page |
|---|---|---|
GET /login | login | Email, password, remember me |
GET /register | register | Name, email, password; disposable email domains are rejected when features.disposable_email_block is on |
GET /forgot-password | password.request | Sends the reset link |
GET /reset-password/{token} | password.reset | |
GET /two-factor-challenge | two-factor.challenge | TOTP code, with a switch to a recovery code |
GET /verify-email | verification.notice | With a resend action |
GET /verify-email/{id}/{hash} | verification.verify | Signed link handler |
GET /confirm-password | password.confirm |
The copy on each page comes from config/ui-kit.php under copy, so a heading can change without editing a view. Every page renders in light and dark.




Two-factor authentication
Fortify's twoFactorAuthentication feature is on by default and the installer publishes the migration that adds the two_factor_* columns to users. Login validates credentials first; when the user has a confirmed two-factor secret it stages a challenge and redirects to /two-factor-challenge, where a TOTP code or a recovery code completes the sign-in. Both steps are rate limited by the login and two-factor limiters the service provider registers.


Enabling two-factor, confirming it with a code, showing recovery codes and disabling it again are the profile module. Without that module a user has no way to turn it on, so login never challenges, and nothing breaks.
Admin shell
resources/views/layouts/admin-sidebar.blade.php is the layout every admin Livewire component uses. It has a collapsible sidebar whose state persists in localStorage, a bottom navigation bar on phones, a theme toggle, and a footer with the current user, a profile link and a logout form. Navigation is read from config/admin.php; modules append their entries between the ui-kit:nav markers at install time and never touch yours.




Routes live in routes/admin.php, prefixed and named from the config:
// config/admin.php
'route_prefix' => 'admin',
'route_name_prefix' => 'admin.',
'middleware' => ['auth', 'verified', \Shipbytes\UiKit\Http\Middleware\EnsureIsAdminFallback::class],
EnsureIsAdminFallback lets a request through when $user->is_admin is truthy. The admin-middleware module swaps it for a Spatie role check.
Dashboard and users
| Route | Component | What it shows |
|---|---|---|
GET /admin | App\Livewire\Admin\Dashboard | Three counts: total users, verified, new this week. A stub to replace. |
GET /admin/users | App\Livewire\Admin\Users\UserList | Paginated, searchable list |
GET /admin/users/{user} | App\Livewire\Admin\Users\UserDetail | One user, with the module partials that apply |


User shell
resources/views/layouts/user-shell.blade.php is a lighter authenticated layout for user-facing pages such as /profile. It carries the same head and banner components as the admin shell, so dark mode and the impersonation ribbon work there too. User routes live in routes/ui-kit-user.php behind auth.
Theme tokens
The kit's styles are a Tailwind v4 @theme block in resources/css/ui-kit-theme.css, imported by resources/css/ui-kit.css, which the installer imports from app.css right after tailwindcss:
@theme {
--color-brand-500: #6366f1; /* bg-brand-500, text-brand-600, … */
--font-boldtext: 'Poppins', ui-sans-serif, system-ui, sans-serif;
}
A @custom-variant dark binds dark: to a dark class on <html> rather than the OS preference, so the toggle can override the system setting. Override any token in your own CSS after the import; the last definition wins.
Alpine stores and the two components
resources/js/ui-kit.js registers two Alpine stores on alpine:init. Livewire loads Alpine, so there is nothing else to include.
| Store | Holds | Persisted as |
|---|---|---|
$store.sidebar | collapsed, with toggle() | ui-kit.sidebar.collapsed |
$store.theme | value of light or dark, with toggle() and apply() | ui-kit.theme |
Two Blade components from the package tie the stores to any layout, including your own public one:
<head>
<x-ui-kit::head /> {{-- applies the saved theme before first paint, so no flash --}}
</head>
<body>
<x-ui-kit::banners /> {{-- the impersonation ribbon, when that module is installed --}}
The kit's three layouts already include both. <x-theme-toggle /> is the published toggle button and works anywhere the store is loaded.
Artisan commands
| Command | Does |
|---|---|
ui-kit:install | Core install, then the module picker. --modules=a,b or --modules=all skips the prompt; --force overwrites existing files. Safe to re-run: an existing install is detected and updated in place. |
ui-kit:install-module <slug>... | Installs one or more modules into an existing install. Slugs can be space- or comma-separated; the batch is validated first, and one unknown slug aborts the whole run. ui-kit:install-modules is an alias. |
ui-kit:list-modules | Every module with its installed state. |
ui-kit:make-admin {email?} | Grants admin: the Spatie admin role when that module is installed, the is_admin flag when the column exists. No email promotes the first user. |
ui-kit:doctor | The health check below. |
What the doctor checks
| Check | Fails when |
|---|---|
| Published files | Any of the two configs, two route files, CSS or JS is missing |
| Auto-loaded routes | A route file lacks the ui-kit:managed header (reported as a skip) |
| Fortify views disabled | config/fortify.php is missing or views is not false |
| Vite wiring | app.css does not import ui-kit.css, or app.js does not import ./ui-kit |
UiKitUser trait applied | The trait was generated but app/Models/User.php does not use it |
| Storage linked | The profile module is installed and public/storage is missing |
| Two-factor columns | The profile module is installed and users.two_factor_secret is absent |
| No duplicate route names | Two routes share a name, which would break route:cache |
| Mail from address | MAIL_FROM_ADDRESS is unset or still the default (a skip, not a failure) |
Every failure prints the command that fixes it.