lemonsqueezy/plain-ui-components
Plain UI Components is a Laravel Blade component library from Lemon Squeezy for building clean, consistent interfaces fast. Drop reusable, customizable UI pieces into your views, with sensible defaults for forms, buttons, and admin screens. Easy to adapt to your own design system.
The package is a Blade component library, so treat onboarding as three steps: install, publish, and inventory.
composer require lemonsqueezy/plain-ui-components
php artisan vendor:publish --provider="LemonSqueezy\PlainUiComponents\PlainUiComponentsServiceProvider"
Run the publish command with --tag variants if the provider exposes several (typically plain-ui-components-config, -views, -assets). Publishing views is the single most important step — with 24 stars and zero dependents, the package's source is its documentation, and you will end up reading the Blade files more than any README.
Where to look first, in order:
vendor/lemonsqueezy/plain-ui-components/src/View/Components/ — this directory is the authoritative component list. Each class's render() tells you the view name and therefore the tag (Button → <x-plain-button> or <x-plain::button> depending on the namespace registered in the service provider).vendor/.../resources/views/components/ — the props are declared in @props([...]) at the top of each file. That array is your API reference.config/plain-ui-components.php after publishing — prefix, theme/token settings, and any asset registration live here.composer.json require block — confirm whether it expects Tailwind, Alpine, or ships standalone CSS. This determines your build setup more than anything else.The first real use case to target is a low-risk internal screen, not your marketing homepage: a settings page or an admin resource with a form, a table, and a couple of modals. That exercises inputs, validation state, buttons, and overlays — the four areas where a component library either fits your app or doesn't — and it fails loudly in a place where you can iterate.
Wrap, don't scatter. Create resources/views/components/ui/* wrappers that proxy to the package components and hard-code your conventions (size, colour, spacing, icon slot). Call sites then use <x-ui.button> and you retain a one-file escape hatch if the package changes or you swap it out. Given the project's maturity, this layer is cheap insurance.
Form binding. If inputs expose name/id props, prefer binding them to a model or old() rather than passing raw values:
<x-plain-input name="email" :value="old('email', $user->email)" />
Check whether the component forwards $attributes to the underlying <input> — if it does, wire:model, x-model, and required all pass through for free. If it doesn't, wrap it and spread $attributes yourself rather than patching vendor files.
Livewire / Inertia. For Livewire, the package's value is entirely in whether $attributes reaches the root element (wire:model.live, wire:loading.attr). Test one input and one button before committing. For Inertia/Vue, use the components only in your Blade root layout and error pages — don't try to reimplement them in JS.
Theming via tokens. Look for CSS custom properties in the published stylesheet (--plain-* or similar). Redefine them in your app's CSS entry point rather than overriding individual component classes; class names are the least stable part of a small package.
Build a demo route. A local-only route rendering every component with every variant is worth the 30 minutes. With no dependents and no docs site, it's the only reliable way to see a breaking change after composer update.
Layout composition. Use a package layout (if one ships) as the base for auth screens and error pages, and your own layout for the app shell. Mixing two layout systems is where these libraries usually get painful.
'./vendor/lemonsqueezy/plain-ui-components/resources/views/**/*.blade.php' to tailwind.config.js content. Skipping this produces components that render correctly in local (where a full build exists) and lose all styling in production — the classic version of this bug.composer update, diff vendor/ against your published copy."lemonsqueezy/plain-ui-components": "~1.0.0" (or an exact constraint) plus a read of the changelog beats a surprise refactor.php artisan view:clear when newly published or newly registered components return "Unable to locate a class or view". Anonymous components are resolved at compile time from the component namespace, so a stale cache is the usual culprit.$attributes->merge([...]) in each component. If the component hard-merges a class after your class attribute, your overrides lose; pass conditional classes through a dedicated prop instead of fighting specificity.x-data, confirm Alpine is loaded once and that you're not double-registering plugins (@livewireScripts + a manual CDN tag is a common conflict).dd($component->data()) inside a wrapper, or add {{ $slot }} temporarily to the published view. Blade swallows slot errors quietly.docs/ui-components.md in your repo listing which components you actually use and where — it's the cheapest hedge against the package being abandoned.How can I help you explore Laravel packages today?