v1 · stableReact 18.2+ · Tailwind v4

Documentation

Everything from install to glow — wiring, tokens, the motion language, and the AI surfaces. Press Ctrl+K anywhere to jump.

Overview

Introduction

Lumora UI is a premium, motion-first React component library — interfaces that glow. Dark-first surfaces, one champagne accent we call the lumen, and three named springs that make every screen move as one body.

The philosophy is quiet by default and expensive in the details: generous whitespace, borders you barely notice, and at most one glowing element per viewport. Components style themselves exclusively through CSS variables and ship accessibility — keyboard paths, focus rings, reduced-motion fallbacks — as table stakes, not opt-ins.

  • Own the source

    Install components as .tsx — no black box, no runtime dependency.

  • Token-driven

    One --lm-* variable sheet, two themes, zero hardcoded hex.

  • Motion as a system

    Three springs drive every interaction across the library.

  • AI-native

    Command menu, chat widget, and selection toolbar are first-class.

Setup

Installation

Start with the CLI. init wires Tailwind and the design tokens into your app; add copies component source straight into your repo — no styling fights, no version drift.

terminal
# scaffold tokens + Tailwind wiring (recommended first run)
npx lumora-ui@latest init

# then own the source, one component at a time
npx lumora-ui@latest add button animated-tooltip block/hero

init writes the token import into your global stylesheet and points a Tailwind @source at the installed components, so the classes inside them are always generated:

app/globals.css
@import "tailwindcss";
@import "@lumora/ui/styles.css";

/* let Tailwind see the classes inside installed components */
@source "./components/lumora";

Working inside a monorepo where Lumora lives as a workspace package? Skip the copy step and depend on it directly — the same components, imported from @lumora/ui:

terminal — workspace
# inside a monorepo where @lumora/ui is a workspace package
pnpm add @lumora/ui @lumora/icons motion

Requirements

React 18.2+, Tailwind CSS v4, and motion. The small utilities — clsx, tailwind-merge, class-variance-authority — come along quietly.

Foundations

Theming & tokens

Every component reads from a small set of CSS variables — never a hard-coded hex. Surfaces, strokes, type, radii, shadows, and one accent: the lumen, a champagne glow used at most once per viewport.

Background--lm-bg
Surface--lm-surface
Surface 2--lm-surface-2
Border--lm-border-strong
Foreground--lm-fg
Muted--lm-fg-muted
The lumen--lm-accent
The dusk--lm-accent-2
lumora.css (excerpt)
:root {
  --lm-bg: #09090b;          /* surfaces */
  --lm-surface: #101014;
  --lm-fg: #f3f1ec;          /* type */
  --lm-fg-muted: #8e8e96;
  --lm-accent: #dcc28a;      /* the lumen */
  --lm-glow: rgba(220, 194, 138, 0.35);
  --lm-radius: 10px;         /* squared-soft radii */
}

Dark is the resting state. Set data-theme="light" on any ancestor for the built-in light theme — the floating toggle on this site does exactly that — or override the variables to make the system fully yours.

theming
<!-- dark is the resting state; light is one attribute -->
<html data-theme="light">

/* or retheme the lumen to the dusk accent entirely */
:root {
  --lm-accent: #9a8bd0;
  --lm-glow: rgba(154, 139, 208, 0.35);
}

Foundations

Motion language

Three named springs drive the entire library, so every screen moves as one body. Micro-interactions stay under 450ms, entrances breathe exactly once, and ambient loops run slow and low-contrast. Tap a chip below to feel each one.

lib/motion.ts
import { springs } from "@lumora/ui/lib/motion";

// every component defaults to one of these three
springs.snap;  // stiffness 480 — hovers, presses, magnetic pulls
springs.drift; // stiffness 260 — tooltips, reveals, entrances
springs.glide; // stiffness 170 — layout shifts, shared elements

<motion.div transition={springs.drift} />

Usage

Component anatomy

Every component is individually addressable and exports a single <Name>Props interface. There are three honest ways to pull one in, and they share the same API surface:

import patterns
// 1 — deep imports: individually addressable, tree-shakable
import { Button } from "@lumora/ui/components/button";
import { Hero } from "@lumora/ui/blocks/hero";
import { CommandMenu } from "@lumora/ui/ai/command-menu";

// 2 — the barrel: same APIs, still tree-shaken
import { Button, Hero, CommandMenu } from "@lumora/ui";

// 3 — CLI source: components copied into your own repo
import { Button } from "@/components/lumora/button";

Prefer deep imports for the leanest cold starts; reach for the barrel when you’re importing a handful at once. Apps that ran lumora add import from their own components/lumora path — the CLI rewrites relative imports to your configured aliases, so nothing changes downstream.

Surfaces

AI components

The command menu, chat widget, and selection toolbar are first-class components. The chat widget streams whatever you give it: return a Promise<string> for a single reply or an AsyncIterable<string> to stream chunks into the assistant bubble as they arrive. Handlers stay backend-agnostic — wire them to any endpoint.

streaming onSend
import { ChatWidget } from "@lumora/ui/ai/chat-widget";

// return an AsyncIterable<string> and the widget streams it
async function* onSend(text: string) {
  const reply = await draftReply(text); // your model call
  for (const chunk of reply) {
    yield chunk;
  }
}

<ChatWidget onSend={onSend} title="Ask Lumora" />

Below, the same widget wired to a local async generator — no network, no model, just the streaming contract:

Open the launcher in the corner and ask about springs, theming, or installing. Replies stream from a local generator — try it.

Foundations

Accessibility

Accessibility is non-negotiable in Lumora — these behaviors ship in every component rather than as opt-ins:

  • Keyboard first: tabs arrow-key navigate, the command menu is fully drivable without a pointer, dialogs trap and restore focus, switches answer to Space and Enter.
  • Visible focus: every interactive element carries a focus-visible ring in the accent tone — including the ones inside blocks.
  • Reduced motion: ambient and looping animation stops entirely under prefers-reduced-motion; marquees become grids, beams disappear, and interaction feedback falls back to opacity.
  • Honest semantics: real buttons, role=switch, aria-expanded on disclosures, aria-live on streaming chat logs, and aria-hidden on every decorative layer.
  • Labels throughout: icons are decorative by default and gain role=img with a label the moment you name them.

Keep going

Next steps