@iamtherealtim/lume-css
v2.1.0
Published
A minimal open-source CSS library for thoughtful interfaces.
Maintainers
Readme
Lume CSS
A minimal, open-source CSS library for thoughtful interfaces.
Lume ships a polished component set, design-token-based theming, dark mode, utility helpers, and practical docs/demo apps you can use as implementation references.
Live Links
- Docs: https://iamtherealtim.github.io/lume-css/lume-docs.html
- Admin demo: https://iamtherealtim.github.io/lume-css/admin-demo/dashboard.html
What’s Included
| File | What it is |
| --- | --- |
| dist/lume.css, dist/lume.min.css | The whole library in one stylesheet |
| dist/reset.css | Global box-sizing reset and bare-element styles (body, a, code, pre, blockquote, kbd) |
| dist/core.css | Design tokens, dark theme, typography and layout classes |
| dist/components.css | All .lume-* components |
| dist/utilities.css, dist/a11y.css, dist/responsive.css | Utility classes, focus and reduced-motion layer, breakpoints |
| dist/extras.css | Opt-in decorative variants (gradient buttons). Not part of lume.css |
| dist/lume.js | Optional runtime for modals, drawers, dropdowns, tabs, toasts and dismissible tags |
The docs, showcase and admin demo live in docs/ and are published at the live links above.
Quick Start
CDN
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@iamtherealtim/[email protected]/dist/lume.min.css" />
<script defer src="https://cdn.jsdelivr.net/npm/@iamtherealtim/[email protected]/dist/lume.js"></script>Pin a version in production. Drop @2.1.0 to always get the latest release.
npm
npm install @iamtherealtim/lume-css@import "@iamtherealtim/lume-css/lume.css";// Side-effect import: runs the runtime and sets window.Lume
import "@iamtherealtim/lume-css/lume.js";Package Contract
These paths are the public API of the package. Anything else inside the package may change without notice.
| Import path | Resolves to |
| --- | --- |
| @iamtherealtim/lume-css | dist/lume.css under the style condition, dist/lume.js otherwise |
| @iamtherealtim/lume-css/lume.css | Full bundle |
| @iamtherealtim/lume-css/lume.min.css | Full bundle, minified |
| @iamtherealtim/lume-css/lume.js | Runtime |
| @iamtherealtim/lume-css/{reset,core,components,utilities,a11y,responsive,extras}.css | Individual layers |
lume.js is a classic script, not an ES module. It has no exports. Loading it, by <script> tag or by side-effect import, sets window.Lume. The package declares sideEffects so bundlers keep both the CSS and the runtime.
Use Only Parts Of The Library
lume.css is the same as importing every layer in this order:
@import "@iamtherealtim/lume-css/reset.css";
@import "@iamtherealtim/lume-css/core.css";
@import "@iamtherealtim/lume-css/components.css";
@import "@iamtherealtim/lume-css/utilities.css";
@import "@iamtherealtim/lume-css/a11y.css";
@import "@iamtherealtim/lume-css/responsive.css";To add Lume to an existing app without touching its base styles, leave out reset.css. Everything else only styles .lume-* classes, data-tooltip attributes and the data-theme / data-ui-init hooks on <html>. core.css is always required because it defines the tokens.
Gradient buttons (lume-btn-gradient-*) are kept out of the core bundle. Add them only where a page wants them:
@import "@iamtherealtim/lume-css/extras.css";Fonts
Lume does not bundle fonts. The token stacks prefer Instrument Sans, Lora and JetBrains Mono, then fall back to system fonts, so Lume looks fine with nothing loaded. To match the docs, load them yourself, for example from Google Fonts:
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Lora:wght@600&family=JetBrains+Mono:wght@400;500&display=swap" />To use your own fonts, override --lume-font-sans, --lume-font-serif and --lume-font-mono.
JavaScript Runtime
dist/lume.js is optional. Every component renders without it. It adds behaviour through data attributes and has no dependencies. Type declarations ship as dist/lume.d.ts.
| Attribute | Effect |
| --- | --- |
| data-lume-toggle + data-lume-target="#id" | Opens or closes a modal, drawer or dropdown. Use data-lume-toggle="dropdown" on menu triggers |
| data-lume-open / data-lume-close | Opens or closes the target. A bare data-lume-close closes the dialog or menu it sits in |
| data-lume-dismiss="toast" / "alert" | Removes the surrounding toast, or alert/callout |
| data-lume-remove | Removes the surrounding tag, badge or chip, or the nearest ancestor matching its selector |
| data-lume-autodismiss="6000" / "false" | Toast lifetime in ms (default 4500), or keep it. Hover pauses the remaining time |
| data-lume-overlay, data-lume-panel | Point a drawer at its overlay, or a tab at its panel, when markup order can't |
An invalid selector in any of these attributes logs a console warning and is ignored. It never throws.
API
| Member | Returns | Notes |
| --- | --- | --- |
| Lume.open(target, trigger?) | void | target is a selector or element. Focus returns to trigger, or to whatever was focused before, on close |
| Lume.close(target) | void | |
| Lume.toggle(target, trigger?) | void | |
| Lume.isOpen(target) | boolean | false if the target doesn't resolve |
| Lume.initTabs(root?) | void | For tabs inserted after page load |
| Lume.initToasts(root?) | void | Rarely needed; added toasts are picked up automatically |
| Lume.dismissToast(toast) | void | |
| Lume.version | string | |
Component contracts
| Component | lume.js provides | You provide |
| --- | --- | --- |
| Modal, drawer | Focus moved in and trapped; Escape closes the top-most dialog; focus restored; scroll locked without touching your body styles; role="dialog" and aria-modal added if missing; closed dialogs hidden from assistive tech | An accessible name: aria-labelledby or aria-label. The runtime warns when it's missing but never invents one |
| Dropdown | The ARIA menu pattern: aria-haspopup, aria-expanded, Arrow/Home/End, Escape, item activation closes and restores focus, click or tab away closes | role="menu", role="menuitem", role="separator". Menus hold actions only |
| Tabs | Roles, aria-selected, roving tabindex, aria-controls / aria-labelledby when ids exist, Arrow/Home/End, hidden inactive panels | Ids on tabs and panels; a label on the tab list |
| Toast | Auto-dismiss, pause on hover, dismiss buttons | aria-live="polite" on the container (or role="alert"); labels on icon-only close buttons |
| Tooltip | Nothing: data-tooltip is pure CSS | Treat it as a visual hint. Screen readers don't announce it, so never put information only there |
Full examples are in the docs.
Development
npm ci
npx playwright install chromium # once, for the browser tests
npm run build # src/ -> dist/ and docs/ runtime assets, stamps the version, prints sizes
npm test # static checks: build output fresh, one version everywhere, classes defined, guards
npm run test:e2e # Playwright: component behaviour + axe accessibility scans in both themes
npm run serve # preview at http://localhost:4173/docs/src/ holds every source file, including the runtime (src/lume.js) and its types. dist/ and the CSS/JS copies in docs/ are generated. Commit them anyway: the CDN serves dist/ and GitHub Pages serves docs/. CI fails if they don't match a fresh build, or if any test fails.
The version lives only in package.json. The build stamps it into every CSS banner and into Lume.version.
No-Flicker Startup (Recommended)
Use this pattern in your app shell for stable first paint (theme + icon hydration):
<script>
document.documentElement.setAttribute('data-ui-init', 'pending');
try {
const savedTheme = localStorage.getItem('lume-theme');
if (savedTheme) document.documentElement.setAttribute('data-theme', savedTheme);
} catch (_) {}
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@iamtherealtim/[email protected]/dist/lume.min.css" />
<script defer src="https://unpkg.com/[email protected]/dist/umd/lucide.min.js"></script>The matching CSS guard (html[data-ui-init="pending"] body { visibility: hidden; }) is built into lume.css, so you only need to remove the attribute once your own hydration is done:
document.documentElement.removeAttribute('data-ui-init');If lume.js is loaded it also clears the attribute shortly after window.load as a safety net, so a page can never stay blank.
Core + extended patterns include:
- Buttons, Forms, Cards, Badges, Alerts, Tables
- Modal, Tooltip, Tabs, Accordion, Dropdown
- Progress, Avatar, Toast, Breadcrumb, Pagination, Skeleton
- Callout, List Group, Stat, Empty State, Stepper, Drawer, Kbd
Browser Support
Lume uses color-mix(), :focus-visible, inset, aspect-ratio and :not() selector lists. That means Chrome/Edge 111+, Firefox 113+ and Safari 16.4+. There are no polyfills or fallbacks for older browsers.
Accessibility & UX
- Focus-visible outlines for interactive controls
- Reduced-motion handling for animations
- Dark mode via
html[data-theme="dark"]with a real elevation scale: page, recessed surface, raised card, floating overlay each get their own shade --lume-on-accentkeeps text on solid accent/danger fills white in both themes- Every text token meets WCAG AA (4.5:1) on every surface and tint in both themes, enforced by axe scans in CI
- Focus rings derive from
--lume-accentthrough--lume-focus-ring, so brand overrides carry through - First-paint stability pattern documented in the docs
Repo Structure
.
├── src/ source of truth: CSS layers + runtime (lume.js)
├── dist/ generated, published to npm
├── docs/ GitHub Pages site: docs, showcase, admin demo, templates
├── scripts/
│ ├── build.mjs builds dist/ and docs/ runtime assets
│ ├── check.mjs static checks (npm test)
│ └── serve.mjs static server for preview and tests
├── tests/ Playwright fixture, behaviour specs, axe specs
├── notes/ planning notes, not part of the library
└── .github/workflows/ CILicense
MIT — see LICENSE.
Contributing
See CONTRIBUTING.md for contribution workflow and PR checklist.
