Laravel Blade component library built on Web Awesome and Alpine.js. Provides form controls, overlays, file uploads, and a shared design token layer for Laravel applications.
- PHP 8.3+
- Laravel 13+
- Node.js 20+ with npm
- Vite (via
laravel-vite-plugin)
Add the package to your Laravel project. For local development, use a path repository:
composer require codeartlv/klementine-uiThe service provider is auto-discovered via Laravel package discovery.
php artisan vendor:publish --tag=klementine-configThis creates config/klementine.php:
return [
'component_prefix' => 'ui', // Blade tags become <x-ui-button>, <x-ui-input>, etc.
];If the config file is not published, the prefix defaults to ui.
After composer require, install the package's npm dependencies:
php artisan klementine-ui:installThis runs npm install in the package directory (works for path repos and vendor/codeartlv/klementine-ui). Node.js must be available on the machine.
The install command does not run automatically when the package is required. To run it after every composer install or composer update, add hooks to your application's root composer.json:
{
"scripts": {
"post-install-cmd": ["@php artisan klementine-ui:install --ansi"],
"post-update-cmd": ["@php artisan klementine-ui:install --ansi"]
}
}If you already have post-install-cmd or post-update-cmd entries, append the artisan line to those arrays instead of replacing them:
{
"scripts": {
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
"@php artisan klementine-ui:install --ansi"
]
}
}You can also call it from a custom setup script:
{
"scripts": {
"setup": [
"composer install",
"@php artisan klementine-ui:install --ansi",
"npm install --ignore-scripts",
"npm run build"
]
}
}| Hook | Runs when |
|---|---|
post-install-cmd |
composer install |
post-update-cmd |
composer update, composer require, composer remove |
Add a path alias in vite.config.mjs so imports resolve to the package resources:
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@klementine-ui': path.resolve('vendor/codeartlv/klementine-ui/resources'),
},
},
});Create or update resources/js/app.js:
import KlementineUI from '@klementine-ui/js';
KlementineUI.configureAlpine((Alpine) => {
// Register custom Alpine components (optional)
}).configureWebAwesome((registerIconLibrary) => {
// Configure Web Awesome settings (optional)
});
KlementineUI.start();Include Vite assets in your layout:
@vite(['resources/js/app.js', 'resources/css/app.css'])Add a CSRF meta tag for AJAX forms and overlays:
<meta name="csrf-token" content="{{ csrf_token() }}">Import Klementine styles in your app CSS (the preview app uses the parent resources/css/app.css which should import or mirror the package layer structure). The package stylesheet entry is:
@import '@klementine-ui/css/app.css';It layers Web Awesome, design tokens (variables.css), form styles, and per-component CSS.
Web Awesome static assets must be available at /build/webawesome in production. Configure the base path in KlementineUI.setupWebAwesome() (handled automatically when using the package entry point).
When running locally, open the component preview at:
http://localhost/klementine-ui
(Requires the package dev routes — registered on the localhost domain.)
Each component demo lives in resources/views/preview/components/.
| Layer | Location | Purpose |
|---|---|---|
| Blade components | src/View/Components/ |
Server-rendered UI tags |
| Blade views | resources/views/ |
Component markup |
| Alpine components | resources/js/alpine-components/ |
Auto-registered as Alpine.data() |
| UI factories | resources/js/ui-components/ |
Programmatic DOM builders |
| Overlay loader | resources/js/lib/overlay-loader.js |
Shared dialog/drawer AJAX logic |
Alpine components are registered automatically from filenames:
| File | Alpine name | Usage |
|---|---|---|
dialog.js |
dialog |
x-data="dialog(url, caption, id)" |
drawer.js |
drawer |
x-data="drawer(url, caption, id)" |
form.js |
form |
x-data="form" on <x-ui-form> |
fileUploader.js |
fileUploader |
Used by <x-ui-uploader> |
passwordField.js |
passwordField |
Used by <x-ui-password> |
datepicker.js |
datepicker |
Used by <x-ui-datepicker> |
All examples use the default ui prefix: <x-ui-{name}>. Replace ui if you changed component_prefix in config.
<x-ui-avatar image="https://example.com/photo.jpg" loading="lazy" />
<x-ui-avatar initials="WA" />Additional Web Awesome attributes (shape, size, etc.) can be passed through {{ $attributes }}.
<x-ui-badge label="New" variant="primary" />
<x-ui-badge label="Draft" variant="neutral" appearance="outlined" pill />
<x-ui-badge label="3" variant="danger" attention="pulse" pill />| Prop / attribute | Description |
|---|---|
label |
Badge text |
variant |
primary, success, neutral, warning, danger |
appearance |
filled, outlined, filled-outlined |
pill |
Rounded pill shape |
attention |
e.g. pulse for animation |
<x-ui-button label="Save" variant="brand" appearance="filled" />
<x-ui-button type="submit" label="Submit" variant="brand" span />| Prop | Description |
|---|---|
label |
Button text |
type |
button, submit, reset |
span |
Full-width block button |
Pass Web Awesome attributes (variant, appearance, size, disabled, loading) as HTML attributes.
<x-ui-callout
title="Heads up"
message="Something needs your attention."
variant="warning"
/>| Prop | Description |
|---|---|
title |
Optional heading |
message |
Body text (supports HTML) |
variant |
brand, success, warning, neutral, danger |
size |
xs, s, m, l, xl |
<x-ui-checkbox name="agree" label="I agree" value="1" />
<x-ui-checkbox name="agree" label="With hint" hint="Required to continue" />
<x-ui-checkbox name="agree" label="Disabled" disabled /><x-ui-radio name="plan" label="Basic" value="basic" />
<x-ui-radio name="plan" label="Pro" value="pro" /><x-ui-input name="name" label="Name" hint="Your full name" />
<x-ui-input name="email" label="Email" type="email" required />| Prop | Description |
|---|---|
name |
Field name |
label |
Label text |
type |
Input type (default text) |
value |
Initial value |
hint |
Helper text below the field |
required |
Shows required indicator on label |
<x-ui-textarea name="bio" label="Bio" hint="Tell us about yourself" />@php
use Codeart\Klementine\View\Components\Select\Option;
use Codeart\Klementine\View\Components\Select\Group;
$options = [
new Option('1', 'Option 1'),
new Option('2', 'Option 2', selected: true),
];
@endphp
<x-ui-select name="country" label="Country" :options="$options" placeholder="Choose…" />
<x-ui-select name="tags" label="Tags" :options="$options" multiple />Options can also be Group instances for grouped <wa-option> lists.
<x-ui-datepicker name="date" label="Date" :value="$date" display-format="d.m.Y" />Uses Vanilla Calendar Pro with an Alpine datepicker component.
<x-ui-password
name="password"
label="Password"
policy="min:8,max:64,lowercase,uppercase,number,special"
/>Policy is a comma-separated rule string consumed by the passwordField Alpine component (live strength meter and rule checklist).
Wrap fields in a form that submits via AJAX and displays validation errors inline.
<x-ui-form method="post" action="{{ route('profile.update') }}">
<x-ui-form-group>
<x-ui-input name="name" label="Name" />
<x-ui-input name="email" label="Email" type="email" />
<x-ui-button type="submit" label="Save" variant="brand" span />
</x-ui-form-group>
</x-ui-form>Server response — return JSON using FormResponse:
use Codeart\Klementine\View\Components\Forms\FormError;
use Codeart\Klementine\View\Components\Forms\FormResponse;
use Codeart\Klementine\View\Components\Forms\FormSuccess;
$response = new FormResponse();
$response->addError(new FormError('Name is required', 'name'));
return response()->json($response);
// Or on success:
$response = new FormResponse();
$response->setSuccess(new FormSuccess('Saved successfully'));
$response->addSuccessAction('redirect', '/dashboard');
return response()->json($response);Declarative (Blade):
<x-ui-dialog label="Confirm" id="confirm-dialog">
<p>Are you sure?</p>
<div slot="footer">
<x-ui-button label="Close" variant="brand" data-dialog="close" />
</div>
</x-ui-dialog>
<button data-dialog="open confirm-dialog">Open</button>Alpine (load content on click):
<a href="#" x-data="dialog('{{ route('items.edit', $item) }}', 'Edit item')">
Edit
</a>JavaScript:
import Dialog from '@klementine-ui/js/ui-components/dialog.js';
const dialog = new Dialog({ id: 'edit-item', caption: 'Edit item' });
// Load HTML from URL
await dialog.open('/items/1/edit');
// Or wrap an existing element
await dialog.open(document.getElementById('my-panel'));
// Options
await dialog.open('/items/1/edit', { autoclose: true, animations: false });
dialog.on('close', () => {
/* … */
});
dialog.close();| Constructor param | Description |
|---|---|
id |
CSS class suffix (dialog--{id}) |
caption |
Dialog title (maps to label on <wa-dialog>) |
open() option |
Description |
|---|---|
autoclose |
Enables light-dismiss on overlay click |
animations |
Set false to disable show/hide animation |
Same API as dialog, backed by <wa-drawer>:
<a href="#" x-data="drawer('{{ route('menu') }}', 'Menu')">Open menu</a>import Drawer from '@klementine-ui/js/ui-components/drawer.js';
const drawer = new Drawer({ id: 'menu', caption: 'Menu' });
await drawer.open('/navigation', { placement: 'start', autoclose: true });Close buttons inside a drawer use data-drawer="close".
<x-ui-uploader
name="files"
data-uploadroute="{{ route('upload') }}"
data-deleteroute="{{ route('upload.delete') }}"
data-limit="5"
/>| Attribute | Description |
|---|---|
data-uploadroute |
POST endpoint for file chunks |
data-deleteroute |
DELETE endpoint for removing files |
data-croproute |
Optional crop endpoint |
data-limit |
Max number of files (default 1) |
data-submitbtn |
Selector for submit button to disable during upload |
Translations for placeholders and error messages come from lang/{locale}/components.php under the klementine-ui namespace.
<x-ui-icon name="upload" />
<x-ui-icon name="circle-xmark" size="l" />Layout wrapper for stacking form fields:
<x-ui-form-group>
<x-ui-input name="first" label="First name" />
<x-ui-input name="last" label="Last name" />
</x-ui-form-group>import KlementineUI from '@klementine-ui/js';
KlementineUI.configureAlpine((Alpine) => {
Alpine.data('myComponent', () => ({
/* … */
}));
})
.configureWebAwesome((registerIconLibrary) => {
// Register custom icon libraries
})
.start();start() loads all Alpine components from alpine-components/ and calls Alpine.start().
For dynamic DOM (used internally by forms and overlays):
import button from '@klementine-ui/js/ui-components/button.js';
import callout from '@klementine-ui/js/ui-components/callout.js';
import spinner from '@klementine-ui/js/ui-components/spinner.js';
document.body.appendChild(callout({ variant: 'danger', message: 'Error' }));Publish or extend translation files at lang/{locale}/components.php:
return [
'uploader' => [
'upload_placeholder' => 'Upload files',
'upload_max_filesize' => 'File exceeds the maximum size (:max MB)',
],
'password_field' => [
'password_hint' => [
'lowercase' => 'Lowercase letter',
// …
],
],
];Loaded under the klementine-ui::components.* namespace.
# In the package directory
npm run dev
# In the Laravel app
npm run dev
php artisan serveVisit http://localhost/klementine-ui for the live component preview.