chandanshakya/mary-ui-starter-kit
Mary UI Starter Kit scaffolds a Laravel app with Mary UI components already wired up, giving you a ready-made Tailwind and daisyUI front end so you can skip the setup and start building polished, consistent interfaces right away.
This is a scaffold, not a dependency. You consume it once, at project birth, then own the code forever. There is no composer require in your day-to-day and no upstream upgrade channel — plan for that from minute one.
Bootstrap, pinned:
composer create-project chandanshakya/mary-ui-starter-kit my-app "v1.0.2"
cd my-app && cp .env.example .env && php artisan key:generate
npm install && npm run build
php artisan migrate --seed
Record the tag and the provenance commit in your README immediately. If composer create-project isn't supported by the repo, clone at the tag and composer install — either way, pin to v1.0.2, never dev-main, because you are about to build on top of this and a moving target makes every later diff useless.
Where to look first, in order:
composer.json + package.json — settle Laravel/PHP, livewire/livewire, livewire/volt, robsontenorio/mary, and daisyUI/Tailwind majors before reading any Blade. These four versions define your entire constraint envelope.tailwind.config.js — the daisyUI plugins registration and the themes array. This file is your design system.resources/views/components/layouts/app.blade.php — the layout almost every page inherits, including the <html data-theme="..."> attribute.routes/web.php — look for Volt::route(...) calls. In a Volt app, routes often live here rather than as controllers, and that surprises people coming from standard Laravel.resources/views/livewire/ — the existing pages are your copy-paste templates. Read two or three end to end.First use case: stand up an internal CRUD screen. Create a Volt component, mount it on a route, and compose MaryUI primitives instead of hand-rolling Tailwind:
// routes/web.php
Volt::route('/projects', 'projects.index')->name('projects.index');
{{-- resources/views/livewire/projects/index.blade.php --}}
<?php
use Livewire\Volt\Component;
use Livewire\WithPagination;
use App\Models\Project;
new class extends Component {
use WithPagination;
public string $search = '';
public function with(): array
{
return [
'projects' => Project::query()
->where('name', 'like', "%{$this->search}%")
->latest()
->paginate(15),
];
}
}; ?>
<div>
<x-header title="Projects" subtitle="Internal tools" separator>
<x-slot:middle class="!justify-end">
<x-input wire:model.live.debounce.300ms="search" placeholder="Search..." icon="o-magnifying-glass" clearable />
</x-slot:middle>
<x-slot:actions>
<x-button label="New" link="{{ route('projects.create') }}" icon="o-plus" class="btn-primary" />
</x-slot:actions>
</x-header>
<x-table :headers="$headers ?? []" :rows="$projects" with-pagination />
</div>
You get a themed, dark-mode-aware, paginated list in about fifteen minutes. That is the whole value proposition — spend your time on domain logic, not on Tailwind scaffolding.
Volt is the unit of work. Day-to-day you live inside resources/views/livewire/**. Each file is a PHP class block followed by its Blade template. Functional API for simple screens (state(), rules(), with()), class API (class extends Component) when you need traits like WithPagination, WithFileUploads, lifecycle hooks, or event listeners. Don't mix styles within a directory — inconsistent API usage is the first thing that makes a Volt codebase confusing.
Layouts: pick a convention and stick to it. Volt routes take a layout via chaining (Volt::route('/x', 'x')->layout('components.layouts.app')) or via #[Layout('components.layouts.app')] on the component. Forgetting it means you render a bare fragment with no <html>; adding a Blade @extends on top means doubled <html> tags and mysterious layout flashes. Choose one; do not mix.
Forms: x-form + wire:model + rules(). MaryUI's form components are built for Livewire binding, and validation messages render themselves.
<x-form wire:submit="save" wire:model="form">
<x-input label="Name" wire:model="form.name" />
<x-select label="Owner" wire:model="form.owner_id" :options="$users" />
<x-textarea label="Notes" wire:model="form.notes" />
<x-slot:actions>
<x-button label="Cancel" link="{{ route('projects.index') }}" />
<x-button label="Save" type="submit" class="btn-primary" spinner="save" />
</x-slot:actions>
</x-form>
Put rules in a rules() method (or a Form object once the screen grows past ~8 fields) and keep flash messages in a single session()->flash('success', ...) convention so the layout's toast slot picks them up.
Tables: keep the query lazy. Use with() for the collection and ->paginate() inside it; x-table handles pagination controls, sorting indicators, and empty-state when you pass them. For per-row actions, use x-slot:actions on a row-level <x-button> with wire:click, and always give interactive children a stable wire:key (wire:key="row-{{ $project->id }}") to avoid swap glitches.
Auth and roles are scaffolding you inherit, not a package you call. The kit ships auth and roles already wired. The practical pattern: define authorization as Gates/Policies in app/Providers/AppServiceProvider.php or app/Policies, then gate the UI with <x-button ... @if(auth()->user()->can('update', $project)) ... @endif> or Livewire authorize() calls in the action methods. Never rely on a hidden button as the security boundary — enforce in the component method too.
Theme switching is a layout-level concern. Set data-theme on <html>; for per-user preference persist a theme column and bind data-theme="{{ auth()->user()?->theme ?? 'light' }}". The daisyUI themes array in tailwind.config.js is the source of truth — the settings dropdown must be derived from it, not hardcoded, or you'll ship dead options.
Copy, then extract. Clone an existing starter page for every new resource (list + form + show). After the second or third clone, extract the repeated shape into resources/views/components/ — your own components sit alongside MaryUI's and compose cleanly. This keeps conventions uniform across a team without a style guide.
Adding packages is unconstrained. Register providers, publish configs, add routes normally. The only kit-specific obligation: add any new view paths to Tailwind's content array, or their classes get purged.
Bumping dependencies is your job now. Livewire, Volt, MaryUI, Tailwind, and daisyUI must move together. Do them in one PR, run the app, and test a form, a table, and a modal — those three surfaces catch most regressions.
content array must include ./vendor/robsontenorio/mary/src/View/Components/**/*.php. Miss it and every MaryUI component renders as unstyled markup. This is the single most common failure in this stack, and it's silent — nothing errors, it just looks wrong.package.json first, then run npm run build, not npm run dev — a stale dev manifest produces the same symptom.livewire/livewire, livewire/volt, and robsontenorio/mary together whenever you bump one.Unable to locate a class or view for component [x-...] means the MaryUI provider isn't registered or its config wasn't published: php artisan vendor:publish --tag=mary. Also check you're not shadowing a vendor component name with one of your own in resources/views/components/.php artisan view:clear resolves a surprising amount of "my change did nothing," especially on compiled Volt components.Volt::route('/projects', 'projects.index') resolves resources/views/livewire/projects/index.blade.php — a nested directory maps to a dot-separated name, not a slash. Get this wrong and you get a 404 with no useful error.database/seeders/ for default users and passwords, confirm the registration route is removed or gated if this is an internal app, and confirm the roles table/gate wiring matches what you actually intend. The scaffold's security posture is a starting point, not an audit.wire:model (deferred) on a search box requires a submit; wire:model.live.debounce.300ms re-queries on keystroke. Mixing them up produces "the filter doesn't work" bug reports that aren't bugs.php artisan vendor:publish --tag=mary to override individual vendor components; your own components under resources/views/components/; a custom daisyUI theme defined inline in tailwind.config.js when the stock themes don't match your brand. Prefer the custom theme over restyling individual components — it's one file instead of forty.php artisan view:clear → check the Tailwind content array → confirm npm run build output is what's being served → php artisan about for version drift → verify the route actually resolves to a Volt component. That sequence resolves the overwhelming majority of issues in this kit.How can I help you explore Laravel packages today?