Customization
WireKit offers 4 levels of customization. Use the lightest level that solves your need.
Customization vs. Theming vs. Design Tokens.
Themingis the setup guide + theme-preset gallery — how to wire tokens into yourapp.css, the seven copy-paste presets (Default, Minimal, Soft, Material, Brutalist, Retro Terminal, Cupertino), and the accessibility guarantees.Design Tokensis the complete variable reference — every--color-wk-*/--radius-wk-*/--shadow-wk-*plus the reading-* component family and chart palette. Customization (this page) is the decision tree — when to override tokens vs. tweak PHP defaults vs. apply scope-based personalization vs. publish the view.
Level 1: CSS Variables
Change colors and radii without touching PHP. See Theming for the full setup walkthrough and Design Tokens for the complete variable reference.
/* In your app.css, after @import 'tailwindcss'; */
:root {
--color-wk-accent: var(--color-blue-600);
--radius-wk-md: 0.75rem;
}
.dark {
--color-wk-accent: var(--color-blue-400);
}
Use this level when: every component should pick up the same change (e.g. brand accent color, global border radius). The override cascades on top of the separately-loaded wirekit.css. No @layer base wrapper is needed — the --color-wk-* tokens are plain CSS custom properties, not Tailwind theme tokens.
Using WireKit tokens in your own Tailwind classes
You can reference any --*-wk-* token inside a Tailwind v4 arbitrary-value utility — but Tailwind needs a type hint so it compiles the value:
- Color:
text-[color:var(--color-wk-text-muted)],bg-[color:var(--color-wk-bg-subtle)] - Length (font size, ring width):
text-[length:var(--text-wk-sm)] - Spacing / radius (untyped):
p-[var(--padding-wk-x-lg)],rounded-[var(--radius-wk-md)]
Omit the color: / length: hint and Tailwind v4 mis-types the arbitrary value and silently drops the class. And because Tailwind only generates classes it has scanned, a newly-authored arbitrary-value class appears only after the dev server re-scans — restart npm run dev (or run a one-shot npm run build) after adding one. When a WireKit component already carries the token, prefer the component over a hand-written token class.
Level 2: PHP Defaults
Set project-wide defaults for every component — no view publishing needed.
In your AppServiceProvider::boot():
use Pushery\WireKit\WireKit;
WireKit::defaults([
'button' => ['intent' => 'neutral', 'surface' => 'outline', 'size' => 'lg'],
'input' => ['size' => 'lg'],
'dropdown' => ['placement' => 'bottom-end', 'offset' => 4],
'modal' => ['size' => 'lg', 'dismissible' => true],
'drawer' => ['position' => 'left', 'size' => 'md'],
'tooltip' => ['placement' => 'bottom', 'delay-show' => 500],
]);
Every key must be a prop the component declares
An unknown one does not fail — Blade folds it into the attribute bag, where it renders as a literal HTML attribute that nothing reads, so the page looks finished. button is the trap worth naming: it has intent and surface, never variant.
In development WireKit logs a warning naming the component, the unrecognized name and the closest declared prop, so a typo surfaces instead of rendering as a silent no-op. It is written to your log channel, not to the page, and it is skipped entirely in production. Valid HTML attributes and framework wiring (aria-*, data-*, wire:, x-, v-) are never flagged. Check the component's own page when you are unsure.
Reading back what you set. WireKit::defaultsFor('button') returns the array registered for
one component, and an empty array when nothing was registered for it:
use Pushery\WireKit\WireKit;
// 1. What THIS call registered — a record of the write, not the value in effect.
$registered = WireKit::defaultsFor('button');
It reports what was registered, not what is in effect
A default can also come from the published config file, and WireKit::defaults() writes into that
same config so both routes end up in one place. defaultsFor() reads only the runtime record, so a
component styled entirely from config/wirekit.php returns an empty array here while rendering with
defaults. Do not read an empty array as “this component has no default” — read
config('wirekit.components') for the value that is actually in effect.
Use this level when: a specific component prop should have a different default project-wide (e.g. every button is size="lg"). Tokens (Level 1) change the look; defaults (Level 2) change the behavior. Both are global; both can coexist.
Level 3: Deep Personalization
Replace entire CSS class blocks per component:
WireKit::personalize('button', [
'base' => 'inline-flex items-center justify-center font-semibold tracking-wide',
]);
Use this level when: the change is structural (the layout / spacing / typography of a specific component slot) and a token override can't express it. Personalization replaces the WireKit-supplied class string for that slot wholesale, so you take ownership of the slot's structural CSS.
Adjusting a block instead of replacing it
Taking ownership of a slot has a cost that shows up much later: from that point the block is yours, and every subsequent improvement WireKit makes to it stops reaching your app. Nothing is broken, so the override simply looks like a decision you made, for as long as it stands.
php artisan wirekit:doctor names them for you. It reports every block whose value is a finished class string — the shape that replaces — and stays silent when every block extends. That puts the trade in front of you on the day you go looking, rather than on the day an upstream change quietly failed to arrive. If you want the same list in your own tooling, WireKit::personalizedComponents() returns it.
When your change is a delta rather than a rewrite, give the block a closure instead of a string. It receives the class string WireKit ships and returns the one you want:
WireKit::personalize('sidebar.item', [
// 1. $vendor is the block exactly as WireKit ships it, this version.
// 2. Return what you want rendered — here, everything plus one change.
'base' => fn (string $vendor): string => $vendor.' rounded-none',
]);
You are not limited to appending. The point is that you can see what you are overriding, so you can also remove or swap one part and keep the rest:
WireKit::personalize('button', [
'base' => fn (string $vendor): string => str_replace('font-medium', 'font-bold', $vendor),
]);
Both forms work on personalize() and on scope(). A plain string still replaces the block outright — that behavior is unchanged, and it is still the right choice when you genuinely want to own the slot.
Prefer the closure form whenever your change is small. It keeps you on the upgrade path: a new utility WireKit adds to that block arrives in $vendor and flows through, while your delta stays applied.
This applies to personalize() and scope(), not to the config-file overrides below. A closure in a config file cannot survive php artisan config:cache, so a personalization written there would work in development and vanish on the first cached deploy.
Class overrides via config
The same class blocks can also be set straight in config/wirekit.php under components.{name}.classes.{block} — the lowest-friction override, with no service-provider boot code:
// config/wirekit.php
'components' => [
'button' => [
'classes' => ['base' => 'inline-flex items-center rounded-full'],
],
// Sub-components use their LITERAL dotted name as the key — 'sidebar.item',
// 'card.header', 'table.th', 'dropdown.panel'. Keep the dot IN the key;
// do NOT nest it as 'sidebar' => ['item' => [...]].
'sidebar.item' => [
'classes' => ['active' => 'bg-[var(--color-wk-accent)] text-[color:var(--color-wk-accent-fg)]'],
],
],
Config overrides sit below personalize() and scope() in the priority chain (deep > scoped > config > default), so a personalize() call always wins over a config entry for the same block.
Heads-up — class strings authored in PHP or config are outside Tailwind's content scan. A utility that appears only inside a
config/wirekit.phpvalue or apersonalize()call (never in a Blade template) is not generated unless you point Tailwind at that file with an@sourceline in your CSS — see the Source Scanning step in the Integration guide. Without it the override resolves to a class name that has no matching CSS, and the change silently does nothing.
Scoped Personalization
Apply personalization only to specific instances:
WireKit::scope('pill', [
'button' => ['classes' => ['base' => 'rounded-full']],
'input' => ['classes' => ['base' => 'rounded-full']],
]);
<x-wirekit::button scope="pill">Pill Button</x-wirekit::button>
<x-wirekit::button>Normal Button</x-wirekit::button>
State-Dependent Slots
Most slots describe a component's resting appearance. A few describe how it looks in a particular state — and those are worth calling out, because they are what you need when a brand styles the selected thing differently rather than just recoloring the whole control.
segmented-control exposes both branches of its selection state:
// 1. Restyle the selected segment — e.g. a brand fill instead of the
// default elevated pill.
WireKit::scope('brand-tabs', [
'segmented-control' => ['classes' => [
'segment-selected' => 'bg-[var(--brand-gold)] text-[var(--brand-navy)] font-semibold',
'segment-unselected' => 'text-[var(--brand-navy)]/60 hover:text-[var(--brand-navy)]',
]],
]);
A state slot is replaced wholesale, never merged — so your override must describe the state's full appearance: background, text color, elevation and weight. Setting only a background silently drops the rest, leaving the selected segment with no text-color or shadow treatment at all.
Not every component with reactive state offers these slots yet. Where the appearance is still resolved inside the runtime binding, a per-instance override cannot reach it and a global token override is the way to change it.
Level 4: Publish Views
Full ownership — edit the Blade templates directly:
php artisan vendor:publish --tag=wirekit-views
Views land in resources/views/vendor/wirekit/components/ and take priority over the package views.
Use this level when: Levels 1–3 can't express the change (entirely new DOM structure, custom slots, custom Alpine wiring). Publishing is the heaviest tool and the only one that survives a custom-Blade-author's full creative control. Avoid using it as a shortcut for what Levels 1–3 can already do — see the warning below.
Published views are detached from updates: when WireKit releases a new version, published views never receive changes automatically. Avoid publishing more than you actually need to modify, or you must hand-merge upstream improvements on every release.
Additional Configuration
Beyond the 4-level component customization, WireKit offers config-level settings for icons and charts:
- Icon Presets: Switch icon sets globally via
config/wirekit.php— see Icon docs - Chart Adapters: Configure chart libraries via config — see Chart docs
- Font Presets: Configure locally bundled fonts — see Fonts docs