robsontenorio/mary
maryUI is a set of gorgeous Livewire UI components powered by daisyUI and Tailwind, offering ready-made building blocks for crafting polished Laravel admin panels and apps quickly.
Install (v2 / Tailwind 4 path):
composer require robsontenorio/mary
php artisan mary:install
npm install && npm run build # or: npm run dev
The installer wires up daisyUI in resources/css/app.css and adds a @source glob so Tailwind scans mary's PHP components for class strings. Verify it after install — a missing @source line is the #1 cause of "components render unstyled":
@import "tailwindcss";
@plugin "daisyui";
@source "../../vendor/robsontenorio/mary/src/View/Components/**/*.php";
Icons are a separate dependency. mary does not ship an icon set; install one so icon="o-user" resolves:
composer require blade-ui-kit/blade-heroicons
Where to look first
vendor/robsontenorio/mary/src/View/Components/*.php — the authoritative prop list (public $label, $hint, $icon, etc.) when the docs are thin on a specific attribute.vendor/robsontenorio/mary/src/View/Components/Main.php + the layout docs — you almost always start by swapping your app shell for <x-mary-main>.First use case (the ~20-minute win): rebuild one existing CRUD screen — a listing page — as a Livewire component with <x-mary-table>, a <x-mary-modal> create form, and <x-mary-toast> feedback. That single screen exercises 80% of the package's surface area (layout, table, form inputs, modal, toast, theme) and tells you immediately whether it fits your app.
Minimal shell:
{{-- resources/views/components/layouts/app.blade.php --}}
<x-mary-main with-nav full-width>
<x-slot:sidebar drawer="main-drawer" collapsible class="bg-base-100">
<x-mary-menu activate-by-route>
<x-mary-menu-item title="Users" icon="o-users" link="{{ route('users') }}" />
</x-mary-menu>
</x-slot:sidebar>
<x-slot:actions>
<x-mary-theme-toggle darkTheme="dark" lightTheme="light" class="btn-ghost" />
</x-slot:actions>
{{ $slot }}
<x-mary-toast /> {{-- required once, or all toasts silently vanish --}}
</x-mary-main>
Form components as the default. <x-mary-form> wraps wire:submit, shows the loading state on its submit button, and surfaces the error bag — so you stop hand-rolling wire:loading and @error blocks. Override the actions slot when you need a "cancel" or a secondary button.
<x-mary-form wire:submit="save">
<x-mary-input label="Name" wire:model="form.name" icon="o-user" hint="Legal name only" />
<x-mary-input label="Price" wire:model="form.price" money prefix="R$" />
<x-mary-select label="Role" wire:model="form.role" :options="$roles" option-value="id" option-label="name" placeholder="Pick one" />
<x-mary-datepicker label="Starts" wire:model="form.starts_at" :config="{ dateFormat: 'Y-m-d' }" />
<x-mary-file label="Photo" wire:model="form.photo" accept="image/*" crop-after-change />
<x-slot:actions>
<x-mary-button label="Cancel" link="{{ route('users') }}" />
<x-mary-button label="Save" class="btn-primary" type="submit" spinner="save" />
</x-slot:actions>
</x-mary-form>
Table-driven index pages. The header array is the contract: key must match your row/model attribute (or accessor), and format closures are the clean place for currency/dates/status badges.
public array $headers = [
['key' => 'id', 'label' => '#', 'class' => 'w-1'],
['key' => 'name', 'label' => 'Name', 'sortable' => true],
['key' => 'email', 'label' => 'E-mail'],
['key' => 'created_at', 'label' => 'Since', 'format' => [self::class, 'asDate']],
];
public array $sortBy = ['column' => 'name', 'direction' => 'asc'];
public string $search = '';
public function users() {
return User::query()
->when($this->search, fn($q) => $q->where('name', 'like', "%{$this->search}%"))
->orderBy(...array_values($this->sortBy))
->paginate(10);
}
<x-mary-table :headers="$headers" :rows="$this->users" :sort-by="$sortBy" with-pagination per-page="10" searchable>
@scope('cell_created_at', $row)
<x-mary-badge :value="$row->created_at->format('M Y')" class="badge-ghost" />
@endscope
@scope('actions', $row)
<x-mary-button icon="o-pencil" wire:click="edit({{ $row->id }})" spinner class="btn-ghost btn-sm" />
@endscope
</x-mary-table>
Modal + form pairing. The modal's wire:model must point at a public boolean. Reset the backing model on close so the next open starts clean (otherwise you inherit stale state — a classic Livewire-modal bug that looks like a mary bug).
public bool $modal = false;
public array $form = [];
public function create() { $this->reset('form'); $this->modal = true; }
public function save() {
$this->validate();
User::create($this->form);
$this->modal = false;
$this->success('User created', 'They are now active.', position: 'toast-top toast-end', icon: 'o-check-circle');
}
<x-mary-modal wire:model="modal" title="New user" subtitle="Fill the basics" separator>
{{-- form here --}}
</x-mary-modal>
Toast trait everywhere you need feedback. use Mary\Traits\Toast; and call $this->success()/warning()/error()/info(). Centralizing the position in the layout by convention (always toast-top toast-end) keeps the UI consistent.
Searchable selects: pick the right component. <x-mary-select> for static arrays; <x-mary-choices> when the option list must come from the server (implement a search(string $value) method — the component calls it as the user types); <x-mary-choices-offline> when the full list is already loaded client-side and you just need filtering. Choosing wrong is the difference between a snappy form and a 3-second keystroke delay.
Wrap, don't sprinkle. Create thin app-level components (<x-ui-input>, <x-ui-card>) with @props and $attributes forwarding so your team has one place to enforce label placement, required markers, and brand classes. This is also the escape hatch when you need to change a mary component's markup without forking it.
Testing. These are Blade components, so Livewire tests can assert on rendered text and on component state: Livewire::test(UserIndex::class)->set('search','bob')->assertSee('Bob'). No special harness required — you are testing your component, not mary's.
Tailwind can't see classes built in PHP. class="btn-{{ $type }}" or a header class assembled from a variable will not be generated. Use literal class strings in Blade, or safelist with Tailwind 4's @source inline("..."). This shows up as "styling works locally, broken in production build" because your dev build cached the class from somewhere else.
Missing icons are silent. No icon package → blank square, no error. If a component renders but its icon is invisible, that's almost always the cause.
Toasts require the layout component. Forget <x-mary-toast /> and every $this->success() call becomes a no-op with zero diagnostics. Add it once and never think about it again.
Focus trap vs. third-party JS. mary's modal/file/gallery components trap focus, and there are documented escape hatches (without-trap-focus). If a nested dropdown, date picker, or a JS library stops receiving keyboard events inside a modal, that's your first suspect — don't debug your own JS for an hour.
Date format mismatch. The datepicker speaks JS/moment-style formats on one side and PHP Carbon on the other. Persist as Y-m-d, format for display with a table format closure or an accessor. Same story for money inputs and live models — a couple of these were patched in 2.0.x, so pin a recent patch release.
Pin the version and read the upgrade notes. The release cadence is fast (frequent single-line patches like "Drawer: better id()", "Tabs: fix refresh"), and there were real breaking changes: v2 moved to Tailwind 4/daisyUI 5, and 2.3.0 renamed the menu enabled prop to hidden. Subscribe to releases and budget a branch for upgrades rather than floating ^2.0 and discovering a rename on a Friday.
Choices needs x-cloak/height sanity. It renders before hydration; if you see a tall unstyled select flash, check that the x-cloak CSS rule exists in your base stylesheet — several of these rough edges were fixed over time, so being on a recent patch matters.
Layout height bugs come from nesting. x-mary-main expects to be the root of your layout. Putting it inside another flex container breaks the with-nav height calculation and gives you a double scrollbar.
Editor/TinyMCE licensing. The <x-mary-editor> component wraps TinyMCE, which is GPL. The gpl-license attribute exists for a reason — if you're shipping closed-source, read the TinyMCE terms or use a different editor before you build a feature on it. Similarly, Packagist reports the license as NOASSERTION even though the repo's license.md says MIT; get a human to confirm before a compliance review.
Debugging workflow: when a prop doesn't do what the docs imply, php artisan view:clear and then open vendor/robsontenorio/mary/src/View/Components/<Component>.php. Because these are anonymous components, prop names and defaults are right there in the constructor — faster than searching the site.
How can I help you explore Laravel packages today?