Getting started
Lyra ships as two packages. @lyra-ds/styles carries the whole look — tokens and
.lyra-* classes, no build step. @lyra-ds/react is a thin wrapper over those classes.
You can use the CSS on its own; the React package is a convenience, not a requirement.
Install
Pick the stack you are building in. All three install @lyra-ds/styles, because the appearance
lives there and nowhere else.
npm i @lyra-ds/react @lyra-ds/stylesnpm i @lyra-ds/styles alpinejs @lyra-ds/alpineRegister the plugin before starting Alpine, once, at your app entry:
import Alpine from 'alpinejs';
import lyra from '@lyra-ds/alpine';
Alpine.plugin(lyra);
Alpine.start();composer require lyra-ds/blade
npm i @lyra-ds/styles alpinejs @lyra-ds/alpineLaravel discovers the service provider automatically, so components are ready with either syntax:
<lyra:button variant="primary">Save</lyra:button>The 29 interactive components emit x-data, so register the Alpine plugin exactly as in the
HTML + Alpine tab. Put @lyraThemeScript in the document <head>, before the stylesheets.
The fonts are peer dependencies in every stack, so you install them yourself and control how they load:
npm i @fontsource/plus-jakarta-sans @fontsource/jetbrains-monoImport the stylesheet once
One import, at your app's root. Everything else is classes.
// app entry (main.tsx / layout.tsx)
import '@lyra-ds/styles/styles.css';Import it once and only once. It is a plain stylesheet, so importing it in several modules just ships the same rules repeatedly.
Your first component
import { Button } from '@lyra-ds/react';
export function App() {
return <Button>Hello Lyra</Button>;
}Dark theme
Set data-theme="dark" on your <html> element. Every semantic token responds — there is
no per-component work and no theme provider to wire up.
<html data-theme="dark"></html>To follow the reader's system preference without a flash of the wrong theme, set the attribute before first paint, in a blocking inline script:
<script>
const stored = localStorage.getItem('theme');
const dark = stored ? stored === 'dark' : matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.dataset.theme = dark ? 'dark' : 'light';
</script>It has to be inline and blocking. Anything deferred runs after the first paint, which is exactly the flash you are trying to avoid.
Where to go next
Every component has its own page with live examples, a generated prop table and the plain HTML behind it. Start from the component index, or read Button for the shape every page follows.