marshmallow/components
Marshmallow Components is a Laravel package offering a collection of reusable, ready-to-use Blade UI components. It helps you build consistent interfaces faster with minimal markup and sensible defaults, keeping your views clean and maintainable.
Install the package alongside Marshmallow itself — it is a UI layer, not a standalone framework, so it is only useful once the parent admin package is running.
composer require marshmallow/components
php artisan vendor:publish --tag=marshmallow-components-config
php artisan vendor:publish --tag=marshmallow-components-views # only if you plan to override
Version note. As of v1.5.0 the package supports Laravel 13. If your app is on Laravel 13, require ^1.5 explicitly — earlier tags cap the framework constraint and Composer will either refuse the install or resolve you onto an older Laravel. On Laravel 10–12 the earlier tags remain fine.
Where to look first. Before writing any markup, spend ten minutes in three places in the repo:
src/View/Components/ — the class components and their constructor signatures. This is the real API; the README will lag behind it.resources/views/components/ — the anonymous Blade components and their @props defaults. This tells you which attributes are forwarded to the root element and which are consumed internally.config/marshmallow-components.php — the prefix, view namespace, and any default styling hooks.First use case. The fastest win is replacing hand-rolled form markup inside a Marshmallow screen with the package's input/field components, so validation state, error display, and label association are handled for you:
<x-marshmallow::field name="email" label="Email address">
<x-marshmallow::input type="email" name="email" wire:model.defer="email" />
</x-marshmallow::field>
If that renders and validation errors appear without extra wiring, the package integrates cleanly with your Marshmallow version. If not, check the component namespace against config('marshmallow-components.prefix') before debugging anything else.
Treat the components as layout primitives, not as a design system. The value is in the attribute forwarding and slot structure, so compose them inside your own domain components rather than scattering them across views:
{{-- resources/views/components/admin/stat-card.blade.php --}}
@props(['label', 'value', 'trend' => null])
<x-marshmallow::card {{ $attributes->class(['admin-stat']) }}>
<x-slot:header>{{ $label }}</x-slot:header>
<p class="text-2xl font-semibold">{{ $value }}</p>
@if ($trend)
<x-marshmallow::badge :variant="$trend > 0 ? 'success' : 'danger'">
{{ $trend > 0 ? '+' : '' }}{{ $trend }}%
</x-marshmallow::badge>
@endif
</x-marshmallow::card>
Wrap, don't fork. For any component whose markup you dislike, create a project-local component that renders the package component in its slot and applies your own classes via $attributes->class([...]). You keep upstream fixes and still get your look. Only publish views for components where the structure is wrong, and publish individual files rather than the whole directory — a full publish freezes you out of upstream template changes.
Extend the class components when you need new behavior. If src/View/Components/ contains class-based components (inputs, tables, modals), subclassing is cheaper than reimplementing:
namespace App\View\Components;
use Marshmallow\Components\View\Components\Input;
class MoneyInput extends Input
{
public function render()
{
return view('components.money-input');
}
}
Confirm the parent's properties are protected/public and not private, otherwise subclassing buys you nothing.
Livewire-aware usage. Marshmallow screens are typically Livewire-driven, so keep bindings on the component's forwarded attributes and avoid wrapping them in extra DOM that breaks morphing:
<x-marshmallow::select name="status" wire:model.live="status" :options="$statuses" />
Composition over configuration. Reach for the config file only for cross-cutting things (namespace prefix, default icon set, theme class). Per-instance styling belongs in attributes at the call site, where a reviewer can see it.
Testing pattern. Blade components are trivially snapshot-testable; assert rendered output rather than mocking:
$this->blade('<x-marshmallow::badge variant="success">OK</x-marshmallow::badge>')
->assertSee('OK')
->assertSee('badge--success', false);
That gives you a cheap upgrade-safety net, which matters more than usual here. Run it after every composer update, since the release cadence is driven by framework-compatibility bumps rather than a documented changelog of behavior.
composer.json in every tag before bumping, and run the full suite on the framework version you actually deploy — CI on the newest Laravel proves nothing about your Laravel 12 app.$attributes->merge(['class' => 'default']) places defaults after caller classes in the resulting string, so your defaults can win in specificity-sensitive utilities. Prefer $attributes->class(['default' => $condition]) or $attributes->twMerge(...) if tailwind-merge is available in the parent stack.@props defaults silently swallow attributes. If a prop isn't declared and isn't forwarded, the attribute disappears with no error. When an attribute "doesn't work," check the component's @props list before assuming a bug.loadViewsFrom. If your app also publishes to resources/views/vendor/..., a stale published copy shadows upstream updates forever. Keep a note of every published file; git diff against the vendor path after each upgrade.php artisan view:clear && php artisan config:clear. A missing-component InvalidArgumentException after a deploy is almost always a stale compiled view rather than a broken install.wire:ignore or move the state out of the server component rather than patching the package views."marshmallow/components": "^1.5"), commit your lockfile, and read the diff on every upgrade — there is no ecosystem to catch a regression for you. Vendor the two or three components you actually use if the API surface you depend on is small; the package is young enough that a breaking change is plausible.php artisan about to confirm the provider is booted and which Laravel/PHP versions resolved, 2) Blade::componentNamespace()/prefix in the config, 3) view:clear, 4) read the component source. Skipping straight to step 4 with a cached view wastes the most time — and a version-constraint conflict masquerades as a "component not found" more often than you'd expect.prefix (lets you avoid clashing with app components like <x-card>), Blade::componentNamespace() for aliasing, and published views for structural overrides. Avoid editing anything under vendor/ — the next composer update erases it silently.How can I help you explore Laravel packages today?