Skip to main content
WireKit
Copy for LLM

Getting Started

This is the recipe used by docs.wirekit.app itself. Following it gets you to a working install with every component rendering, every Alpine-driven interaction working, no console errors, and php artisan wirekit:doctor reporting green.

Recommended setup Laravel 12+/13+ with Livewire v4+, Vite, and Tailwind CSS v4 — this is the configuration WireKit is built and tested against on every commit. Other setups (Livewire v3, Tailwind v3, Inertia, no Vite) may work but receive no continuous-integration coverage and are unsupported. If you're starting a fresh project, follow the "From scratch" recipe below verbatim.

Already using the Livewire Starter Kit? Your Starter Kit project ships with its own pre-configured layout, Vite config, and Tailwind v4 setup — adding WireKit on top requires touching a few specific files instead of following the clean-room recipe. See Adding WireKit to the Livewire Starter Kit for the four-step retrofit.

Prerequisites

Before installing WireKit, your Laravel app needs Laravel 12+/13+ with Livewire v4+ installed and a working Vite + Tailwind CSS v4 toolchain. If your app already meets these, skip to Installation.

From scratch — Laravel + Livewire + Tailwind v4

# 1. Create a Laravel app
composer create-project laravel/laravel my-app
cd my-app

# 2. Add Livewire v4 (provides Alpine.js automatically — no separate npm install needed)
composer require livewire/livewire:^4.0

# 3. Add Tailwind CSS v4 + Vite plugin
npm install tailwindcss @tailwindcss/vite

Wire Tailwind into Vite — vite.config.js:

// 1. Vite is Laravel 12's default asset bundler — defineConfig + the
//    laravel-vite-plugin wire your Blade @vite directive to a dev /
//    build pipeline.
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

// 2. Tailwind v4 ships as a first-class Vite plugin — no PostCSS config
//    file needed, no tailwind.config.js required.
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
    plugins: [
        // 3. Register the CSS + JS entry points Vite watches. `refresh: true`
        //    triggers a Livewire-aware page-reload on Blade / route changes.
        laravel({ input: ['resources/css/app.css', 'resources/js/app.js'], refresh: true }),
        // 4. Activate Tailwind's compiler — utilities are scanned out of
        //    your views at build time.
        tailwindcss(),
    ],
});

Replace resources/css/app.css with the v4 entrypoint:

@import 'tailwindcss';

Verify the toolchain runs cleanly:

npm run dev

Vite should start without errors. Stop with Ctrl+C once you see the green "ready" banner.

Don't skip the next step At this point your Tailwind v4 toolchain is wired but your project has no WireKit yet — the next two commands must run before any production build. WireKit components emit Tailwind utility classes; without registering WireKit's Blade templates as a Tailwind @source, those classes get tree-shaken at build and the components render unstyled. wirekit:install writes the @source line for you. If you run npm run dev between steps 1 and 4 below and see unstyled output, that's the symptom.

Installation

# 1. Add WireKit + the two icon packages it relies on at runtime
#    (pushery/wirekit = the component library itself,
#     blade-ui-kit/blade-icons = the icon-renderer <x-wirekit::icon> calls,
#     blade-ui-kit/blade-heroicons = the heroicons preset that ships the actual SVGs)
composer require pushery/wirekit blade-ui-kit/blade-icons blade-ui-kit/blade-heroicons

# 2. Run the WireKit installer — publishes config/wirekit.php, copies the
#    asset bundles to public/vendor/wirekit/, and adds the Tailwind @source
#    line for WireKit's Blade templates to resources/css/app.css
php artisan wirekit:install

wirekit:install does everything in one shot — publishes config/wirekit.php, publishes the assets to public/vendor/wirekit/, and adds the @source line for WireKit Blade templates to resources/css/app.css.

Why install icon packages by default? <x-wirekit::icon> is used internally by buttons, dropdowns, modals, alerts, and the close-button on most overlays. Without blade-ui-kit/blade-icons + a preset (blade-heroicons is the safest default), icons fall back to inert placeholders. They don't crash, but the UX is missing the visual cues. Installing both alongside WireKit keeps the experience complete from day one.

Layout setup — the verified-working pattern

The exact layout shape used by docs.wirekit.app. Two critical points:

  1. Both directives at the end of <body>, in this order: @wirekitScripts THEN @livewireScripts. Always emit both, never gate them on "do I use Livewire on this page?".
  2. Alpine arrives via @livewireScripts — Livewire v4 bundles Alpine, you do NOT install alpinejs via npm and you do NOT import it in app.js.

Two layout paths are first-class on a Livewire 4 install — both work; which one applies depends on how your project was created:

Fresh app (no Starter Kit)resources/views/layouts/app.blade.php. This is the path php artisan livewire:layout creates, because Livewire 4's default page layout is the 'layouts::app' namespace (which resolves to resources/views/layouts/). Reference it with #[Layout('layouts.app')], or omit the attribute entirely since 'layouts::app' is already Livewire's default.

Starter Kit appresources/views/components/layouts/app.blade.php. The Laravel Starter Kits scaffold this path (it matches Laravel's <x-layouts.app> component shape). If your app already ships it, add the WireKit directives there and reference it with #[Layout('components.layouts.app')].

wirekit:install writes the @wirekitStyles + @wirekitScripts directives into your layout. If no layout exists yet — a fresh Laravel + Livewire 4 app (without a Starter Kit) ships none — the installer creates one for you via Livewire's own php artisan livewire:layout command, which produces resources/views/layouts/app.blade.php, then injects the directives (in the correct order, see below). So a clean install needs no manual layout step — just reference that layout with #[Layout('layouts.app')], or leave the attribute off. You can also create the layout yourself first (php artisan livewire:layout, or hand-write the file below) and re-run the installer to wire it. wirekit:doctor honors both canonical paths — it scans every *.blade.php file under either. The example below shows the fresh-app path (resources/views/layouts/app.blade.php) with the directives already in place:

{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ $title ?? 'My App' }}</title>

    @vite(['resources/css/app.css', 'resources/js/app.js'])

    {{-- WireKit's design tokens — the --color-wk-* CSS variables every  --}}
    {{-- component reads for its colors, spacing, radii, and shadows.    --}}
    {{-- Without this line components render against undefined tokens     --}}
    {{-- (i.e. unstyled). wirekit:install adds it for you; it is shown    --}}
    {{-- here so the layout is complete on its own. (If you instead       --}}
    {{-- @import wirekit.css inside app.css — see "Two valid setup        --}}
    {{-- paths for CSS" below — drop this line so tokens don't load twice.)--}}
    @wirekitStyles
</head>
<body>

{{ $slot }}

{{-- Critical order: @wirekitScripts BEFORE @livewireScripts --}}
{{-- @livewireScripts boots Alpine; WireKit's Alpine components must  --}}
{{-- register on the alpine:init event BEFORE Alpine starts.          --}}
@wirekitScripts
@livewireScripts

</body>
</html>

Dark mode. WireKit components are dark-mode aware via --color-wk-* design tokens that auto-switch when <html> carries the class dark. Toggle it with any pattern you like (a button + Alpine x-data, a server-side preference check, prefers-color-scheme MediaQuery — all work). For the simplest possible activation, hardcode <html lang="en" class="dark"> and refresh.

Two valid setup paths for CSS

wirekit:install already wired path 1 for you — it added @wirekitStyles to your layout's <head> (shown above), so a fresh install is already loading WireKit's tokens. Read this section only if you'd rather use path 2 — piping WireKit's CSS through your own Tailwind build, the way docs.wirekit.app does. If you switch to path 2, remove @wirekitStyles from the layout so the tokens don't load twice.

Two valid setup paths for CSS:

  1. @wirekitStyles Blade directive in <head> — emits a <link> tag straight to the package CSS. Fastest path, no Tailwind compile step required for variables.

  2. @import 'wirekit.css' in app.css — pipes through the Tailwind v4 bundler so utilities defined inside WireKit get optimized together with your own. This is what docs.wirekit.app uses:

    /* resources/css/app.css */
    @import 'tailwindcss';
    @import '../../public/vendor/wirekit/wirekit.css';
    @source '../../vendor/pushery/wirekit/resources/views/**/*.blade.php';
    

    Pick whichever fits your build pipeline. Both render byte-identical CSS at runtime.

JavaScript pipeline — keep it minimal

In a standard Laravel app there is nothing to do here. A fresh composer create-project laravel/laravel (the "From scratch" recipe above) already scaffolds both resources/js/app.js and resources/js/bootstrap.js, and the two blocks below simply show what Laravel writes into them by default. WireKit adds nothing to either file. The single point of this section: app.js must NOT import Alpine — Livewire v4 ships Alpine in its bundle and starts it for you when @livewireScripts runs, so a manual import Alpine from 'alpinejs' would boot Alpine twice and break every WireKit interaction.

Do NOT create bootstrap.js if your project doesn't have one Whether the file exists depends on how your project was scaffolded, and the missing-file case is not an invitation to create it:

  • composer create-project laravel/laravel ships resources/js/app.js (holding the single import './bootstrap'; line) and resources/js/bootstrap.js, and lists axios in package.json. Nothing to do — confirm app.js has no Alpine import and move on. The two blocks below are shown only so you can recognize what's already there.
  • The Livewire Starter Kit ships app.js alone. There is no bootstrap.js, and axios is not in its package.json. Writing the bootstrap.js block below into such a project fails the very next build with [vite]: Rolldown failed to resolve import "axios" (or Rollup failed to resolve import "axios" on Vite 7 and earlier), because the import names a package that was never installed.

WireKit uses no axios, so on a project without it the correct action is to create nothing. The blocks below are Laravel's own convention, not a WireKit requirement. If you want that baseline anyway, install the package first:

# Only needed if you actually want Laravel's axios baseline AND your
# scaffold didn't already install it (the Livewire Starter Kit does not).
npm install axios
// resources/js/app.js — as the Laravel skeleton writes it
//
// 1. Single entry point loaded by Vite via the @vite directive in the
//    Blade layout. The skeleton keeps this file as one import statement
//    so future additions stay obvious. Delete the line if you have no
//    bootstrap.js — an import of a file that doesn't exist fails the
//    build just like a missing package does.
import './bootstrap';
// resources/js/bootstrap.js — Laravel convention, NOT a WireKit requirement.
// Requires `axios` in package.json (see the box above before copying this).
//
// 1. Make axios available globally as `window.axios`, the way Laravel's
//    own skeleton wires it. This is for YOUR OWN request code —
//    Livewire does not read window.axios; it issues its own fetch()
//    calls. Nothing in WireKit or Livewire breaks without this file.
import axios from 'axios';
window.axios = axios;

// 2. Tag every axios request as an XMLHttpRequest so Laravel's
//    `Request::expectsJson()` returns true for the AJAX calls you make
//    through axios. It has no effect on Livewire's own requests.
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

That's it. No import Alpine from 'alpinejs', no Alpine.start(), no Alpine version pinning in package.json. Custom Alpine components register via Alpine.data(name, fn) inside document.addEventListener('alpine:init', …) listeners loaded with defer — they fire BEFORE @livewireScripts boots Alpine because defer scripts execute in document order.

Your first Livewire page

A complete, copy-pasteable example wiring up a Livewire page-component, a route, and a wire:model-bound form input on the layout above.

Run the command with --class — the flag is what decides which files you get, so it is not optional here. php artisan make:livewire Showcase --class generates an empty app/Livewire/Showcase.php class plus a placeholder resources/views/livewire/showcase.blade.php view: the two files this walkthrough edits. Leave the flag off and neither file exists — Livewire 4 defaults to a single-file component and writes resources/views/components/⚡showcase.blade.php instead, so every path referenced below would be missing.

Either way the command generates only the file structure — it does NOT write the example code for you. After running it, open each generated file and replace its stub contents with the corresponding block shown below (the PHP class, then the Blade view). The routes/web.php line you add by hand.

# 1. Generate the Livewire component. Livewire 4 creates a SINGLE-FILE component
#    by default (resources/views/components/⚡showcase.blade.php — the ⚡ prefix is
#    intentional, it marks Livewire components in your file tree). This walkthrough
#    uses the class-based form, so pass --class: that creates
#    app/Livewire/Showcase.php + resources/views/livewire/showcase.blade.php. Both
#    arrive as empty stubs (a placeholder <div> + a bare render() method) — that's
#    normal; replace their generated contents with the two files shown below.
#    Prefer class-based everywhere? Set 'type' => 'class' in config/livewire.php
#    and you can drop the flag.
php artisan make:livewire Showcase --class
<?php
// app/Livewire/Showcase.php

namespace App\Livewire;

use Livewire\Component;

class Showcase extends Component
{
    // No #[Layout(...)] attribute is needed here: Livewire wraps a full-page
    // component in its default layout (the 'layouts::app' view, i.e.
    // resources/views/layouts/app.blade.php) — the exact file wirekit:install
    // created for you. To pin it explicitly, add `use Livewire\Attributes\Layout;`
    // plus #[Layout('layouts.app')] above the class. (Starter Kit apps reference
    // the `components.layouts.app` view instead — see Layout setup above.)

    // Public properties become two-way bindings for any wire:model in the view.
    public string $email = '';

    public function render()
    {
        return view('livewire.showcase');
    }
}
{{-- resources/views/livewire/showcase.blade.php --}}
<div class="p-8 space-y-4">
    <x-wirekit::button>Save</x-wirekit::button>

    <x-wirekit::input
        label="Email"
        type="email"
        name="email"
        wire:model="email"
    />

    <x-wirekit::select
        label="Role"
        name="role"
        placeholder="Choose a role..."
        :options="['admin' => 'Admin', 'editor' => 'Editor', 'viewer' => 'Viewer']"
    />

    <x-wirekit::dropdown>
        <x-slot:trigger>
            <x-wirekit::button>Open Menu</x-wirekit::button>
        </x-slot:trigger>
        <x-wirekit::dropdown.item>Profile</x-wirekit::dropdown.item>
        <x-wirekit::dropdown.item>Settings</x-wirekit::dropdown.item>
    </x-wirekit::dropdown>
</div>
// routes/web.php
use App\Livewire\Showcase;

// A fresh Laravel app already maps `/` to the welcome page:
//     Route::get('/', function () { return view('welcome'); });
// REPLACE that existing line with the one below — don't just add a
// second `/` route. When two routes share the same URI, the one
// registered LAST wins, so a leftover welcome route sitting below this
// one would silently shadow your component and you'd still see the
// Laravel welcome screen.
//
// Pointing a route at a Livewire component renders it as a full page,
// wrapped automatically by Livewire's default layout (or the
// #[Layout(...)] you set on the component above). WireKit never touches
// your routes — adding this line is yours to do.
Route::get('/', Showcase::class);

Run the app

WireKit needs both the PHP server (Laravel) and the Vite dev server (CSS + JS bundling) running side by side. Two terminals:

# Terminal 1 — Laravel HTTP server (serves PHP routes on http://127.0.0.1:8000)
php artisan serve
# Terminal 2 — Vite dev server (compiles app.css + app.js, hot-reloads on save)
npm run dev

Open http://127.0.0.1:8000 in your browser. The four WireKit components render styled and interactive: the dropdown opens, the inputs validate, the button hovers, the email field round-trips its value to the Livewire backend on every keystroke. Alpine has booted via Livewire; WireKit's Alpine components have registered on alpine:init.

If your fresh Laravel install needs a database touch (some wire:model round-trips persist via Eloquent later), run:

# 1. Create the SQLite file. Laravel 12 defaults to SQLite but ships no database file,
#    and the driver will not create one for you — the first query fails without this.
touch database/database.sqlite

# 2. Apply the migrations. This builds the tables your Livewire component writes to;
#    until it runs, a wire:model round-trip that persists will fail on a missing table.
php artisan migrate

Build for production

npm run dev above is Vite's development server (hot reload) for local work. For a production deploy — and so the wirekit:doctor "Built app CSS contains WireKit utility rules" check below has a compiled bundle to read — run the optimized build:

# 1. Compile app.css + app.js for production. wirekit:install added the `@source`
#    line, so this build is what bakes WireKit's utility classes into your
#    compiled CSS — without it a production deploy renders unstyled.
npm run build

Dev versus build For local development npm run dev is enough; you only need npm run build for a real deploy. Both modes read WireKit's @source line, so components are styled either way. (wirekit:doctor's built-CSS check stays skipped until a npm run build has produced public/build/.)

Verify the install

php artisan wirekit:doctor

A green install prints a for every check and ends with the totals line and All checks passed. The full annotated transcript, the glyph vocabulary and what each check means live on the command's own page: wirekit:doctor.

If a check fails, the doctor prints the exact fix instruction. The verified-working pattern above clears every check.

Change the prefix

By default, components use the wirekit prefix: <x-wirekit::button>. To change:

php artisan vendor:publish --tag=wirekit-config
// config/wirekit.php
'prefix' => 'ui', // Now use <x-ui::button>

Code that builds a tag name — a page generator, a CMS field, a custom Artisan command — should ask rather than assume, because the prefix is a setting and wirekit is only its default:

use Pushery\WireKit\WireKit;

$tag = '<x-'.WireKit::prefix().'::button>';

JavaScript bundle (full vs core)

The @wirekitScripts directive serves the full bundle by default, which contains every Alpine component including overlays (Dropdown, Tooltip, Modal, Drawer, etc.) and the reading-* family. Current sizes for every bundle are in Dependencies.

If you only use form components and charts, switch to the core bundle in config/wirekit.php:

'scripts' => ['bundle' => 'core'],

Core ships only the chart Alpine component, and is a fraction of the full bundle.

Optional: publish assets for production

For better performance (web server serving instead of PHP routes), publish the CSS and JS:

php artisan vendor:publish --tag=wirekit-assets

This copies wirekit.css and the JS bundles to public/vendor/wirekit/. The @wirekitStyles and @wirekitScripts directives auto-detect published assets and prefer them.

Cache busting is automatic. Both directives append ?v={filemtime} to every URL, so browsers fetch fresh content on every deploy. Even if you forget vendor:publish --tag=wirekit-assets --force after a composer update, WireKit detects the stale published copy and transparently falls back to serving the fresh version via its built-in route — you'll never see outdated CSS or JS in the browser. See Integration → Asset Publishing for the full story.

Next Steps

  • Theming — Change colors without touching a single component
  • Button — Your first component
  • Dropdown — Your first overlay component
  • Integration Guide — Asset publishing, optional dependencies, verification checklist

Optional Features

Each of these is opt-in, lives behind its own config flag, and has a dedicated docs page with the full setup walk-through. The links below are the canonical reference — read them when you actually need the feature.

  • Icons — swap or stack the icon preset (heroicons / lucide / phosphor / tabler), set up the alias system, customize per-component icon defaults
  • Fonts — load GDPR-compliant local fonts (no Google Fonts CDN), pick a sans / serif / mono pairing, override per-component
  • Chart — opt-in Chart.js integration with automatic theming, custom adapters, dark-mode aware data colors. Three steps: set 'charts.library' => 'chartjs' in config/wirekit.php, npm install chart.js, AND add the Chart.register(...registerables) snippet to resources/js/app.js per Integration → Optional Dependencies. Skipping the JS-side registration is the #1 first-run gotcha — wirekit:doctor flags it explicitly.
  • QR Code — render scannable QR codes server-side via bacon/bacon-qr-code (Composer package, not auto-required). See Integration → QR Code for the one-line install + the trade-offs vs. JavaScript-based generators.

Was this page helpful?

Voting requires cookies or local storage. What we store