davidgut/boson

Minimal, well-designed, flexible Laravel Blade components.

Maintainers

Package info

github.com/davidgut/bosonui

Language:CSS

pkg:composer/davidgut/boson

Statistics

Installs: 35

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.2.1 2026-04-02 00:45 UTC

README

Minimal, well-designed, flexible Laravel Blade components. Turbo-ready out of the box.

Requirements

  • PHP 8.2+
  • Laravel 12.0+

Installation

composer require davidgut/boson

The package auto-registers its service provider via Laravel's package discovery.

Include CSS & JS

Import the Boson stylesheet and script in your application's entry points:

/* resources/css/app.css */
@import '../../vendor/davidgut/boson/resources/css/boson.css';
// resources/js/app.js
import '../../vendor/davidgut/boson/resources/js/boson.js';

Then compile your assets as usual with Vite (or your bundler of choice).

Usage

All components are available under the boson:: namespace:

<x-boson::button>Click me</x-boson::button>

<x-boson::card>Content goes here.</x-boson::card>

<x-boson::input label="Email" type="email" name="email" />

Available Components

Accordion, Avatar, Badge, Button, Card, Checkbox, Combobox, Description, Dropdown, Error, Field, Form, Heading, Icon, Img, Input, Label, Link, Listbox, Modal, Navbar, Radio, Select, Separator, Spacer, Table, Tabs, Textarea, Toast.

Toasts

Add <x-boson::toast /> once in your layout. Then flash toasts from PHP or trigger them from JavaScript.

PHP. Flash via the Toast helper in controllers or middleware:

use DavidGut\Boson\Toast;

Toast::show('Something happened.');
Toast::success('Saved successfully!');
Toast::warning('Check your input.', 'Heads up');
Toast::danger('Something went wrong.');

All methods accept an optional $heading and $duration (in ms, default 5000).

JavaScript. Trigger toasts client-side via the global $toast helper:

$toast.show('Something happened.');
$toast.success('Saved!');
$toast.warning({ heading: 'Heads up', text: 'Check your input.' });
$toast.danger({ heading: 'Error', text: 'Something went wrong.', duration: 8000 });

You can also dismiss a toast programmatically:

const toast = $toast.success('Done!');
$toast.dismiss(toast);

Fetch Forms

Add fetch to <x-boson::form> for JavaScript-powered submission. Response handling is automatic:

Controller returns Boson does
redirect('/dashboard') Navigates normally
response()->json(['data' => $model]) Updates all [data-field] elements in-place, resets the form, closes the parent modal
422 validation response Populates matching <x-boson::error> components
<x-boson::form fetch action="/users" method="POST">

Without fetch, the form submits normally (Turbo handles it if installed, browser handles it otherwise).

GET forms send form data as URL query parameters automatically.

In-page updates support dot-notation for nested data:

return response()->json([
    'data' => ['team' => ['name' => 'Acme']]
]);
<span data-field="team.name">Old Name</span>  {{-- updates to "Acme" --}}

Events

Boson provides a declarative event system via on: attributes. Write inline expressions directly in Blade:

<form on:success="$toast.success('Saved!')">

<button on:click="console.log('clicked')">Click me</button>

<form on:success="$('#badge').text($data.status).data('color', $match($data.status, { active: 'green', pending: 'gray' }))">

Inside a handler, these helpers are available:

$event is the raw DOM event:

<button on:click="console.log($event.target.tagName)">Inspect</button>

$data is shorthand for $event.detail.data (the response payload on form success):

<form on:success="console.log($data.id, $data.name)">

$(selector) is a chainable DOM helper for updating page elements without a reload:

{{-- Update text content --}}
<form on:success="$('#user-name').text($data.name)">

{{-- Set a data attribute (e.g. for CSS-driven badge colors) --}}
<form on:success="$('#status').text($data.status).data('color', 'green')">

{{-- Toggle visibility --}}
<button on:click="$('#panel').toggle()">

Available methods: .text(value), .class(name, force?), .data(key, value), .attr(key, value), .toggle().

$match(value, map, fallback?) is a value lookup, like PHP's match:

<form on:success="$('#badge').data('color', $match($data.status, { active: 'green', pending: 'yellow', archived: 'gray' }, 'red'))">

$toast triggers toast notifications:

<form on:success="$toast.success('Saved!')">
<button on:click="$toast.danger({ heading: 'Error', text: 'Something went wrong.' })">

this refers to the element that owns the on: attribute:

<button on:click="this.textContent = 'Clicked!'">Click me</button>

The system supports both native DOM events (click, submit, keydown, etc.) and custom Boson events (success, error, open, close, change, select, deselect).

Register custom events at runtime:

import { $events } from '../../vendor/davidgut/boson/resources/js/boson.js';

$events.register('myevent');        // custom (dispatched as boson:myevent)
$events.register('scroll', true);   // native

Component Data Attributes

All interactive Boson components follow a consistent data-controller / data-{name}-target convention:

<!-- Root element identifies the component -->
<div data-controller="dropdown">
    <!-- Children declare their role within the component -->
    <button data-dropdown-target="trigger">Toggle</button>
    <div data-dropdown-target="menu">...</div>
</div>

Access component instances programmatically via el.boson:

const el = document.querySelector('[data-controller="modal"]');
el.boson.open();
el.boson.close();
el.boson.destroy();  // removes all event listeners
Component Controller
Accordion accordion
Combobox combobox
Dropdown dropdown
Form form
Listbox listbox
Modal modal
Navlist navlist
Sidebar sidebar
Tabs tab
Toast toast

Turbo Compatibility

Turbo is entirely optional. Boson has no dependency on it and works perfectly without it. When Turbo Laravel is present, everything works seamlessly with zero configuration.

The internal lifecycle system automatically handles:

  • Turbo Drive: components re-initialize after every page navigation and clean up before Turbo caches the page
  • Turbo Frames: components inside frames initialize when the frame loads new content
  • Turbo Streams: dynamically inserted components are initialized automatically via MutationObserver

If your app doesn't use Turbo, nothing changes. Components initialize on DOMContentLoaded as usual.

Building custom components? Follow the same protocol to get Turbo compatibility for free:

import { lifecycle } from '../../vendor/davidgut/boson/resources/js/core/lifecycle.js';

class MyComponent {
    constructor(element) {
        this.element = element;
        this.abortController = new AbortController();
        // bind events with { signal: this.abortController.signal }
    }

    destroy() {
        this.abortController.abort();
    }
}

lifecycle.register('my-component', MyComponent);

Then in Blade:

<div data-controller="my-component">...</div>

Turbo Attributes

Link, Form, Button, Navbar Item, Navlist Item, and Breadcrumbs Item accept Turbo data attributes via the turbo: prop prefix. Each turbo:* prop maps to its data-turbo-* HTML attribute:

{{-- Target a Turbo Frame --}}
<x-boson::link href="/users" turbo:frame="main">Users</x-boson::link>

{{-- Replace history instead of push --}}
<x-boson::link href="/tab" turbo:action="replace">Tab</x-boson::link>

{{-- Preload link into cache --}}
<x-boson::link href="/dashboard" turbo:preload>Dashboard</x-boson::link>

{{-- Confirm dialog before form submission --}}
<x-boson::form action="/delete" method="DELETE" turbo:confirm="Are you sure?">
    <x-boson::button turbo:submits-with="Deleting...">Delete</x-boson::button>
</x-boson::form>

{{-- Accept Turbo Stream responses on a GET link --}}
<x-boson::link href="/notifications" turbo:stream>Notifications</x-boson::link>

To disable Turbo on any of these components, use :turbo="false":

<x-boson::link href="/legacy" :turbo="false">Legacy Page</x-boson::link>
<x-boson::form action="/upload" method="POST" :turbo="false">...</x-boson::form>
Prop HTML Output Description
:turbo="false" data-turbo="false" Disable Turbo on this element
turbo:frame data-turbo-frame Target a specific Turbo Frame
turbo:action data-turbo-action advance or replace history
turbo:preload data-turbo-preload Pre-fetch into cache
turbo:prefetch data-turbo-prefetch Control hover prefetching
turbo:stream data-turbo-stream Accept Turbo Stream responses
turbo:confirm data-turbo-confirm Show confirmation dialog
turbo:method data-turbo-method Override link request method
turbo:submits-with data-turbo-submits-with Text to show while submitting

AI Rules Generation

Boson can generate a compact .mdc context file containing all component documentation, props, and usage examples. Designed to be consumed by AI coding assistants.

php artisan boson:rules

This parses every Boson component's @description, @usage, and props, then writes a single boson.mdc file to your IDE's rules directory.

Options

Option Default Description
--ide= cursor Target IDE: cursor (writes to .cursor/rules/) or antigravity (writes to .agent/rules/)
--output= (auto) Custom output directory
--canary false Include a verification string to test context loading

The canary flag can also be enabled globally via the boson.rules.canary config option.

Publishing

Publish the config file:

php artisan vendor:publish --tag=boson-config

Publish the views for customization:

php artisan vendor:publish --tag=boson-views

License

MIT. See LICENSE for details.