mortenebak/tallstarter
mortenebak/tallstarter is an opinionated Laravel starter kit for the TALL stack, combining Tailwind CSS, Alpine.js, and Livewire with ready-made authentication, layouts, and common scaffolding so you can skip boilerplate and start building features faster.
tallstarter is a starter kit, not a composer library. You consume it once, when creating a project — then it's your codebase. Two supported flows:
# Laravel 12 installer, picks the kit as the skeleton
laravel new my-app --using=mortenebak/tallstarter
# or clone/fork it directly (recommended if you plan to track upstream)
git clone https://github.com/mortenebak/tallstarter my-app && cd my-app
composer install && npm install
cp .env.example .env && php artisan key:generate
php artisan migrate --seed
npm run dev # or: composer run dev
If you use Sail: vendor/bin/sail up -d then run the artisan commands through Sail. On Herd the app is already served at https://my-app.test.
Read these five files first, in this order:
routes/web.php — the kit maps routes straight to Volt page components; there is usually no controller layer for UI.database/seeders/DatabaseSeeder.php (and any RoleSeeder/PermissionSeeder it calls) — this defines the permission vocabulary you'll extend.app/Models/User.php — traits (HasRoles/HasPermissions), relationship methods, and the casts() method.app/Providers/AppServiceProvider.php — look for Gate::before() (super-admin bypass) and any Model::preventLazyLoading().lang/ and resources/css/app.css — localization keys and the Tailwind v4 @theme block.First use case: log in with the seeded admin user (credentials are in the README/seeder), open the users screen, create a role, tick a few permissions, assign it to a second user, then log in as that user and confirm a nav item disappears. That single loop exercises the model layer, the Livewire components, the Blade @can usage, and the caching behaviour — it will tell you more than reading the docs.
Authorization in three layers, mirroring the kit. The kit's components use all three; stay consistent rather than inventing a fourth:
{{-- Blade / navigation --}}
@can('users.view')
<flux:navlist.item href="{{ route('users.index') }}">Users</flux:navlist.item>
@endcan
// Livewire/Volt action methods — always authorize, never trust the UI
public function delete(int $userId): void
{
$this->authorize('users.delete');
User::findOrFail($userId)->delete();
}
Keep the Gate::before() super-admin shortcut in AppServiceProvider intact — every check in the kit assumes it exists, and removing it silently locks out your seeded admin.
Adding a new admin screen. Copy the nearest sibling rather than starting blank — the kit is internally consistent, so resources/views/livewire/users/… is the best template:
php artisan make:livewire Users/CreateUser --no-interaction
Then mirror the existing component's structure: mount() to seed state, a save() action that runs validate() → authorize() → persist, and a Flux form. Add the route to routes/web.php next to its neighbours and add the @can-guarded nav entry.
Permissions are data, not code. Add new abilities in the seeder with firstOrCreate so re-running the seeder on an existing database is idempotent, then reset the cache. Don't hardcode a role list in a component — read it from the DB so the role editor keeps working.
Localization. Every user-facing string goes through __(), with keys in lang/{locale}/. When adding a language, check whether the kit registers its locale middleware in bootstrap/app.php (Laravel 12 has no Http/Kernel.php) — if locale is chosen per-user, it's a middleware or a session-backed setter, and a new locale file without the middleware wired up will silently render English. php artisan route:list shows whether locale is part of the URL or purely session-driven.
Testing pattern for permission-gated actions. Pest + RefreshDatabase, seeding roles in the test rather than relying on the dev seeder:
use function Pest\Livewire\livewire;
it('forbids deleting users without permission', function (): void {
$user = User::factory()->create();
livewire(Users\Index::class)->actingAs($user)->call('delete', $user->id)->assertForbidden();
});
Livewire 3's livewire() helper is the fast path for component behaviour; use plain HTTP feature tests for anything behind middleware.
Frontend. Tailwind v4 is CSS-first — there is no tailwind.config.js. Design tokens live in resources/css/app.css under @theme. Prefer <flux:button>, <flux:field>, <flux:input>, <flux:modal> over raw markup; the free component set already covers forms, nav, dropdowns, and modals.
Daily workflow: change → vendor/bin/pint --dirty → php artisan test --compact --filter=... → npm run dev if you touched CSS. Use Boost's search-docs before writing new Livewire/Volt code so you get v3/v1 examples rather than v2 muscle memory.
composer require this. It's a skeleton. If you started from your own app and want the kit's features, you're copying files, migrations, and seeders by hand — and upstream improvements become manual cherry-picks. Decide the fork-vs-copy question on day one.new class extends Component { ... }. Helper functions declared outside the class are not visible inside it — use state(), mount(), computed, or extract to a plain class.avatar, badge, button, checkbox, dropdown, field, input, modal, navbar, profile, select, switch, table?… — verify in vendor/livewire/flux). If a view you copy from the docs uses a Pro component (data tables, charts, flux:command), it will render nothing rather than error — check the DOM, not just the PHP.@can wrong" bug. After seeding or editing roles, php artisan permission:cache-reset (or app(PermissionRegistrar::class)->forgetCachedPermissions() in the seeder/teardown). In tests, seed after the cache is warm or you'll chase ghosts.#[Locked] to public properties like $roleId / $userId on edit forms — otherwise a crafted request can retarget the update to another record, bypassing the URL-based authorization you thought you had.wire:key in every loop over users/roles/permissions, or Livewire will reuse DOM nodes and you'll get stale checkbox states in the permission matrix — the exact symptom people misdiagnose as a permission bug.bootstrap/app.php; app/Console/Kernel.php and app/Http/Kernel.php do not exist. Kit docs or blog posts referencing them are stale.->change() migration, otherwise columns are dropped.Unable to locate file in Vite manifest) means assets aren't built — npm run build or start composer run dev.env() outside config files is forbidden by the project conventions; use config('app.name'). The kit's .env keys should already be surfaced through a config file — if you add one, do the same.with('roles', 'permissions') in the index query; the permission matrix renders one row per user and will otherwise fire a query per row per column.app/Policies/, a Gate::before super-admin hook, the seeder's permission map, lang/, and resources/css/app.css @theme. Everything else is meant to be edited in place — that's the trade-off the kit makes.How can I help you explore Laravel packages today?