Skip to content
Brok UI

Loading…

No results

JavaScript and NPM

Blade-only components add no JavaScript. Interactive components ship one behaviour module each — a plain ES module that registers an Alpine.js 3.x data component — copied into resources/js/ui and bundled by your own Vite build. There is no runtime package, no global script tag, and nothing loaded from a CDN.

The contract, in one table

Every item declares its JavaScript need in its manifest (api.javascript); ui:show <item> prints it and the docs show it as a badge.

Declared as Without JavaScript Examples
none The complete component works. button, card, badge, table
progressive enhancement Core content and native actions work; motion, positioning, or feedback can be absent. accordion, tooltip, toast
required Content stays readable and causes no browser error, but the custom interaction is unavailable. dialog, combobox, command palette

The full support matrix, browser window, and bundler requirements are on the Compatibility page.

Wiring

Alpine boots from your entrypoint

ui:install writes resources/js/ui/index.js and adds one import to resources/js/app.js. If Alpine is not installed yet, ui:install --alpine writes the three bootstrap lines for you.

resources/js/app.js JS
import Alpine from 'alpinejs';
import './ui/index.js';   // every installed behaviour module, in one place

window.Alpine = Alpine;
Alpine.start();

ui:add appends one import per behaviour module

Items that ship JavaScript declare it in files[] with type: "js". The CLI copies the file and appends its import to index.js; ui:remove prunes it again. Imports must land before Alpine.start(), which is why they are collected here rather than in your page templates.

resources/js/ui/index.js JS
import './dialog.js';
import './dropdown.js';
import './ai/prompt.js';

A behaviour module registers an Alpine data component

The Blade file carries x-data="uiDialog({ … })"; the module registers uiDialog on alpine:init. Config is passed from Blade with @js(...), so server state (open on load, initial value) reaches the browser without a second render.

resources/js/ui/dialog.js JS
// resources/js/ui/dialog.js — copied by `ui:add dialog`, yours to edit.
document.addEventListener('alpine:init', () => {
    window.Alpine.data('uiDialog', (config = {}) => ({
        open: config.open === true,

        show() {
            this.open = true;
            // remember the trigger, move focus into the panel, lock scroll…
        },

        hide() {
            this.open = false;
            // …restore focus to the trigger on close.
        },
    }));
});

Heavy dependencies load on demand

The few components that need a large library (WebGL effects use three, charts use apexcharts) import it dynamically inside init(). Vite splits it into its own chunk, so pages that never mount the component never download it. Keep that pattern when you add your own.

resources/js/ui/blob.js JS
// resources/js/ui/blob.js — a heavy dependency loads only when the component mounts.
async init() {
    let THREE;
    try {
        THREE = await import('three');   // split into its own chunk by Vite
    } catch (e) {
        return;                          // dependency missing: empty canvas, no error
    }

    const renderer = new THREE.WebGLRenderer({ canvas: this.$refs.canvas, alpha: true });
    // …
}

NPM dependencies

Items declare their npm requirements in dependencies.npm. The CLI prints the install command at the end of ui:add (and in --dry-run); it never edits package.json or runs npm for you.

terminal
php artisan ui:add blob --dry-run
npm install three
npm run build

Livewire and morphing

Behaviour modules survive Livewire re-renders and wire:navigate: state lives in Alpine, the root keeps its x-data, and overlays clean up their listeners on destroy(). Components that must not be morphed by a third-party library document a wire:ignore region. Details and the tested patterns are on the Livewire page.

Note

The docs site itself loads behaviour modules lazily (a DOM scan decides which to import before Alpine.start()). That is a documentation-site optimisation for 800+ items; an application with a handful of components should keep the simple static imports above.