Search by

webmintydotcom / laravel-quickstart-dashboard-inertia

webmintydotcom

Laravel starter kit with common addons, including Intertia and a dashboard skeleton

Package info

github.com/webmintydotcom/laravel-quickstart-dashboard-inertia

Language:TypeScript

Type:project

pkg:composer/webmintydotcom/laravel-quickstart-dashboard-inertia

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-09-25 23:12 UTC

This package is auto-updated.

Last update: 2026-09-29 18:23:58 UTC


README

Introduction

A starter kit for Laravel applications with React, Inertia.js, Shark UI, and Tailwind CSS v4. Designed to help you quickly set up a new full-stack Laravel project with a modern development environment.

Out of the box it ships Fortify-backed authentication screens, an authenticated app shell (sidebar, top bar, light/dark/system appearance), a Settings page, a Profile page, and two removable demo pages: a dashboard that shows every panel state a data-driven page needs, and a vehicle list, detail and edit flow that shows real server-side search, filtering, sorting and pagination. See Application Shell for the details.

Installation

laravel new my-app --pest --npm --using=webmintydotcom/laravel-quickstart-dashboard-inertia

Tech Stack

Layer Technology
Backend Laravel 13, PHP 8.4
Frontend React 19, TypeScript
Routing Inertia.js
Auth Laravel Fortify
UI Components Shark UI (Ark UI)
Styling Tailwind CSS v4
Build Vite 8
Testing Pest, Larastan

Included Packages

Laravel

Version 13 of Laravel is used in this starter kit.

Inertia.js

Inertia.js connects the Laravel backend to the React frontend without needing an API. Pages are React components rendered via Inertia::render() in your routes and controllers.

Homepage | Docs

Ziggy

Ziggy provides a route() helper in JavaScript, so you can use Laravel named routes in your React components.

Docs

Spatie Laravel Data

Spatie Laravel Data is included to help you create data transfer objects (DTOs) in a simple and elegant way.

Docs

Laravel Fortify

Fortify is the authentication backend. There are no auth controllers in this starter kit. Fortify registers the routes, and this app supplies the React screens and the actions behind them. Four of Fortify's features are enabled in config/fortify.php: registration, password reset, profile information updates and password updates. Everything else it ships (email verification, two-factor authentication, passkeys) is deliberately switched off, and you enable one by adding it back to the features array.

That gives you /login, /register, /forgot-password, /reset-password/{token} and /user/confirm-password, rendered by the Inertia pages in resources/js/Pages/Auth/, plus the PUT /user/profile-information and PUT /user/password endpoints that the Profile page submits to. The views are bound in app/Providers/FortifyServiceProvider.php and the actions behind each feature (create user, reset password, update profile information, update password) live in app/Actions/Fortify/. A successful login, registration or password confirmation lands on /dashboard.

Password reset needs real mail configuration in production. .env.example ships MAIL_MAILER=log, so reset links are written to storage/logs/laravel.log locally instead of being delivered.

Docs

Intervention Image

Intervention Image does the server-side avatar resizing in app/Actions/Profile/StoreAvatar.php (see Profile Page).

Homepage | Docs

Laravel Person Name

A validation rule (ValidPersonName) for first and last names, applied by the Fortify create-user and update-profile actions.

Docs

Hashids

A Hashids bridge for Laravel, included for obfuscating sequential ids in URLs. Nothing in the starter kit uses it yet; it is installed and configured so it is ready when you need it.

Docs

Frontend

React

React 19 with TypeScript. Entry point is resources/js/app.tsx with SSR support via resources/js/ssr.tsx.

Docs

Shark UI

Components come from Shark UI: shadcn/ui-style components built on Ark UI and styled with Tailwind CSS and tailwind-variants. Like shadcn/ui, the source is copied into resources/js/components/ui/ and is yours to edit. Behavior and accessibility (keyboard handling, focus management, ARIA) come from Ark UI.

components.json registers the @shark registry, so new components install with the shadcn CLI:

npx shadcn@latest add @shark/<component>

Dependencies: @ark-ui/react, tailwind-variants, tailwind-merge, clsx and Lucide icons.

A few things differ from shadcn/ui and Radix:

  • Ark APIs. Select takes a collection built with createListCollection and a string[] value. Change handlers receive a details object: onValueChange={({ value }) => …}, onCheckedChange={({ checked }) => …}, onOpenChange={({ open }) => …}.
  • Sizes. Button sizes are xs/sm/md/lg/xl plus icon-xs … icon-xl; md is the default and lg matches shadcn's default height. Button also takes isLoading, which swaps the label for a spinner.
  • Fields. Wrap a control in Field with FieldLabel and FieldError so Ark wires up the label, aria-* attributes and invalid styling. components/form-field.tsx does this for text inputs.
  • Status colors. Badge, Button and Alert use the success, info and warning tokens in resources/css/app.css. --destructive-foreground is the red text tone for tinted surfaces, not white.

Local fix: SelectGroup in components/ui/select.tsx rendered its heading only when none was passed. It has been corrected in this copy, so re-check it if you re-install select from the registry.

Homepage | Docs | GitHub

Tailwind CSS

Tailwind CSS v4 with the Vite plugin. Brand palette, semantic color tokens and dark mode support live in resources/css/app.css.

Homepage | Docs

Toast Notifications

goey-toast powers the flash-message toaster (resources/js/components/flash-toaster.tsx). It declares framer-motion as a non-optional peer dependency (floor >=10.0.0), so framer-motion is installed either way; it is listed directly in package.json at ^13.2.0 so the resolved version is one we choose rather than one that drifts to goey-toast's much older floor. Nothing in resources/js imports framer-motion directly, so do not remove it thinking it is unused.

Homepage | Docs

Debugging

Spatie Laravel Ray

Spatie Laravel Ray is included to help you debug your Laravel applications with ease.

Homepage | Docs

Testing

Pest

Pest is included to help you write expressive and elegant tests for your Laravel applications.

Homepage | Docs

Spatie Pest Expectations

Docs

Pest Plugin - Faker

Docs

Pest Plugin - Laravel

Docs

Pest Plugin - Type Coverage

Docs

Pest Plugin - Browser

Browser testing through Playwright. tests/Pest.php already binds the Browser directory to the Laravel test case, so create tests/Browser/ and start writing visit() tests. playwright is in devDependencies; run npx playwright install once before the first browser test.

Docs

Laravel Pao

Pao reformats Pest and PHPStan output into a compact form that is easier for AI coding agents to read.

Docs

Larastan

Larastan is included to help you catch type errors in your Laravel applications using PHPStan.

Docs

Pint

Pint is included to help you format your Laravel code according to the Webminty coding standard.

Docs

Rector Laravel

Rector, with the Laravel rule set, is included to help you refactor and upgrade your codebase automatically.

Docs

Formatting

Prettier

Prettier is included to help you format your code consistently across your project.

Homepage | Docs

Prettier Plugin - Tailwind CSS

Docs

Prettier Plugin - Blade

Docs

AI Tooling

Laravel Boost Guidelines

Webminty's Laravel and PHP coding guidelines, packaged for Laravel Boost so AI coding agents working in this repository follow the same conventions the human contributors do.

Docs

Development

composer run dev starts the PHP server, queue listener, log tail and Vite together. composer run dev:ssr does the same with the Inertia SSR server instead of the Vite dev server (it builds the SSR bundle first).

Task Command
Tests vendor/bin/pest
Static analysis vendor/bin/phpstan
PHP formatting vendor/bin/pint
Rector vendor/bin/rector
TypeScript check npm run typecheck
Prettier npm run format (or format:check)
Production build npm run build (add :ssr for the SSR bundle)

Application Shell

The authenticated app (sidebar, top bar, and pages like Settings) ships as a starting shell, not a finished product.

  • Navigation is a single list edited in resources/js/components/app-shell/navigation.ts. Add, remove, or reorder entries there rather than hunting through the sidebar and mobile drawer components separately.
  • Appearance (light/dark/system) lives on the authenticated user's record, so it follows them across devices. It's also mirrored into an appearance cookie on save, purely so the very first server-rendered response can paint the right theme before Inertia props are available - without that mirror, the page would flash the wrong theme on load. The cookie is httpOnly and read only on the server; no JavaScript touches it. It's exempt from Laravel's cookie encryption in bootstrap/app.php, sharing that exemption list with the sidebar_collapsed cookie below, which JavaScript genuinely does read and write.
  • Sidebar collapse is stored in a cookie rather than localStorage. This starter has SSR wired up, and localStorage isn't available during a server render - a cookie is, so the collapsed/expanded state can be read on the very first render instead of flashing open and then collapsing.
  • Originality is still your job. This shell (layout, components, and defaults) is intentionally generic so it can serve any product. A real product built on this starter still needs its own product promise and a signature visual device that makes it feel like something, not a starter kit.

Profile Page

/profile (resources/js/Pages/Profile.tsx) gives the signed-in user five independent forms, each backed by its own controller and validated into its own named Laravel error bag: profile information (name/email) and password (both submitted to Fortify's own endpoints), avatar, browser sessions, and account deletion. Each form's useForm() call passes its bag name only as the errorBag request option on that form's submit call, so a validation error from one form never renders under another form's field - this only works because every submit call names its bag explicitly. Never pass that bag name (or anything else) as a leading string argument to useForm() itself: in @inertiajs/react that argument is a history remember key, which mirrors the form's state into window.history.replaceState on every keystroke, and three of these forms hold a plaintext password.

Avatars are resized to a 256px square WebP (app/Actions/Profile/StoreAvatar.php) and stored on the public disk. That disk is only browser-reachable through the storage symlink, which php artisan storage:link creates - already wired into composer.json's post-create-project-cmd, so a fresh laravel new --using=... install has it from the start. If avatars 404 in an existing checkout, run php artisan storage:link yourself.

The browser sessions list and "Log out other devices" both depend on SESSION_DRIVER=database - they read from and delete rows in the sessions table. With any other driver the list renders empty and the logout button evicts nobody, though it still reports success.

Two features Fortify ships are still switched off and are not part of this page: email verification and two-factor authentication. Both are cycle 2b work - enabling them means adding a "Verify email" prompt and a two-factor section here.

Demo Pages

Two of this starter's routes render a worked example rather than a blank page, so a cloned application has real layouts, states, and content density to hold itself to instead of starting from nothing. Both are fiction - fixed sample data, not features meant to ship - and both are built to be deleted together.

  • /dashboard - a customer-onboarding pipeline: metrics, a chart, an attention queue and a table. The page shape a dashboard needs.
  • /vehicles - a fleet of 45 vehicles, listed, viewed and edited. The page shape almost every dashboard application actually needs next: a list with real server-side search, filtering, sorting and pagination; a detail view; and a form that validates and saves.

The two use deliberately unrelated vocabularies. That is the honest signal that both are disposable examples rather than two halves of a half-built product.

States

Every dashboard panel reads its state from a ?state= query switch:

  • populated (default) - every panel ready with sample data.
  • empty - every panel in its empty, first-run state.
  • loading - every panel showing its skeleton.
  • partial - the chart panel is unavailable while everything else stays ready. Demonstrates that one stalled panel shouldn't block the rest of the page.
  • error - everything ready except the chart, which shows a load failure and a retry action.

An unrecognised value falls back to the populated state rather than erroring.

The vehicle list carries the same switch, with the two states a list has: ?state=empty for a fleet with nothing in it yet, and ?state=loading for the skeleton. The detail and edit pages have none - they always have a record, so an empty state there would be invented rather than demonstrated. Filtering to nothing is a third, different state, and the list says so in different words with a way to undo it: try /vehicles?q=zzzz. An unrecognised value here falls back to ready rather than erroring - the vehicle list's own default state, not the dashboard's populated.

Data

The dashboard's numbers come from a deterministic PHP provider with no database behind it. The vehicles are a real table, demo_vehicles, because an edit form that appears to save but changes nothing teaches the wrong thing, and search, sorting and pagination are only worth copying if they are the real server-side implementations.

That table is created and populated by a migration inside app/Demo/, loaded by App\Demo\DemoServiceProvider. A migration that seeds is unusual on purpose: it means a fresh clone is populated by php artisan migrate alone, every test starts from the same 45 rows, and removing the demo never has to touch database/seeders/DatabaseSeeder.php. The cost is that those rows exist in every test's database, including tests that have nothing to do with vehicles.

Removing both

When you are ready to start building your own application, run the removal script from the project root:

./remove-demo.sh

It asks for confirmation (pass --yes to skip the prompt), then performs the entire contract below for you: it deletes the three demo directories, restores the /dashboard route to DashboardController, unregisters the demo service provider, removes the Vehicles navigation entry, drops the demo_vehicles table, and clears the cached config and routes. When it finishes it deletes itself - a starter kit with no demo has no use for a demo-removal script, so remove-demo.sh is not something you carry into your own project. The one thing it deliberately leaves behind is chart.js in package.json; remove that yourself if you want it gone.

If you would rather do it by hand - or want to understand exactly what the script does - the contract is below.

rm -rf app/Demo resources/js/Pages/Demo tests/Feature/Demo

chart.js is a dependency only the demo uses (via resources/js/Pages/Demo/chart.tsx); it can be uninstalled in the same pass with npm uninstall chart.js.

Then three one-line edits:

  1. In bootstrap/providers.php, delete the App\Demo\DemoServiceProvider::class entry.

  2. In routes/web.php, point the dashboard route back at the fallback controller that already sits in the tree unrouted for exactly this, restore its import, and delete the four vehicles.* routes:

    Route::get('/dashboard', DashboardController::class)->name('dashboard');

    The route's name('dashboard') does not change, so the Dashboard entry in resources/js/components/app-shell/navigation.ts needs no edit - it links by route name, not by component.

  3. In resources/js/components/app-shell/navigation.ts, delete the Vehicles entry and the now-unused Car icon import.

One thing the contract cannot do for you: deleting the migration does not drop demo_vehicles from a database that has already run it. A fresh clone never creates the table. An existing install drops it by hand. The deleted migration's row in Laravel's own migrations tracking table is left orphaned too, but that is benign - modern Laravel prints "Migration not found" for it and continues rather than failing.

A test suite enforces the rest as the starter grows. The isolation test under the demo's own test directory fails the build the moment anything outside those three directories references the demo namespace, so a stray import can't quietly widen the removal surface. routes/web.php and bootstrap/providers.php are its only two exceptions, both named explicitly. Laravel's generated provider manifest under bootstrap/cache/ is also exempt from the scan, because it is build output that regenerates itself and is never committed - a reader who greps for the demo's name and finds a hit in bootstrap/cache/services.php should not conclude the removal contract is broken. The navigation entry is the one piece no scan can catch - it links by route name and contains nothing that looks like the demo - so it has a test of its own that disappears along with the demo.

This contract was executed for real - directories deleted, provider and routes and navigation stripped, full suite and production build run - as part of building this work, to prove it rather than just assert it.

What is not part of the demo

badge, field, input, select, switch and textarea in components/ui/ are general-purpose primitives the rest of the application uses too, and are not part of the removal. table and skeleton are the same kind of stock primitive, but the demo currently happens to be their only consumer - they stay for the same reason every other unused primitive does: an application built on this starter will want a table and a loading skeleton, and re-adding them by hand is worse than leaving them. Neither is components/form-field.tsx, which the profile and auth pages use.

The demo's own components live under its page directory rather than the shared components/ tree - a deliberate deviation - specifically so that deleting the demo never means picking components back out of a shared folder.

What the tests don't cover

There are no browser tests in this repository, and the vehicle list has two behaviours that only a browser exercises: the debounce on the search box (the server-side result of a search is tested; the fact that typing issues one request rather than one per keystroke is not), and the click behaviour of a sortable column header (the sorted result is tested; the header's own state is not). ?state=loading proves the skeleton branch renders - not that it appears at the right moment during a real in-flight visit.

Additional Configurations

Changes to the default Laravel files are included in this starter kit to improve performance and developer experience.

app/Providers/AppServiceProvider.php

Model::preventLazyLoading();

if ($this->app->isProduction()) {
    Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
        $class = get_class($model);

        info("Attempted to lazy load [{$relation}] on model [{$class}].");
    });
    DB::prohibitDestructiveCommands();
} else {
    Model::preventAccessingMissingAttributes();
    Model::preventSilentlyDiscardingAttributes();
    Model::shouldBeStrict();
}

Vite::usePrefetchStrategy('aggressive');

Thank you Webminty Team