electrik/slate
Slate is a shadcn-inspired Blade UI kit for Laravel. Build product interfaces with anonymous x-slate::* components, Tailwind CSS v4, Slate-owned design tokens, first-class dark mode, and Livewire-aware forms—all in Blade, no React or copy-paste CLI required.
Slate is a set of anonymous Blade components for Laravel with pre-styled markup driven by Tailwind CSS v4 and its own design tokens. You don't install a JS framework or a service provider you configure — you install it, point Tailwind at it, and start replacing hand-rolled markup.
Install the package
composer require electrik/slate
Requires Laravel 12.x/13.x. If the quickstart lists publish tags (config, views, assets), publish only the config to start: php artisan vendor:publish --tag=slate-config.
Wire it into Tailwind v4. There is no tailwind.config.js in a v4 setup. In resources/css/app.css, import Slate's stylesheet before your own layers and add a @source entry so Tailwind scans the vendor Blade files:
@import "tailwindcss";
@import "../../vendor/electrik/slate/resources/css/slate.css"; /* check exact path in docs */
@source "../../vendor/electrik/slate/resources/views";
The @source line is the step people skip, and it's the one that causes "it works locally, broken in prod" bug reports.
Verify dark mode. Slate's dark mode is class-based. Add the variant and a toggle on <html>:
@custom-variant dark (&:where(.dark, .dark *));
<html class="{{ $dark ? 'dark' : '' }}">
Look here first, in this order: the Quickstart page (slate.electrik.dev/docs/quickstart), the Components index (that's your real API reference — variant/size prop tables), then vendor/electrik/slate/resources/views/ to see what the component actually emits. If you use an AI assistant, https://slate.electrik.dev/llms.txt and the MCP docs are worth feeding it so it stops inventing prop names.
First use case: take one existing form and one modal in your app and rebuild them with Slate primitives — input, label, button, card, dialog. That exercises attribute passthrough, variant props, dark mode, and interactivity in about 20 minutes, which is enough to decide whether the kit fits your design language.
Compose, don't configure. Slate is closest to shadcn in spirit: components are markup you own the call site for. Treat the components as primitives and build your own thin wrappers around them so your app has one place to change a pattern:
{{-- resources/views/components/field.blade.php --}}
@props(['label', 'name', 'error' => null])
<div class="space-y-1.5">
<x-slate::label for="{{ $name }}">{{ $label }}</x-slate::label>
<x-slate::input id="{{ $name }}" name="{{ $name }}" {{ $attributes }} />
@error($name) <p class="text-sm text-red-600">{{ $message }}</p> @enderror
</div>
Now <x-field name="email" label="Email" type="email" wire:model="email" /> everywhere, and a restyle is a one-file change. Do the same for page headers, empty states, and table shells — three or four wrappers cover most apps.
Use variant props as the API, not class overrides. The point of a token-driven kit is that variant="outline" and size="sm" stay consistent; passing class="bg-blue-500" at every call site reimplements the theme badly. Reserve inline classes for layout (margins, grid placement) and push visual changes down into the token layer.
Theme by overriding tokens, not views. Slate owns the tokens, but you can still set CSS custom properties in your own @theme / :root block after the import. That's the cheap path to "our brand colors" without forking Blade. Only publish a view when you need a structural change — not a color.
Livewire integration is mostly free. Because the components are stateless Blade, they work inside Livewire components as-is: pass wire:model, wire:click, wire:confirm straight through, since attribute passthrough merges them onto the root element. The pattern that pays off:
wire:key on every repeated component in a @foreach — modal/dropdown state is client-side and morphing will otherwise leave stale open states.wire:navigate, listen for livewire:navigated if you have custom JS touching Slate markup.Layout and blocks. Build the app shell (sidebar, topbar, page container) yourself in layouts/app.blade.php and pull Slate in for the chrome pieces. Use the Blocks section of the docs for whole-page scaffolding — copy the markup into your views rather than trying to make blocks configurable.
Ship form validation the Laravel way. Pair components with Form Requests and @error/$errors->has(); don't add client-side validation libraries on top. If a component supports an invalid state, drive it from $errors->has($name) so server and client agree.
Tailwind v4 will silently purge vendor classes. If every interactive-looking component renders flat, your @source path is wrong or missing. Confirm by grepping the built CSS for a distinctive Slate class. This is the single most common failure mode with vendor-installed Blade kits under v4.
Don't paste a v3 tailwind.config.js. v4 theming lives in CSS. If you follow a blog post that adds content: [...] and theme.extend, you'll get a build that ignores half of it. Use @source and @theme.
Dark mode needs two things, not one. The @custom-variant declaration and a .dark class on an ancestor. If toggling does nothing, check the variant first — the class alone is inert.
Published views are a fork. vendor:publish copies the Blade file into your app; from then on you get no upstream fixes for it and you own the upgrade diff. Publish the 1–2 components you genuinely must restructure, leave the rest in vendor, and note which ones you published in your upgrade checklist.
Attribute merging order matters. Anonymous components typically merge() defaults with what you pass; your class="mt-4" should win, but a conflicting utility (e.g., a component default p-4 vs your p-2) may not resolve the way you expect without a modifier. When a style "won't take", inspect the rendered class list before assuming a package bug.
Interactive components imply Alpine. shadcn-style dialogs, dropdowns, and tabs need client state. Confirm whether Slate ships Alpine or expects yours; if you already have Livewire, Alpine is present, but stacking a second copy of Alpine is a classic breakage. Test keyboard nav and escape-to-close — small packages often under-test a11y, and you don't want to discover a focus trap bug in production.
Treat it as a young dependency. 33 stars, zero dependents, rapid releases. Pin the version in composer.json, read the CHANGELOG before composer update, and keep your wrapper components thin so a breaking component rename costs you minutes, not a day. Avoid mixing Slate with another Blade UI kit in the same project — token and class collisions will produce genuinely confusing styling bugs.
Debugging workflow: php artisan view:clear after publishing, inspect vendor/electrik/slate/resources/views/ for the true markup, and diff your published views against vendor after every upgrade (diff -r vendor/electrik/slate/resources/views resources/views/vendor/slate). If a component misbehaves inside Livewire, reproduce it as plain Blade first — that separates package issues from morphing issues in about one minute.
How can I help you explore Laravel packages today?