Welcome to Lux
The attribute-first CSS & JS library. Style and add interactivity to anything using plain HTML attributes — no class names, no build step, no dependencies.
Quick Example
Here's all it takes to build a glass card with an animated title, a badge, and a button that fires confetti:
<div surface="glass" radius="xl" density="spacious" motion="expressive"> <span badge="dot" tone="success">Live</span> <h2 text="heading" text-gradient="electric">Hello Lux</h2> <p text="body">One attribute per feature.</p> <button surface="solid" tone="primary" radius="full" ripple magnetic confetti-trigger> Celebrate 🎉 </button> </div>
Hello Lux
One attribute per feature.
How Attributes Work
Lux reads standard HTML attributes and applies pre-built CSS variables and classes. Every attribute maps directly to a visual property:
| Attribute | What it does | Example |
|---|---|---|
| surface= | Visual material of the element | "glass", "solid", "neon" |
| tone= | Color role — sets --lux-tone-color | "primary", "danger", "success" |
| text= | Typography style preset | "heading", "hero", "caption" |
| layout= | Display layout mode | "grid", "stack", "row" |
| motion= | Hover interaction style | "subtle", "expressive" |
| reveal= | Scroll-triggered entrance animation | "bottom", "scale", "blur" |
| animate= | Looping CSS animation | "spin", "bounce", "pulse" |
| gradient= | Named background gradient | "sunset", "ocean", "neon" |
Installation
Three ways to use Lux — pick the one that fits your workflow.
CDN — Plain HTML
Paste these two lines inside your <head> and you're done:
<!-- CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/luxcss/dist/lux.css"/> <!-- JS --> <script src="https://cdn.jsdelivr.net/npm/luxcss/dist/lux.js"></script>
npm
npm install luxcss # or yarn add luxcss # or pnpm add luxcss
Download
Download lux.css and lux.js from the GitHub repo and host them yourself.
lux.js loads after lux.css and after the DOM is ready. Placing the script before </body> is the safest approach.Framework Guides
Step-by-step setup for React, Next.js, Vue, and Svelte.
React / Vite
// src/main.jsx import 'luxcss/dist/lux.css'; import 'luxcss/dist/lux.js';
export default function Card() { return ( <div surface="glass" radius="xl" motion="expressive"> <h2 text="heading" text-gradient="electric">Hello Lux</h2> <button surface="solid" tone="primary" ripple magnetic>Click</button> </div> ); }
Next.js — App Router
app/layout.tsx is a Server Component by default. Side-effect imports like lux.js only run on the server there — so we use a tiny Client Component to load it in the browser.Step 1 — Create LuxLoader
// src/components/LuxLoader.tsx 'use client'; import 'luxcss/dist/lux.js'; import { useEffect } from 'react'; import { usePathname } from 'next/navigation'; export default function LuxLoader() { const pathname = usePathname(); useEffect(() => { // Re-init Lux on every route change so reveal=, ripple=, // tooltip=, motion= etc. work on freshly rendered elements if (typeof window !== 'undefined' && window.Lux) { window.Lux.init(); } }, [pathname]); return null; }
usePathname re-init is important. Without it, Lux JS features like reveal=, ripple=, tooltip=, and motion= will stop working after client-side navigation — because Next.js updates the DOM without a full page reload, so Lux never re-scans the new elements.Step 2 — Use it in your root layout
// app/layout.tsx import 'luxcss/dist/lux.css'; import LuxLoader from '@/components/LuxLoader'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <LuxLoader /> {children} </body> </html> ); }
lux.css is safe to import directly in layout.tsx — CSS has no executable code so it works fine on the server. Only lux.js needs the Client Component wrapper.Next.js — Pages Router
// pages/_app.tsx import 'luxcss/dist/lux.css'; import 'luxcss/dist/lux.js'; export default function App({ Component, pageProps }) { return <Component {...pageProps} />; }
_app.tsx runs entirely on the client, so the simple two-import pattern works directly. No LuxLoader needed.Vue 3
// src/main.js import 'luxcss/dist/lux.css'; import 'luxcss/dist/lux.js';
SvelteKit
<!-- src/routes/+layout.svelte --> <script> import 'luxcss/dist/lux.css'; import { browser } from '$app/environment'; import { onMount } from 'svelte'; onMount(async () => { if (browser) await import('luxcss/dist/lux.js'); }); </script> <slot />
TypeScript
Lux ships with full TypeScript declarations. To activate them in your project, create one file:
// src/types/lux.d.ts ← create this file /// <reference path="../../node_modules/luxcss/dist/lux.d.ts" />
That's it — one file, one line. After that, every Lux attribute like surface=, tone=, ripple, magnetic will be fully typed across your entire project with no errors and full autocomplete.
import 'luxcss' does not reliably apply the type augmentation with modern bundler-mode TypeScript.Also make sure your tsconfig.json includes the types folder:
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"src/types/**/*.d.ts" // ← add this line
]
}Core Concepts
The mental model behind Lux in five minutes.
Attributes over Classes
Traditional CSS libraries use class names. Lux uses HTML attributes. The difference is readability — your markup describes intent, not implementation:
<!-- Tailwind approach --> <div class="bg-white/10 backdrop-blur-md border border-white/20 rounded-xl p-6 hover:-translate-y-1 transition-transform"> <!-- Lux approach --> <div surface="glass" radius="xl" density="spacious" motion="subtle">
Tone System
The tone= attribute sets a CSS variable --lux-tone-color that cascades into any child attribute that uses color. You set the tone once, and everything inside inherits it:
<div tone="danger"> <!-- All of these use var(--lux-danger) --> <span badge>Alert</span> <div glow>Glowing danger</div> <div surface="neon">Neon danger card</div> </div>
Density & Radius
Two attributes control spacing and shape independently of surface type:
JS Auto-Init
When lux.js loads it automatically finds all Lux attributes in the DOM and wires up interactions. You never call init() manually — though you can access the Lux global for programmatic control.
Lux.init() again to pick up new elements.Theming
Override CSS variables to match your brand. Light/dark modes built in.
CSS Variables
All Lux tokens live in :root. Override any of them in your stylesheet:
:root { /* Brand colors */ --lux-primary: #your-color; --lux-accent: #your-accent; /* Typography */ --lux-font-sans: 'Your Font', sans-serif; --lux-font-display: 'Your Display', sans-serif; /* Shape */ --lux-r-lg: 0.5rem; /* default radius for surfaces */ /* Spacing */ --lux-gap-md: 1.25rem; }
Dark / Light Mode
Lux defaults to dark mode. Switch with the scheme attribute on <html>:
<!-- Force light --> <html scheme="light"> <!-- Force dark --> <html scheme="dark"> <!-- Toggle button (auto persists to localStorage) --> <button theme-toggle>Toggle Theme</button>
Seed Color
Apply a custom one-off color to any element with seed=:
<div seed="#e11d48" surface="neon">Custom pink neon</div>
Surfaces
The visual material of any element. Combine with tone=, radius=, and density=.
All Surface Types
| Value | Description |
|---|---|
| solid | Filled with tone color. Best for CTAs and primary actions. |
| matte | Subtle filled surface with a faint border. Default card style. |
| glass | Frosted glass with backdrop blur. Great over images. |
| frosted | Heavier frost effect, brighter than glass. |
| ghost | Transparent with a tone-colored border and text. |
| neon | Dark background with glowing tone-colored border. |
| outline | Transparent with a subtle neutral border. |
| raised | Slightly elevated surface with a drop shadow. |
| ink | Inverted — fg color background, bg color text. |
| aurora | Animated gradient that shifts through colors. |
| mesh | Animated mesh gradient using tone colors. |
Combining with Tone
<div surface="neon" tone="danger" radius="xl" density="spacious"> Danger neon card </div>
Typography
Type scale, weights, gradients, strokes, and more.
Type Scale
code — monospaced inline text
Text Gradients
<h1 text="heading" text-gradient="sunset">Gradient Text</h1>
Text Stroke & Glow
Typewriter
<p typewriter typewriter-speed="60" typewriter-cursor> Types itself when scrolled into view. </p>
Building with Lux is pure joy.
Layout
Powerful layout primitives — grid, stack, row, bento, masonry, and more.
Layout Modes
| Value | Description |
|---|---|
| stack | Vertical flex column |
| row | Horizontal flex row, centered |
| cluster | Flex row with wrapping |
| grid | CSS grid — use cols= to set columns |
| center | Flex center both axes |
| cover | Full-height centered hero |
| split | 50/50 two-column grid |
| sidebar | Sidebar + main content grid |
| bento | Auto-fit bento box grid |
| masonry | Multi-column masonry |
| prose | Centered reading column (68ch) |
Grid & Columns
<div layout="grid" cols="3" gap="md"> <div>1</div> <div span="2">spans 2 columns</div> <div>3</div> </div>
Responsive Columns
<div layout="grid" cols="1" md-cols="2" lg-cols="4" gap="md"> <!-- 1 col mobile, 2 col tablet, 4 col desktop --> </div>
Stagger Children
Add stagger= to a parent to automatically apply staggered reveal animations to all children:
<div layout="grid" cols="3" stagger="100"> <!-- Each child gets reveal="bottom" with 0ms, 100ms, 200ms delays --> </div>
Gradients
18 named background gradients and 8 text gradients — ready to use.
Background Gradients
<div gradient="aurora" radius="xl" density="spacious">Aurora card</div> <!-- Text gradient --> <h1 text-gradient="sunset">Gradient Headline</h1>
Effects & Filters
Glow, ring, blur, backdrop, and CSS filter effects.
Glow
Filters
<img filter="grayscale" src="..."/> <img filter="vintage" src="..."/> <img filter="dreamy" src="..."/> <div backdrop="blur-md">Blurred backdrop</div>
| filter= | Effect |
|---|---|
| blur-sm / blur-md / blur-lg | Gaussian blur at 4px, 12px, 24px |
| grayscale | 100% desaturated |
| sepia | Warm sepia tone |
| saturate / desaturate | Boost or reduce color saturation |
| bright / dim | Brightness +30% or -30% |
| contrast | Contrast +40% |
| vintage | Sepia + contrast + slight desaturation |
| dreamy | Soft blur + brightness + saturation |
Motion & Hover
Hover interactions with spring physics. Zero JS needed.
Motion Modes
Float
<div float="slow">Slow float</div> <div float="med">Medium float</div> <div float="fast">Fast float</div> <div float="slow" axis="xy">XY float</div>
Animations
12 looping CSS animations, one attribute.
All Animations
<div animate="spin">⟳</div> <div animate="heartbeat">❤</div> <!-- Speed modifiers --> <div animate="spin" anim-duration="fast">Fast spin</div> <div animate="bounce" anim-once>Bounce once</div>
Scroll Reveal
Elements animate in when they enter the viewport — no JS required on your end.
Directions
<div reveal="bottom">Slides up from below</div> <div reveal="left">Slides in from left</div> <div reveal="scale">Scales in</div> <div reveal="blur">Unblurs into view</div> <div reveal="rotate">Rotates in</div> <!-- With delay --> <div reveal="bottom" reveal-delay="300">Delayed 300ms</div> <!-- Auto-stagger all children --> <div layout="grid" cols="3" stagger="100"> <div>child 1</div> <div>child 2</div> <div>child 3</div> </div>
IntersectionObserver with a 12% threshold. Elements trigger once they're 12% in view, with a 40px bottom margin.Accordion
Collapsible sections with smooth animation. No JavaScript needed.
Basic Accordion
<div accordion> <div accordion-item> <button accordion-trigger>Question?</button> <div accordion-content>Answer here.</div> </div> </div> <!-- Allow multiple open at once --> <div accordion multi>...</div>
Over 200 attributes covering surfaces, layout, typography, motion, gradients, components, and more.
Yes — add Lux attributes to any JSX element. Import the CSS and JS once in your entry file.
Lux uses semantic HTML patterns. Full ARIA support is on the v2 roadmap.
Tabs
Animated tab panels. First tab activates automatically.
Usage
<div tabs> <div tab-list> <button tab>Overview</button> <button tab>Details</button> <button tab>Code</button> </div> <div tab-panel>Content for overview</div> <div tab-panel>Content for details</div> <div tab-panel>Content for code</div> </div>
Lux tabs animate in with a smooth slide-up on panel switch. The first tab is activated automatically on load.
Each tab and panel are matched by DOM order. Tabs and panels are children of the same tabs container.
<div tabs> ... </div>
Modal
Spring-animated modal dialogs with backdrop blur. ESC key supported.
Usage
<!-- Trigger button --> <button modal-open="my-modal">Open Modal</button> <!-- Modal structure (anywhere in body) --> <div modal-backdrop id="my-modal"> <div modal radius="xl"> <button modal-close>✕</button> <h2 text="heading">Modal Title</h2> <p>Modal content here.</p> </div> </div>
Lux Modal
Modals spring in with a scale + translate animation. Close with the ✕ button, click the backdrop, or press ESC.
Drawer
Slide-in panels from any side of the screen.
Usage
<button drawer-open="my-drawer">Open Drawer</button> <div drawer-backdrop id="my-drawer"> <div drawer drawer-side="right"> <button drawer-close>✕</button> Drawer content </div> </div> <!-- drawer-side: right | left | top | bottom -->
Right drawer content.
Left drawer content.
Bottom drawer content.
Popover
Click-triggered floating panels. Closes on outside click automatically.
Usage
<div popover-trigger> <button>Open Popover</button> <div popover-panel> Popover content here </div> </div>
With custom content
<div popover-trigger> <button surface="matte" radius="full">Profile ↓</button> <div popover-panel> <div layout="stack" gap="sm"> <div layout="row" gap="sm"> <div surface="solid" tone="primary" radius="full" style="width:36px;height:36px;display:flex;align-items:center;justify-content:center">A</div> <div> <div style="font-weight:600;font-size:0.875rem">Abideen</div> <div style="font-size:0.75rem;opacity:0.5">admin@luxcss.dev</div> </div> </div> </div> </div> </div>
Toast
Non-blocking notifications that auto-dismiss. Trigger via HTML or JS.
HTML Trigger
<button toast-trigger toast-title="Saved!" toast-msg="Your changes were saved." toast-type="success" toast-duration="3000" >Save</button> <!-- toast-type: success | danger | warning | info | default -->
JS API
Lux.toast("File saved!", { title: "Success", type: "success", // success | danger | warning | info | default duration: 3500, // ms before auto-dismiss }); // Returns an object with dismiss() const t = Lux.toast("Loading…"); setTimeout(() => t.dismiss(), 2000);
Badges
Small status indicators and counters.
Variants
<span badge tone="success">Active</span> <span badge="dot" tone="success">Live</span> <span badge="counter" tone="danger">99+</span> <span badge="pill" tone="primary">New</span>
Tooltip
CSS-only hover tooltips in four directions. No JS needed.
Usage
<button tooltip="This is a tooltip">Hover me</button> <!-- Position --> <button tooltip="Below" tooltip-pos="bottom">Below</button> <button tooltip="Left" tooltip-pos="left">Left</button> <button tooltip="Right" tooltip-pos="right">Right</button>
Progress
Animated progress bars with optional striped variant.
Basic Progress
<div progress tone="primary"> <div progress-bar style="width:65%"></div> </div> <!-- Animated stripes --> <div progress="striped" tone="success"> <div progress-bar style="width:40%"></div> </div>
Attributes
| Attribute | Description |
|---|---|
| progress | Creates the track. Use tone= to set the bar color. |
| progress="striped" | Animated diagonal stripe pattern on the bar. |
| progress-bar | The fill element inside. Set width via style="width:X%". |
Forms
Styled inputs, selects, checkboxes, and field wrappers.
Input Types
Field Wrapper
<div field> <label>Email</label> <input input type="email"/> <span hint>We'll never share your email.</span> </div> <!-- With states --> <div field state="error"> <label>Password</label> <input input type="password"/> <span hint>Must be 8+ characters.</span> </div>
Tilt 3D
Hardware-accelerated 3D perspective tilt on hover.
<div tilt>Default tilt (12°)</div> <div tilt="8">Gentle tilt (8°)</div> <div tilt="20" tilt-glare>Intense with glare</div>
tilt
tilt + glare
Magnetic
Elements that attract toward the cursor with spring physics.
<button magnetic>Default (0.4 strength)</button> <button magnetic="0.7">Stronger attraction</button> <button magnetic="0.2">Subtle attraction</button>
Ripple
Material-style click ripple from cursor origin.
<button ripple>Click me</button> <div ripple surface="matte">Clickable card</div>
Confetti 🎉
Particle burst from click origin. One attribute, instant delight.
<!-- HTML trigger --> <button confetti-trigger>🎉</button> <button confetti-trigger confetti-count="150">More!</button> // JS API Lux.confetti({ count: 80, duration: 3000, origin: { x: 0.5, y: 0.4 } });
Typewriter
Text that types itself when scrolled into view.
<p typewriter>Types at default speed.</p> <!-- With options --> <p typewriter typewriter-speed="40" <!-- ms per char --> typewriter-cursor <!-- blinking cursor --> typewriter-loop <!-- repeat forever --> >Loops forever.</p>
Building beautiful UIs with Lux.
Zero dependencies. One attribute.
Attributes
| Attribute | Description | Default |
|---|---|---|
| typewriter | Enables the effect — required | — |
| typewriter-speed= | Milliseconds per character | 50 |
| typewriter-cursor | Shows a blinking cursor while typing | off |
| typewriter-loop | Restarts after finishing | off |
Counter
Animated number that counts up when scrolled into view.
<!-- Basic --> <span counter="1000">0</span> <!-- With all options --> <span counter="9999" counter-from="1000" counter-duration="2000" counter-decimals="0" counter-prefix="$" counter-suffix="k" counter-sep <!-- thousands separator --> >0</span>
Cursor Effects
Canvas cursor trail and custom cursor replacement.
Cursor Trail
<!-- Add to body or any parent container --> <body cursor-trail cursor-color="#6366f1"> <body cursor-trail="line" cursor-color="#f472b6"> <!-- cursor-trail: dots (default) | line -->
Custom Cursor
<!-- Replaces the OS cursor with a styled dot + ring --> <div custom-cursor></div>
JS API
Programmatic access to all Lux features via the global Lux object.
// Toast const t = Lux.toast(msg, { title, type, duration }); t.dismiss(); // programmatically dismiss // Confetti Lux.confetti({ count, duration, origin: { x, y } }); // Theme Lux.applyScheme('dark' | 'light'); // Modal Lux.openModal(backdropEl); Lux.closeModal(backdropEl); // Drawer Lux.openDrawer(backdropEl); Lux.closeDrawer(backdropEl); // Counter (manually trigger) Lux.animateCounter(element); // Re-init (for dynamically added elements) Lux.init();
Custom Events
// Modal events el.addEventListener('lux:modal:open', handler); el.addEventListener('lux:modal:close', handler); // Theme change btn.addEventListener('lux:theme:change', (e) => { console.log(e.detail.scheme); // 'dark' | 'light' });
Design Tokens
All CSS variables available to override.
:root { /* Colors */ --lux-primary: #6366f1; --lux-danger: #ef4444; --lux-success: #22c55e; --lux-warning: #f59e0b; --lux-info: #38bdf8; --lux-accent: #f472b6; --lux-neutral: #6b7280; /* Surfaces */ --lux-bg: #0f0f13; --lux-fg: #ffffff; --lux-surface-1: rgba(255,255,255,0.04); --lux-surface-2: rgba(255,255,255,0.08); --lux-border: rgba(255,255,255,0.10); /* Typography */ --lux-font-sans: 'Inter var', system-ui, sans-serif; --lux-font-mono: 'Fira Code', monospace; --lux-font-display: 'Cal Sans', var(--lux-font-sans); /* Radius */ --lux-r-sm: 0.25rem; --lux-r-md: 0.5rem; --lux-r-lg: 0.75rem; --lux-r-xl: 1.25rem; --lux-r-full: 9999px; /* Spacing */ --lux-gap-sm: 0.5rem; --lux-gap-md: 1rem; --lux-gap-lg: 1.5rem; --lux-gap-xl: 2.5rem; /* Motion */ --lux-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); --lux-dur-base: 220ms; --lux-dur-slow: 400ms; }
Utilities
Handy one-off attributes for quick adjustments.
| Attribute | Description | Values |
|---|---|---|
| aspect= | Aspect ratio | square, video, portrait, wide, golden |
| object= | object-fit for images | cover, contain, fill |
| overflow= | Overflow behaviour | hidden, auto, scroll, clip |
| opacity= | Opacity level | 0, 25, 50, 75, 100 |
| blend= | mix-blend-mode | multiply, screen, overlay, difference… |
| z= | z-index shortcut | 0, 10, 20, 50, 100 |
| pos= | position property | relative, absolute, fixed, sticky |
| w= | Width shortcut | full, screen, auto |
| h= | Height shortcut | full, screen, auto |
| inset | position:absolute; inset:0 | (boolean) |
| center-abs | Absolute centered via transform | (boolean) |
| no-select | user-select: none | (boolean) |
| pointer-none | pointer-events: none | (boolean) |
| truncate= | Text truncation | (boolean), 2, 3, 4, 5 |
| balance | text-wrap: balance | (boolean) |
| scrollbar= | Scrollbar style | none, thin |
| hide= | Hide at breakpoint | mobile, tablet, desktop |
| show= | Show only at breakpoint | mobile, tablet, desktop |
| skeleton | Loading skeleton shimmer | (boolean) |
| shimmer | Shimmer effect (no content hide) | (boolean) |
| dismiss | Removes nearest surface on click | (boolean) or CSS selector |
| copy= | Copies text to clipboard on click | text or CSS selector |
| toggle= | Toggles display of target element | CSS selector |
| smooth-scroll | Smooth scroll to href anchor | (boolean) |
| lazy | Lazy-load image via data-src | (boolean) |
Patterns & Masks
CSS background patterns and mask effects.
Patterns
Masks
<img mask="fade-bottom" src="..."/> <div mask="vignette">...</div> <div mask="fade-x">...</div> <!-- mask: fade-bottom | fade-top | fade-x | vignette | circle | spotlight -->