Documentation
Everything you need to build with Vantage.
Getting started
Vantage Laravel is an ordinary Laravel 12 application. There is no starter kit to unpick and no admin package wrapping the framework — routes are routes, views are Blade, and the asset pipeline is stock Vite.
It runs with no database. Every screen reads from a plain PHP class
in app/Support/Demo, so php artisan serve is enough to see
the whole template working.
Install dependencies
composer install
npm install
Requires PHP 8.2 or newer and Node 18 or newer.
Build the assets
npm run build
Compiles the stylesheet and the script into public/build.
Serve it
php artisan serve
npm run dev
The app runs at http://127.0.0.1:8000, with Vite hot-reloading your edits.
The stylesheet is shared, not copied
resources/sass/app.scss compiles the same layers under
packages/ui/scss that the static, React and WordPress editions use.
A change to a token or a component rule moves all of them at once.
File structure
app/
├── Http/Controllers/ DashboardController · OrderController · CalendarController
├── Providers/ Registers the pager view
└── Support/Demo/ Mock data — replace with your models
config/
├── vantage.php Branding, default skin, the signed-in user
└── navigation.php The sidebar, as data
resources/
├── sass/app.scss tokens → Bootstrap → shared layers → product shell
├── js/app.js Bootstrap's bundle + the shared behaviour script
└── views/
├── layouts/ app · auth · blank
├── partials/ sidebar · topbar · icons · auth art · theme boot
├── components/ The Blade component library
├── pages/ One view per screen
└── vendor/pagination/ The template's pager
routes/web.php Every screen, with named routesRouting & layouts
Every screen is a named route in routes/web.php. The sidebar resolves
those names from config/navigation.php, so renaming a route is caught
at render time rather than shipping a dead link — and the active state works itself
out from request()->routeIs().
To add a screen:
- Add a view under
resources/views/pages/that extends a layout. - Add a named route in
routes/web.php. - If it belongs in the sidebar, add a line to
config/navigation.php.
Three layouts ship: layouts.app (sidebar and topbar),
layouts.auth (the split sign-in screen) and layouts.blank
(a bare shell for pages like the 404).
No authentication is included
The template has no auth layer, and guessing at one would only be in your way. The
sign-in screens are presentation only. Install Laravel Breeze — the field names
already match what it expects — then wrap the application routes in
->middleware('auth').
Blade components
resources/views/components holds anonymous Blade components for every
recurring piece of the interface. They are plain files — no class, no registration.
<x-card hover>
<x-card-header title="Recent orders" subtitle="Updated 4 minutes ago" />
<x-card-body>
<x-badge tone="success">Paid</x-badge>
<x-meter :value="68" label="Storage used" />
</x-card-body>
</x-card>
| Component | Purpose |
|---|---|
x-card and friends | The surface every panel is built on. |
x-page-header | Breadcrumb, title, lede and an actions slot. |
x-stat-tile | A figure with an optional icon and trend. |
x-badge | Status, with a dot so it is not colour alone. |
x-trend | An arrow and words, for direction of travel. |
x-meter | A progress bar with the right ARIA built in. |
x-alert | Four tones, each with its own icon. |
x-empty-state | What a table shows when it has nothing. |
x-avatar | Initials, three sizes. |
x-chart | Area, bar or donut. See below. |
x-icon | One symbol from the sprite. |
A card header that holds controls rather than a heading — the filter bars on Orders
and Customers, for instance — is written out as a plain
<div class="spm-card__header">, because a tablist must not sit
inside an <h2>.
Replacing the data
The demo classes return Laravel collections, so the controllers already read the way they would against Eloquent. Swapping in real models is mostly deleting code.
// Before — a collection built in PHP
$orders = Orders::all()->where('status', $status);
// After — an Eloquent query
$orders = Order::query()
->when($status, fn ($q) => $q->where('status', $status))
->paginate(8)
->withQueryString();
OrderController builds its paginator by hand only because these rows
come from an array. With Eloquent you call ->paginate() and delete
that method — the view does not change, because it already speaks
$orders->links(), ->firstItem() and
->total().
Derived figures are computed rather than stored — invoice totals come from their line items, device percentages from the same numbers the donut draws — so the text beside a chart cannot drift out of step with it. Keep that habit.
Design tokens
Colour, type, spacing, radii, shadows and motion all live in one file:
packages/tokens/tokens.json. Running npm run tokens from
the repository root compiles it into Bootstrap Sass variables and CSS custom
properties.
/* Every token is also a CSS custom property at runtime */
.my-component {
color: var(--spm-neutral-700);
background: var(--spm-neutral-0);
padding: var(--spm-space-5);
border-radius: var(--spm-radius-lg);
box-shadow: var(--spm-shadow-md);
transition: all var(--spm-motion-base) var(--spm-motion-ease);
}
To change the palette, edit the JSON and rebuild. Nothing else needs touching.
Theming & skins
Dark mode uses Bootstrap 5.3's native colour modes:
<html data-bs-theme="dark">
resources/views/partials/theme-boot.blade.php is a small inline script
that sets this before first paint, reading a saved preference from
localStorage and falling back to the operating system setting. That is
what prevents the white flash you get with a bundled theme script. Keep it inline
and keep it ahead of the stylesheet.
Skins are accent presets on the same element:
<html data-spm-skin="ocean">
The default comes from config/vantage.php, and a visitor's choice is
remembered. Six ship with the template: indigo, ocean,
forest, ember, rose and slate.
Add your own in the skins block of tokens.json.
Skins recolour the whole interface
Accents derive from --bs-primary-rgb, so navigation, avatars, badges,
selection states and charts all follow the skin — not only buttons.
Charts
Vantage includes a small SVG chart renderer with no dependencies — no Chart.js, no ApexCharts, nothing to license or keep updated. Pass PHP arrays; the component encodes them for you.
<x-chart
type="area"
:height="280"
:series="[18400, 21200, 19800, 24600]"
:labels="['Jan', 'Feb', 'Mar', 'Apr']"
alt="Revenue by month, rising from 18,400 to 24,600" />
| Prop | Purpose |
|---|---|
type | area, bars or donut. |
series | Array of numbers, or of ['label' => …, 'value' => …] for donuts. |
labels | Axis labels. Not used by donuts. |
height | Height in pixels. Optional. |
alt | Describes the chart for screen readers. Always write one. |
Charts measure their container and redraw on resize and on theme change, so they
stay proportional in narrow columns instead of being squashed by a fixed
viewBox. If you prefer a full charting library, keep the div and point
your own code at it.
Icons
The icon set is original, drawn on a 24×24 grid, and inlined as an SVG sprite from
resources/views/partials/icons.blade.php. There is no icon font and no
external request.
<x-icon name="i-search" />
<x-icon name="i-bell" label="Notifications" />
Icons inherit currentColor and their size from the .icon
class, so they match the text beside them automatically. An icon with no
label is decorative and hidden from assistive technology; if an icon is
the only content of a control, label the control instead.
The UI Kit lists every icon and copies its tag when you choose one.
Accessibility
What is built in, so you do not undo it by accident:
- Every page opens with a skip link to the main region.
- Focus is never removed —
:focus-visibledraws a visible ring for keyboard users. - Status is never carried by colour alone; badges have dots, trends have arrows and words.
- Tables use real
<th scope>headers and a visually hidden<caption>. - Icon-only buttons carry an
aria-label. - Charts expose
role="img"with a written description. - Filters are links with real URLs, so they work without JavaScript and survive the back button.
prefers-reduced-motiondisables every animation in the template.- Body text meets WCAG AA contrast in both light and dark modes.
When you add content
Keep the aria-label on icon-only buttons and write an alt
for every chart. Those are the two that get dropped most often.
Support
Support covers template bugs, build problems and questions about how something in Vantage works. It does not cover customisation of your own application code, third-party packages, or hosting.
Include your PHP and Laravel versions, the screen you are on, and a screenshot or short description of what you expected. That usually turns a two-day thread into a one-reply answer.
Social Points Media — see the item page for current support contact details.