@allandecastro/lume
v0.2.0
Published
Lume — frosted-glass React component library: layout, forms, overlays, agentic UI, diagnostics, and Dataverse / Power Platform tooling.
Maintainers
Readme
Lume
A frosted-glass React design system — opinionated components for layout, forms, overlays, data, diagnostics, agentic UI, and Dataverse / Power Platform tooling.
Why Lume
- 60+ components, one cohesive aesthetic. Frosted-glass, dense, dark-by-default-but-light-aware. Same vocabulary across every primitive.
- Themable with two CSS variables.
--accentand--accent-linkre-colour the entire system. No fork. - A11y is non-negotiable. Every component ships with
jest-axeassertions in its smoke tests. Overlays trap focus, return focus on close, and honourprefers-reduced-motion. - Native semantics where they exist. Checkbox / Radio / Switch wrap a real visually-hidden
<input>— keyboard, screen reader, form submission all just work. - CSS Cascade Layers. All styles ship in
@layer lume, so your stylesheet wins by default with zero specificity wars. - Tree-shakable by component. Each component has its own ESM subpath (
@allandecastro/lume/Button) so you only pay for what you import. - TypeScript-first. Strict types, no
anyin the public API, generics where they help.
Install
npm install @allandecastro/lume react react-dom lucide-reactOptional peers (only required if you import the corresponding component):
npm install @tanstack/react-table # required by DataTable
npm install @tanstack/react-virtual # required by LogViewer + DataTable's `virtualized` modeUse it
// main.tsx
import "@allandecastro/lume/styles"; // tokens + base + component CSS, one bundle
import { Button, LumeProvider } from "@allandecastro/lume";
export function App() {
return (
<LumeProvider>
<Button variant="primary">Save changes</Button>
</LumeProvider>
);
}For tightest tree-shaking, import per-component:
import { Button } from "@allandecastro/lume/Button";
import { Modal } from "@allandecastro/lume/Modal";A taste
import {
Button, Field, TextInput, Modal, Toaster, notify,
DataTable, Stepper, Composer, ChatMessage, MessageList,
} from "@allandecastro/lume";
// A primary action with a destructive twin
<Button variant="primary"><Send size={13} /> Publish</Button>
<Button variant="primary" danger loading>Deleting…</Button>
// A field with validation state
<Field label="Logical name" hint="Use the publisher prefix"
state="error" message="No spaces.">
<TextInput defaultValue="cr_industry code" />
</Field>
// A focus-trapped modal with semantic footer
<Modal open={open} onClose={() => setOpen(false)} title="Discard changes?"
footer={<>
<Button variant="ghost" onClick={cancel}>Cancel</Button>
<Button variant="primary" danger onClick={discard}>Discard</Button>
</>}>
<p>You have 4 unpublished changes.</p>
</Modal>
// A sortable, filterable, paginated table — TanStack under the hood,
// Lume styling on top
<DataTable
data={rows}
columns={[
{ id: "name", header: "Name", accessor: "name", sortable: true, filter: "text" },
{ id: "tier", header: "Tier", accessor: "tier", filter: "select" },
{ id: "spend", header: "Spend", accessor: "spend", align: "end" },
]}
globalSearch pagination={{ pageSize: 20 }}
/>
// A conversation surface for agentic UIs
<MessageList>
<ChatMessage role="user" author="You" time="2:14 PM">
Add a tier column to Account.
</ChatMessage>
<ChatMessage role="assistant" author="Lume" time="2:14 PM">
Proposing <code>cr_tier</code> · Choice (A / B / C). Apply?
</ChatMessage>
</MessageList>
<Composer value={draft} onChange={setDraft} onSend={send} />
// Global toasts — programmatic, no provider context needed
<Toaster />
notify.success("Published to dev");What's in the box
Theming
Two CSS variables drive the entire interactive palette:
:root {
--accent: hsl(355 80% 60%); /* primary — focus, selection, primary buttons */
--accent-link: hsl(280 70% 60%); /* secondary — links, brand rail, agent surfaces */
}Every other accent-derived token (--border-accent, --surface-active, --text-accent, ...) is computed from these via color-mix(...). Per-route override works too:
[data-app="finance"] { --accent: hsl(150 60% 50%); }
[data-app="ops"] { --accent: hsl(30 90% 55%); }Light / dark mode is data-theme="light" or "dark" on <html>:
document.documentElement.dataset.theme = "light";…or use the bundled hook:
import { useTheme } from "@allandecastro/lume";
const { theme, toggle } = useTheme();Localization
Default strings (Cancel / Yes / No matches / Search... etc.) flow through LumeProvider:
import { LumeProvider } from "@allandecastro/lume";
<LumeProvider strings={{
confirm: { cancel: "Annuler", confirm: "Confirmer" },
commandPalette: { placeholder: "Tapez une commande…", noMatches: "Aucun résultat" },
pagination: { previous: "Précédent", next: "Suivant" },
}}>
<App />
</LumeProvider>Without a provider, Lume uses DEFAULT_LUME_STRINGS (English).
Accessibility
- Focus management. Dialogs (
Modal,ConfirmDialog,Sheet,CommandPalette) trap Tab inside, capture focus on open, and return focus to the trigger on close. - Native input semantics. Visual checkboxes / radios / switches are backed by real
<input>elements (opacity: 0, notdisplay: none) so keyboard navigation and screen-reader announcements come for free. - Reduced motion. Every infinite animation has an explicit
animation: noneoverride under@media (prefers-reduced-motion: reduce). Overlay enter/exit transitions are zeroed too. - Color contrast. Default palettes meet WCAG AA in both themes. If you override
--accentor any text token, re-verify against your background. - Every smoke test asserts
axe(container)returns no violations. A few component-specific rules are scoped-skip with documentation; the rest pass clean.
Performance
- Per-component subpath exports —
import { Button } from "@allandecastro/lume/Button"for tightest tree-shaking. LogViewervirtualizes automatically via@tanstack/react-virtual— 50k entries scroll smoothly.DataTablevirtualizes opt-in via thevirtualizedprop (incompatible with grouping + pagination — virtualization is for flat row streams).- Bundle budgets enforced in CI — every component has a
size-limitcap; PRs that bloat are flagged.
SSR safety
Every document / window / navigator reference is inside a useEffect, an event handler, or guarded with typeof document === "undefined". Portaled overlays render null until hydration. Components render to a string on the server without throwing.
Browser support
Modern evergreen browsers — Chrome / Firefox / Safari / Edge latest. Uses color-mix() (Baseline 2023) and CSS @layer (Baseline 2022). IE is not supported.
TypeScript
The package ships its own .d.ts files (built via vite-plugin-dts) — no @types/ package needed. Requires TS 5+ for the strict subpath exports. React 19 types required (the lib uses React.ReactNode patterns that React 18 types accept but emit deprecation warnings on).
Contributing
Bug reports, feature ideas, and PRs welcome. See CONTRIBUTING.md for setup, conventions, and the new-component checklist.
License
MIT — see LICENSE. Free for commercial and personal use. Attribution appreciated but not required.
Built with React 19 · Vite · TanStack Table · lucide-react · @tanstack/react-virtual
