Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Lume

A frosted-glass React design system — opinionated components for layout, forms, overlays, data, diagnostics, agentic UI, and Dataverse / Power Platform tooling.

npm version bundle size types license React 19


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. --accent and --accent-link re-colour the entire system. No fork.
  • A11y is non-negotiable. Every component ships with jest-axe assertions in its smoke tests. Overlays trap focus, return focus on close, and honour prefers-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 any in the public API, generics where they help.

Install

npm install @allandecastro/lume react react-dom lucide-react

Optional 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` mode

Use 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

Controls & identity Button · Pill · Switch · Spinner · StatusDot · Kbd · Avatar · Divider
Layout Stack · Inline · Grid · Splitter
Form composition Field · TextInput · TextArea · SearchInput · Checkbox · RadioGroup · Select · Combobox · MultiSelect · TagInput · NumberInput · Slider · DatePicker · OtpInput · Rating · ColorPicker · FormSection · FormRow · FormActions
Navigation Tabs · Segmented · Pagination · Breadcrumbs · Stepper
Surfaces & feedback Card · EmptyState · Banner · Callout · Toast · notify · Stat · ProgressBar · Skeleton · CodeBlock
Overlays Modal · ConfirmDialog · Sheet · Popover · Tooltip · HoverCard · CommandPalette · ContextMenu
Disclosure Accordion · Toolbar
Data DataTable (sortable / filterable / groupable / virtualizable) · TreeView
Diagnostics LogViewer (virtualized) · NetworkInspector · JsonViewer
Agentic UI ChatMessage · MessageList · Composer · ToolCallCard · StreamingText · TokenMeter · TraceTree
Power Platform / Dataverse EnvironmentSwitcher · PublisherPrefixInput · ColumnEditor · RecordPicker
Hooks useTheme · useClickAway · useEscapeKey · useLumeStrings
Utilities cx · toneClassMap · syntax highlighters (highlightXml, highlightSql, highlightJson, highlightCsharp, highlightJs, highlightTs, highlightPython)

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, not display: none) so keyboard navigation and screen-reader announcements come for free.
  • Reduced motion. Every infinite animation has an explicit animation: none override 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 --accent or 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 exportsimport { Button } from "@allandecastro/lume/Button" for tightest tree-shaking.
  • LogViewer virtualizes automatically via @tanstack/react-virtual — 50k entries scroll smoothly.
  • DataTable virtualizes opt-in via the virtualized prop (incompatible with grouping + pagination — virtualization is for flat row streams).
  • Bundle budgets enforced in CI — every component has a size-limit cap; 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