Install a target package
Add Astro, React, or Elements; every target shares the same stylesheet package.
Semantic markup, one token-driven stylesheet, and a small progressive-enhancement runtime. Pick a framework below; the markup and class contract stay the same everywhere.
Glass system
Built from Lumen primitives@santi020k/lumen-astro@santi020k/lumen-react@santi020k/lumen-elementsAdd Astro, React, or Elements; every target shares the same stylesheet package.
Import the standalone stylesheet from a global CSS file, root layout, or app entry.
Markup, tokens, glass surfaces, and runtime behavior stay identical across targets.
Start here
pnpm
pnpm add @santi020k/lumen-astropnpm add @santi020k/lumen-astronpm
npm install @santi020k/lumen-astronpm install @santi020k/lumen-astroyarn
yarn add @santi020k/lumen-astroyarn add @santi020k/lumen-astroWith the stylesheet loaded globally, import components where you use them. Mount UIPrimitives once in your root layout only if you use interactive primitives; do not add it beside every component.
---
import { Button, Card, Input } from '@santi020k/lumen-astro'
---
<Card>
<label for="email">Email</label>
<Input id="email" type="email" placeholder="you@example.com" />
<Button>Subscribe</Button>
</Card>---
import { Button, Card, Input } from '@santi020k/lumen-astro'
---
<Card>
<label for="email">Email</label>
<Input id="email" type="email" placeholder="you@example.com" />
<Button>Subscribe</Button>
</Card>pnpm
pnpm add @santi020k/lumen-reactpnpm add @santi020k/lumen-reactnpm
npm install @santi020k/lumen-reactnpm install @santi020k/lumen-reactyarn
yarn add @santi020k/lumen-reactyarn add @santi020k/lumen-reactInstall the React package, load its stylesheet once, then import primitives directly. React ships the same styled markup and data contract, including data-display, overlay, form, rich text, and schedule attributes; use hooks for behavior-heavy primitives.
import { Button, Card, Input } from '@santi020k/lumen-react'
export function SubscribeForm() {
return (
<Card>
<label htmlFor="email">Email</label>
<Input id="email" type="email" placeholder="you@example.com" />
<Button>Subscribe</Button>
</Card>
)
}import { Button, Card, Input } from '@santi020k/lumen-react'
export function SubscribeForm() {
return (
<Card>
<label htmlFor="email">Email</label>
<Input id="email" type="email" placeholder="you@example.com" />
<Button>Subscribe</Button>
</Card>
)
}pnpm
pnpm add @santi020k/lumen-elementspnpm add @santi020k/lumen-elementsnpm
npm install @santi020k/lumen-elementsnpm install @santi020k/lumen-elementsyarn
yarn add @santi020k/lumen-elementsyarn add @santi020k/lumen-elementsInstall the Elements package, load its stylesheet once, register Lumen elements, and use lumen-* tags anywhere HTML is valid. Behavior-backed elements include overlays, context menus, select, forms, rich text, schedule, toast, DataTable, ThemeBuilder, and VirtualList.
<script type="module">
import { defineLumenElements } from '@santi020k/lumen-elements/define'
defineLumenElements()
</script>
<lumen-card>
<label for="email">Email</label>
<lumen-input id="email" type="email" placeholder="you@example.com"></lumen-input>
<lumen-button>Subscribe</lumen-button>
</lumen-card><script type="module">
import { defineLumenElements } from '@santi020k/lumen-elements/define'
defineLumenElements()
</script>
<lumen-card>
<label for="email">Email</label>
<lumen-input id="email" type="email" placeholder="you@example.com"></lumen-input>
<lumen-button>Subscribe</lumen-button>
</lumen-card>Framework contracts
Lumen keeps one visual contract, but each target has its own integration surface for behavior, local wrappers, and registration.
Shared stylesheet
Lumen ships standalone CSS. Load it once from a global CSS file, root layout, or app entry; Tailwind configuration is optional.
Use the stylesheet exported by your framework package in your main CSS entry.
@import "tailwindcss";
@import "@santi020k/lumen-astro/styles.css";@import "tailwindcss";
@import "@santi020k/lumen-astro/styles.css";Use this in an Astro root layout when you do not already have a shared CSS entry.
---
import '@santi020k/lumen-astro/styles.css'
---
<html lang="en">
<body>
<slot />
</body>
</html>---
import '@santi020k/lumen-astro/styles.css'
---
<html lang="en">
<body>
<slot />
</body>
</html>Use this in a bundled app entry, such as main.tsx, main.ts, or client.ts.
import '@santi020k/lumen-react/styles.css'
import { createRoot } from 'react-dom/client'
import { App } from './App'import '@santi020k/lumen-react/styles.css'
import { createRoot } from 'react-dom/client'
import { App } from './App'Token contract
Color tokens live on the root data-theme attribute. Light and dark ship built in, and any brand can be expressed through the same token contract.
Omit data-theme or set data-theme="light" for the default light theme; set data-theme="dark" for the built-in dark theme.
<html data-theme="light">
<body>...</body>
</html>
<html data-theme="dark">
<body>...</body>
</html><html data-theme="light">
<body>...</body>
</html>
<html data-theme="dark">
<body>...</body>
</html>Switch themes by updating document.documentElement.dataset.theme; components read the CSS variables from the root element.
const nextTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
document.documentElement.dataset.theme = nextThemeconst nextTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
document.documentElement.dataset.theme = nextThemeThese docs run santi020k-light and santi020k-dark, a custom theme family built on the same token contract. Copy the shape and adapt the values to your brand.
@import "@santi020k/lumen-astro/styles.css";
:root[data-theme="santi020k-light"] {
color-scheme: light;
--canvas: 268 20% 98%;
--surface: 268 20% 100%;
--ink: 268 10% 20%;
--brand: 264 92% 47%;
--accent: 264 95% 57%;
/* ...override the remaining tokens */
}
:root[data-theme="santi020k-dark"] {
color-scheme: dark;
--canvas: 260 43% 8%;
--surface: 260 30% 12%;
--ink: 260 10% 88%;
--brand: 264 90% 58%;
--accent: 264 90% 68%;
/* ...override the remaining tokens */
}@import "@santi020k/lumen-astro/styles.css";
:root[data-theme="santi020k-light"] {
color-scheme: light;
--canvas: 268 20% 98%;
--surface: 268 20% 100%;
--ink: 268 10% 20%;
--brand: 264 92% 47%;
--accent: 264 95% 57%;
/* ...override the remaining tokens */
}
:root[data-theme="santi020k-dark"] {
color-scheme: dark;
--canvas: 260 43% 8%;
--surface: 260 30% 12%;
--ink: 260 10% 88%;
--brand: 264 90% 58%;
--accent: 264 90% 68%;
/* ...override the remaining tokens */
}Surface system
Glass is a surface treatment, not a theme: shared tokens control blur, saturation, borders, and highlights, with fallbacks for browsers without backdrop-filter support.
Shared tokens control blur, saturation, borders, highlights, and shadow depth across light and dark themes.
Use the glass prop for standalone content surfaces.
<Card glass>
<h2>Launch window</h2>
<p>Shared glass tokens.</p>
</Card><Card glass>
<h2>Launch window</h2>
<p>Shared glass tokens.</p>
</Card>Use the same glass prop for overlays without changing their semantics.
<Popover glass>
<Button data-ui-trigger>
Invite
</Button>
<div>
<Input type="email" />
</div>
</Popover><Popover glass>
<Button data-ui-trigger>
Invite
</Button>
<div>
<Input type="email" />
</div>
</Popover>Elements support glass with a boolean attribute.
<lumen-card glass>
<h2>Production</h2>
<p>Same class contract.</p>
</lumen-card><lumen-card glass>
<h2>Production</h2>
<p>Same class contract.</p>
</lumen-card>Package map
@santi020k/lumen-astro
Full component catalog, shared CSS, and the UIPrimitives runtime.
@santi020k/lumen-react
React components for the full primitive catalog using the shared class and data contract.
@santi020k/lumen-elements
Standards-based custom elements for the full primitive catalog using the shared class and data contract.
@santi020k/lumen-core
Design tokens, component metadata, and small adapter utilities.
Behavior layer
Mount UIPrimitives once in your Astro root layout to wire every interactive primitive. Static components never need it, and component snippets assume the root layout already owns that runtime include.
Trigger, dismiss, escape-key, and focus behavior for modal surfaces.
Lightweight disclosure patterns for contextual actions and floating content.
Progressively enhanced keyboard navigation and client-side filtering.
Document-level behavior for transient feedback and slide navigation.
Every component page includes a live preview plus code for all three targets.