lyra-ds / blade
Blade components for the Lyra Design System.
Fund package maintenance!
Requires
- php: >=8.3
- illuminate/support: ^12.0|^13.0
- illuminate/view: ^12.0|^13.0
- mallardduck/blade-lucide-icons: ^2.0
Requires (Dev)
- laravel/boost: ^2.4
- laravel/pint: ^1.27
- livewire/livewire: ^4.0
- orchestra/testbench: ^10.6|^11.1
- pestphp/pest: ^3.8.4|^4.1.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v0.10.0
- v0.9.0
- v0.8.1
- v0.8.0
- v0.7.0
- v0.6.0
- v0.5.0
- v0.4.0
- v0.3.0
- v0.2.0
- v0.1.0
- dev-release-please--branches--main--components--lyra-ds/blade
- dev-feat/create-workspace-dialog
- dev-feat/calendar-view
- dev-feat/tag-toast-action-bar-actions
- dev-feat/file-manager-open-navigate
- dev-docs/migration-1-0
- dev-ci/browser-runtime
- dev-chore/v1-release-prep
- dev-feat/owned-root-x-data
- dev-fix/27-toast-live-regions
- dev-fix/40-file-upload-controlled
- dev-fix/dropdown-tooltip-a11y
- dev-fix/overlays-return-focus
- dev-fix/data-table-date-range-a11y
- dev-fix/nav-real-links
- dev-fix/26-tabs-alpine-v1
- dev-feat/25-otp-input
- dev-fix/shell-workspace-switcher
- dev-fix/container-max-icons
- dev-fix/188-brand-initial-aria-label
- dev-fix/188-brand-mark-default
- dev-fix/187-container-max-keyword
This package is auto-updated.
Last update: 2026-09-29 16:25:29 UTC
README
Thin Blade wrappers for the Lyra Design System in Laravel applications. Each component renders the same canonical .lyra-* classes as its React counterpart—all appearance lives in @lyra-ds/styles, never in this Composer package.
The package ships 42 static and 30 interactive anonymous Blade components under the lyra namespace. Laravel discovers its service provider automatically, so components are ready to use with the short syntax (<lyra:button>) or the equivalent namespaced syntax (<x-lyra::button>).
Quickstart
1. Install the Blade package
composer require lyra-ds/blade
2. Install the styles and fonts
npm i @lyra-ds/styles @fontsource/plus-jakarta-sans @fontsource/jetbrains-mono
Follow the @lyra-ds/styles font setup to load Plus Jakarta Sans and JetBrains Mono in your application.
3. Import the styles once
With Vite, import the package from your JavaScript entry point, usually resources/js/app.js:
import '@lyra-ds/styles';
Alternatively, import the CSS export from your application's stylesheet:
@import '@lyra-ds/styles/styles.css';
lyra-ds/blade ships no CSS. The styles package is the single source of appearance for both the Blade and React components.
4. Render your first components
<lyra:button variant="primary">Save</lyra:button> {{-- Equivalent namespaced syntax: --}} <x-lyra::button variant="primary">Save</x-lyra::button>
Component props and ordinary HTML attributes can be combined. For example, this input renders its label and validation message while passing name, type, and autocomplete to the underlying input:
<x-lyra::input name="email" type="email" label="Email address" error="Enter a valid email address." autocomplete="email" />
Components
| Category | Components |
|---|---|
| Actions | action-bar, button, icon-button |
| Brand and identity | avatar, brand, icon, person-cell |
| Feedback and status | alert, badge, empty-state, progress, segmented-ring, skeleton, spinner, stat, tag, toast, toast-stack |
| Layout and structure | card, container, footer, grid, page-header, separator, shell, stack |
| Navigation | app-sidebar, bottom-nav, breadcrumb, nav-link, navbar, pagination, sidebar-group, stepper, table-of-contents, tabs, workspace-switcher |
| Forms and selection | checkbox, checkbox-group, combobox, fieldset, form-row, input, radio, radio-group, segmented-control, select, switch, textarea |
| Dates and scheduling | calendar, calendar-view, date-picker, date-range-picker, recurrence-selector, slot-picker, time-input, time-picker, time-zone-picker, weekly-schedule-editor |
| Data and files | code-block, data-table, file-manager, file-upload, table |
| Overlays and disclosure | accordion, bottom-sheet, command-palette, cookie-banner, create-workspace-dialog, dialog, drawer, dropdown, popover, tooltip |
Use each name with either equivalent form—for example, button becomes <lyra:button> or <x-lyra::button>.
Component notes & limitations
- Dropdown disabled items (lyra-ds/lyra#289): Items marked with
'disabled' => truerender witharia-disabled="true". Under@lyra-ds/alpine1.1.0, arrow-key roving tabindex does not skip disabled items (they still receive focus). Activation is inert (click, Enter, and Space are swallowed before dispatchinglyra:selector navigating), and disabled links drop theirhref. Visual disabled styling is applied inline pending a dedicated rule in@lyra-ds/styles.
Documentation API artifact
docs/api.json describes every component in this package: its props, a curated usage snippet, the HTML that snippet actually renders, and the name of the Alpine factory that animates it. It is generated by php bin/generate-docs-api, committed to the repository, and attached to every GitHub release, so a consumer can fetch it with gh release download --repo lyra-ds/blade --pattern api.json.
The artifact is consumed by lyra-ds.dev, which uses it to render the Blade tab of the documentation. Treat its field names as a public contract: renaming one breaks the site, so open that change in the site repository as well.
Nothing in it is written by hand. The props come from each @props directive, the usage snippet from resources/docs-examples/<slug>.blade.php, the HTML from rendering that snippet, and values only from the class-emission fixtures—absent observation is an empty list, never a guess. A freshness test fails the suite whenever the committed artifact drifts from the sources, exactly as it does for the Boost guidelines.
{
"version": "1.0.0",
"components": [
{
"slug": "dropdown",
"usage": "<lyra:dropdown align=\"end\" :items=\"[…]\">…</lyra:dropdown>",
"html": "<span x-data=\"lyraDropdown({ … })\" class=\"lyra-dropdown\">…</span>",
"binding": "lyraDropdown",
"rootXData": "owned",
"props": [
{ "name": "align", "default": "'start'", "required": false, "values": ["end", "start"] }
]
}
]
}
components is sorted by slug, binding is null for static components, rootXData is owned for interactive Alpine components (passthrough otherwise), and default is null when the prop is required. Ids that the components derive from uniqid() are replaced by stable id1, id2… placeholders so the artifact is byte-identical across runs.
Class parity with React
Every component emits exactly the class strings emitted by the corresponding Lyra React component. Data-driven class-emission tests enforce that contract using the fixtures in tests/Fixtures/class-emission/, keeping Blade and React on the same styling surface.
Class parity is not the same as full coverage. One React component has no Blade equivalent:
CreateWorkspaceDialogis a composition, not a primitive: it isdialogplus fields this package already ships. Build it in your application rather than importing a fixed arrangement of them.
React's ThemeProvider and ToastProvider also have no matching tag, because a provider is not a Blade shape. Their behavior is here: the theme lives in @lyraThemeScript plus the Alpine $store.theme, and the toast queue lives in toast-stack.
Compatibility
lyra-ds/blade |
Laravel 12 | Laravel 13 | PHP 8.3 | PHP 8.4 | @lyra-ds/styles |
@lyra-ds/alpine |
alpinejs |
|---|---|---|---|---|---|---|---|
1.0.x |
^12.41.1 |
^13.24 |
Supported | Supported | ^1.1 |
^1.2 |
>=3.13 <4 |
0.10.x |
Supported | Supported | Supported | Supported | ^0.4.2 |
^0.4.0 |
>=3.13 <4 |
These are the versions the respective lyra-ds/blade releases were tested against.
Laravel 11 is not supported because it reached security end-of-life in March 2026.
Upgrading from 0.10.x? See the Migration Guide.
Versioning
lyra-ds/blade follows Semantic Versioning with independent SemVer from @lyra-ds/styles, @lyra-ds/react, and @lyra-ds/alpine. See VERSIONING.md for the complete versioning policy, declared public API surface, deprecation process, and migration guarantees.
Releasing
Conventional commits drive the changelog in the bot-maintained release PR. Merge that PR to cut a release; the resulting tag triggers Packagist through its GitHub webhook. Review the compatibility matrix above for every release. See VERSIONING.md for details on release coordination and breaking change requirements.
Interactivity
The Alpine-backed components are accordion, app-sidebar, bottom-sheet, calendar, calendar-view, code-block, combobox, command-palette, cookie-banner, create-workspace-dialog, data-table, date-picker, date-range-picker, dialog, drawer, dropdown, file-manager, file-upload, popover, recurrence-selector, segmented-control, sidebar-group, slot-picker, table-of-contents, tabs, time-input, time-picker, time-zone-picker, toast-stack, tooltip, weekly-schedule-editor, and workspace-switcher. They get their behavior from the @lyra-ds/alpine plugin. Alpine.js >=3.13 <4 is a consumer-installed peer and is never bundled. calendar-view and create-workspace-dialog require @lyra-ds/alpine 1.2.0 or later.
Static components continue to work without Alpine. Alpine-backed components are static-first: except for the data-driven regions described below, their structure and initial state are present in the served HTML and remain inert until Alpine starts.
Some repeated content is intentionally runtime-rendered because filtering, locale-aware generation, queue state, or user-added rows belong to the Alpine binding. calendar, calendar-view, combobox, command-palette, file-upload, time-picker, toast-stack, and weekly-schedule-editor stamp those grids, options, items, toasts, or rows through x-for; those repeated regions do not exist in the served DOM until Alpine boots.
FileUpload controlled lifecycle (breaking change)
file-upload follows the controlled upload lifecycle of @lyra-ds/alpine 1.x and @lyra-ds/styles 1.x (Alpine 0.6/1.0 contract). Your application owns items and the transport; the component never simulates progress or completion.
- Root gets a unique
id(yours, or a generatedlyra-upload-…) and the dropzone is a<label>for a sibling<input type="file">. - Selection is proposed through the bubbling
lyra:file-upload:selectevent. EchoproposedIteminto youritems, upload, and commituploading/success/error. Retry, cancel and remove are thelyra:file-upload:retry|cancel|removeevents. Forward them withx-on:on the component and bindx-modelto your items array. - Statuses:
selected,uploading,canceling,success,canceled,error;progressis{kind: 'indeterminate'}or{kind: 'determinate', value}. - New props:
id,name,disabled,required,items,messages(Alpine message overrides with{name},{percent},{accept},{maxSizeMB}),statusLabels,cancelLabel,retryLabel. - Removed props:
defaultItems(useitems),uploadDuration,doneLabel; statusdone(usesuccess), numericitem.progress,remove(id), and.lyra-upload__bar-fill/.lyra-upload__check.removeLabelis replaced bymessages.remove.
data-table keeps sorting server-side by default: its header controls emit lyra:sort, and the application returns the rows in the requested order. Set clientSort to opt into in-browser sorting; sortable cells then provide their comparison value through data-sort-value.
After installing @lyra-ds/styles as described in the Quickstart, install Alpine.js and the Lyra plugin:
npm install alpinejs @lyra-ds/alpine
Then register the plugin before starting Alpine in resources/js/app.js:
import Alpine from 'alpinejs'; import lyra from '@lyra-ds/alpine'; Alpine.plugin(lyra); Alpine.start();
Theme
Place @lyraThemeScript in the document <head>, before stylesheets that use theme tokens. It emits a blocking inline script that applies the stored Lyra theme before the first paint; the Alpine theme store takes over after it starts.
<!DOCTYPE html> <html lang="en"> <head> @lyraThemeScript @vite(['resources/css/app.css', 'resources/js/app.js']) </head> <body> {{ $slot }} </body> </html>
The optional storage key is declared in one place—the directive argument—and the Alpine store reads it from the <html data-lyra-theme-key> attribute written by the script. Without an argument, the key defaults to lyra-theme; alternatively, declare data-lyra-theme-key on the <html> element in your layout and keep the argumentless directive.
@lyraThemeScript(config('app.theme_key'))
Toggle between the resolved light and dark themes through the Alpine store:
<button type="button" x-on:click="$store.theme.toggle()"> Toggle theme </button>
Add this required rule to your application's CSS:
[x-cloak] { display: none !important; }
The styles package does not ship this rule. Without it, closed menus and dialogs can flash before Alpine boots.
Livewire is a first-class integration. Twenty-five components expose controllable state through x-modelable:
open:bottom-sheet,command-palette(overlay mode),create-workspace-dialog,dialog,drawer,dropdown,popover, andworkspace-switcherselected:calendar,date-picker,date-range-picker,time-input, andtime-pickervalue:combobox,recurrence-selector, andsegmented-control- Component-specific state:
accordion(openItems),app-sidebar(collapsed),data-table(selected, orsortingviax-modelable),file-manager(view, withquerymodelable on its search field),file-upload(items),slot-picker(date, ortimezoneviax-modelable),table-of-contents(activeId),tabs(active), andweekly-schedule-editor(value, orexceptionsviax-modelable)
Use wire:model or x-model directly on a component tag for its root modelable state. Where alternatives are listed, select the target with the x-modelable attribute.
Requirements
- PHP 8.3 or later
- Laravel 12 or 13
Contributing
See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
License
This package is open-sourced software licensed under the MIT License.