tomatophp/tomato-components
A Laravel package from TomatoPHP that provides reusable UI components for admin panels and web apps, including ready-to-use interface elements to help developers build consistent forms, tables, and layouts faster.
Confirm the stack first — this is the whole decision. tomato-components is a Blade component kit built for Splade rendering and TomatoPHP admin panels. If the app is Inertia, Livewire, Filament, or plain Blade with its own design system, the components are close to worthless to you: you'll burn a day stripping Splade-shaped markup out of them. Check composer.json for splade and/or tomato-admin before you install a single thing.
Pin the exact version.
composer require tomatophp/tomato-components:1.0.3
The release notes for v1.0.3 are a compare URL and nothing else. A floating ^1.0 means an undocumented patch can silently rewrite component markup inside your lockfile. Commit the lockfile.
Run the publish step, then look at what it actually did.
php artisan vendor:publish --provider="TomatoComponents\TomatoComponentsServiceProvider"
php artisan about
If no provider matches that name, open vendor/tomatophp/tomato-components/composer.json and look under extra.laravel.providers — auto-discovery may be the only registration path, or you may have to add it to bootstrap/providers.php by hand. Note which files landed in config/ and resources/views/vendor/; that determines your extension strategy later.
Your first hour belongs to vendor/tomatophp/tomato-components/, because there is no documentation.
composer.json — the real constraints on php, laravel/framework, and splade. Run composer why-not tomatophp/tomato-components 1.0.3 against your target versions. If it fails, stop; do not force it with --ignore-platform-reqs.src/ — service provider, config merge, any component classes or view composers.resources/views/components/ — this is the catalogue. There is no component index anywhere else. ls -R it and write the list down.resources/js, resources/css, dist/ — decide up front whether it ships compiled assets or expects you to build them through Vite.First use case: rebuild one screen you already have. Pick a settings page or a CRUD index you wrote in plain Blade — a known-good target. Rebuild it with the package's components on a throwaway branch and time both versions. That delta is the entire business case. Under ~20% and the kit costs more in ownership than it saves in keystrokes.
Write a rendering test on day one per component you adopt. There is no upstream test suite, so this is the cheapest insurance available:
it('renders the tomato alert', function () {
$this->blade('<x-tomato-alert type="info">ok</x-tomato-alert>')
->assertSee('ok');
});
Use the real component names and props you found in the catalogue — not the ones in this text.
Wrap every component you use. Never call a package component directly from a page.
{{-- resources/views/components/admin/card.blade.php --}}
<x-tomato-card {{ $attributes->merge(['class' => 'admin-card']) }}>
{{ $slot }}
</x-tomato-card>
One wrapper file per component gives you a single swap point per component when the package (or your patience) gives out, and it's where your design tokens live without forking upstream markup. It costs ten seconds per component and is the highest-leverage thing in this entire assessment.
Treat it as an acquirable scaffold, not a dependency. For a zero-star, single-maintainer, changelog-less package the realistic play is:
resources/views/components/vendor/ or app/View/Components/ under your own namespace on day one, keeping the MIT header. Do it before the first break, not after.Compose with slots; keep props thin. Kits of this vintage expose behaviour through a small handful of props and pass everything else through attributes. Prefer {{ $slot }} composition over hunting for a prop that probably doesn't exist — you can read the Blade file in ten seconds and the answer is almost always "wrap it."
Alias long or colliding names once, in a service provider.
// AppServiceProvider::boot()
Blade::component('tomato-card', 'admin-card');
Blade::component('tomato-alert', 'admin-alert');
This is the cheapest immunization against Splade name collisions (see Gotchas) and future-proofs you against a package rename.
Contain it to internal surfaces. Admin panel only. Nothing customer-facing, nothing with an availability commitment. A silent visual regression on a settings page is an annoyance; on checkout it's an incident.
Publish config once, commit it, stop republishing. Publish → diff against the package default → commit only your changes with a comment explaining why. When you eventually upgrade, that comment is your migration note.
Import per-component assets where the package allows it. If individual JS/CSS entry points exist, wire only what you use into vite.config.js. Whole-catalogue imports re-bundle Tailwind and Vue that Splade already ships and add real weight to every admin page load.
Own your upgrade notes, because nobody upstream will. Record in the repo: installed version, install date, tag commit SHA, the component inventory, and the name of the owner. Keep the diff recipe handy:
composer require tomatophp/tomato-components:1.0.4 --dry-run
diff -ru vendor/tomatophp/tomato-components@{1.0.3,1.0.4}/resources/views/components
Classify the diff: dependency constraint change? Blade slot fix? cosmetic? Each carries a different compatibility implication, and only the first is likely to break the build loudly.
Tailwind silently drops the package's classes. This is the number one failure mode with vendored Blade libraries. Your content globs don't scan vendor/, so utility classes written inside the package never reach the compiled CSS and the component renders as an unstyled div:
// tailwind.config.js
content: [
'./resources/**/*.blade.php',
'./vendor/tomatophp/tomato-components/resources/views/**/*.blade.php',
],
Corollary: if the package targets a different Tailwind major, or uses a prefix/safelist config you don't share, the classes exist but resolve to nothing. Verify visually, never by grepping the rendered DOM.
Component name collisions with Splade are silent, not loud. Splade ships its own card, modal, alert. If the package reuses a name, load order decides the winner and the failure is confusing — props are quietly ignored rather than throwing. Aliasing everything through x-admin-* makes the ambiguity disappear.
vendor:publish --force destroys your customisations. Once you've edited a published view, --force is destructive. Either don't edit published views (override via wrapper components instead) or drop --force from muscle memory and diff first.
Stale caches masquerade as bugs. Anonymous component resolution and compiled views cache aggressively:
php artisan view:clear && php artisan optimize:clear && composer dump-autoload
If it renders under php artisan serve but not after deploy, it's almost always compiled views or a missing view:cache step in CI — not the package.
A Vite build step you didn't budget for. If the package ships only source assets, add its entry points to vite.config.js and make sure CI builds them. If it ships dist/, confirm the published assets survive deploy — the classic failure is public/vendor/... being gitignored and never copied by the release script. Symptom: unstyled components in staging, perfect locally.
"Patch" is a claim, not a guarantee. v1.0.2 → v1.0.3 shipped with release notes consisting of a compare URL. Never bump this package in a composer update sweep. Bump it deliberately, on a branch, with the diff read.
Compatibility is unverified, not verified-good. The declared constraint ranges at v1.0.3 are the only evidence that exists. If composer why-not fails against your Laravel version, --ignore-platform-reqs just defers the failure to runtime, in an admin screen, at the worst possible moment.
Silent visual regression after a Laravel or Splade upgrade. There is no upstream test suite to catch markup drift. Cheap mitigation: three to five browser tests (Playwright screenshots or Pest browser) over the screens using these components, running in CI. That's the only thing that will tell you a Blade slot changed shape.
Extension points, in order of preference.
app/ — last resort, and only for load-bearing components.For an MIT package with one maintainer, most teams should jump from (1) straight to (4) and skip (3) entirely.
Name an owner before the first commit lands. The likeliest failure here isn't a breaking change — it's that nobody notices it broke until three admin screens look wrong and nobody remembers installing it. Put the owner's name in the README next to the pinned version, the component inventory, and the fork point. That paragraph is the difference between a cheap speedup and a quiet liability.
How can I help you explore Laravel packages today?