Component Anatomy
When you run php artisan ui:add button, the registry
copies the component's Blade file into
resources/views/components/ui/ inside your project.
From that moment it is your code — an anonymous Blade component like any other.
No runtime dependency on this package is required; you can read, edit, and version it freely.
Blade API
Every component is invoked with the standard Laravel anonymous-component syntax. Props control variant, size, and behaviour; the default slot carries content.
<x-ui.button variant="secondary" size="sm">
Save changes
</x-ui.button>
Component names use the neutral <x-ui.*> prefix.
The Jml/jml brand
is confined to the package namespace, registry, and docs site — nothing that gets copied into
your app leaks that name. This keeps the installed code portable and rebrandable.
Props, slots & attributes
Anonymous Blade components declare their API through
@props() at the top of the file.
Anything not in that list is collected into
$attributes and forwarded to the root element
via $attributes->merge().
{{-- @props declares prop names and optional defaults --}}
@props([
'variant' => 'default',
'size' => 'md',
])
@php
$styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
$button = $styles['button'];
$classes = trim($button['base'].' '.($button['variants'][$variant] ?? $button['variants']['default']).' '.($button['sizes'][$size] ?? $button['sizes']['md']));
@endphp
<button
{{ $attributes->merge(['class' => $classes]) }}
data-slot="button"
>
{{ $slot }}
</button>
This means you can pass any HTML attribute — disabled,
id, aria-*,
extra classes — and it lands on the root element automatically:
{{-- Extra attributes you pass flow through $attributes->merge() onto the root element --}}
<x-ui.button variant="primary" class="w-full" disabled>
Submit
</x-ui.button>
Some components expose named slots for regions like headers, footers, or icons. Named slots are documented on each component's page. Because the file lives in your project, you can inspect — or modify — the slot structure at any time.
The data-slot contract
Every primitive exposes a stable data-slot attribute on
its root element (and on significant internal regions, e.g.
data-slot="card-header"). This gives you a reliable
selector that survives class name refactors:
{{-- Target data-slot in CSS or Alpine expressions --}}
<style>
[data-slot="button"]:focus-visible {
outline: 2px solid hsl(var(--ring));
}
</style>
{{-- Or in Playwright / Pest browser tests --}}
$page->locator('[data-slot="card-header"]')->assertVisible();
Components also use logical CSS properties
(ms-, me-,
ps-, pe-, …)
so that layout mirrors automatically when the page direction is
dir="rtl".
See the RTL guide
for enabling bidirectional support.
Styling & tokens
Components reference semantic design tokens — Tailwind utilities backed by CSS
custom properties defined in ui.css. These tokens
adapt automatically to light/dark mode and to admin vs customer surfaces without any
per-component changes.
{{-- Correct: semantic design tokens --}}
<div class="bg-card text-card-foreground border border-border rounded-radius-md">
<p class="text-muted-foreground text-sm">Helper text</p>
</div>
{{-- Wrong: raw colour utilities (test-enforced; will fail BlockRenderTest) --}}
{{-- <div class="bg-white text-gray-900 border-gray-200"> --}}
Warning
bg-blue-500,
text-gray-900) and inline
hsl(…) styles are test-enforced failures.
BlockRenderTest will reject a block that ships
raw colours — use semantic tokens only.Common token names you will encounter:
bg-background/text-foreground— page surfacebg-card/text-card-foreground— card surfacebg-primary/text-primary-foreground— primary actionbg-muted/text-muted-foreground— de-emphasised contentborder-input— form field bordersring— focus ring colour
Variant and size class maps belong in
packages/ui/resources/views/components/ui/_styles.php,
not inlined across every Blade template. Installed components load the copied
resources/views/components/ui/_styles.php
recipe file so class strings remain Tailwind-scannable and easy to override:
{{-- Variant maps live in packages/ui/resources/views/components/ui/_styles.php --}}
{{-- Components load the copied recipe file directly, so the package is not needed at runtime. --}}
@props(['variant' => 'default', 'size' => 'md'])
@php
$styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
$badge = $styles['badge'];
$classes = trim($badge['base'].' '.($badge['variants'][$variant] ?? $badge['variants']['default']).' '.($badge['sizes'][$size] ?? $badge['sizes']['md']));
@endphp
<div
{{ $attributes->merge(['class' => $classes]) }}
data-slot="badge"
>{{ $slot }}</div>
Full token definitions — including radius, typography, shadow, motion, and z-index scales —
live in packages/ui/resources/css/ui.css.
See the Theming guide
to customise or extend them.
Where files land & editing
After ui:add, each component lands at the path its
registry manifest declares under target. For
Blade components that is always inside
resources/views/components/ui/; for JavaScript
behaviour modules it is resources/js/ui/, with
an import appended to resources/js/ui/index.js.
Note
Running php artisan ui:update button later is
edit-aware: the CLI shows a diff between the upstream version and your local
file before writing anything, so your customisations are never silently overwritten.
See the Updating guide
for the full update and diff workflow.
ui:add button