luxen-ui
v0.16.2
Published
Modern web components and CSS-first UI library built with Lit. Framework-agnostic, customizable prefix, design tokens.
Maintainers
Readme
Luxen UI
A Web UI library built with with modern CSS-first, native HTML and custom HTML elements.
Features
- Web Components — Custom elements with the
l-prefix (e.g.<l-badge>,<l-tooltip>) - CSS-only elements — Styled with plain CSS classes, no JavaScript required
- Modular — Import only what you need (JS and CSS are individually tree-shakeable)
- CDN-ready — Ship individual modules directly from a CDN
Documentation
Visit https://luxen-ui.com to explore the documentation.
📦 Elements
| Name | HTML tag | Type |
| -------------- | -------------------- | --------------------------------------- |
| Alert | <l-alert> | ◇ Custom HTML Element (no Shadow DOM) |
| Alert dialog | <l-alert-dialog> | ⬢ Custom HTML Element (with Shadow DOM) |
| Avatar | <l-avatar> | ⬢ Custom HTML Element (with Shadow DOM) |
| Badge | <l-badge> | ◇ Custom HTML Element (no Shadow DOM) |
| Breadcrumb | <nav> | ⏣ Native HTML Element |
| Button | <button> | ⏣ Native HTML Element |
| Button group | <l-button-group> | ◇ Custom HTML Element (no Shadow DOM) |
| Carousel | <l-carousel> | ⬢ Custom HTML Element (with Shadow DOM) |
| Carousel item | <l-carousel-item> | ⬢ Custom HTML Element (with Shadow DOM) |
| Checkbox | <input> | ⏣ Native HTML Element |
| Close button | <button> | ⏣ Native HTML Element |
| Combobox | <l-combobox> | ⬢ Custom HTML Element (with Shadow DOM) |
| Dialog | <l-dialog> | ⬢ Custom HTML Element (with Shadow DOM) |
| Disclosure | <details> | ⏣ Native HTML Element |
| Divider | <l-divider> | ◇ Custom HTML Element (no Shadow DOM) |
| Drawer | <l-drawer> | ⬢ Custom HTML Element (with Shadow DOM) |
| Dropdown | <l-dropdown> | ⬢ Custom HTML Element (with Shadow DOM) |
| Dropdown item | <l-dropdown-item> | ⬢ Custom HTML Element (with Shadow DOM) |
| Dropdown label | <l-dropdown-label> | ⬢ Custom HTML Element (with Shadow DOM) |
| Form field | <l-form-field> | ⬡ Progressive Custom HTML Element |
| Icon | <l-icon> | ⬢ Custom HTML Element (with Shadow DOM) |
| Input | <input> | ⏣ Native HTML Element |
| Input group | <l-input-group> | ⬡ Progressive Custom HTML Element |
| Input OTP | <l-input-otp> | ⬡ Progressive Custom HTML Element |
| Input stepper | <l-input-stepper> | ⬡ Progressive Custom HTML Element |
| Kbd | <kbd> | ⏣ Native HTML Element |
| Popover | <l-popover> | ⬢ Custom HTML Element (with Shadow DOM) |
| Progress | <progress> | ⏣ Native HTML Element |
| Prose editor | <l-prose-editor> | ⬢ Custom HTML Element (with Shadow DOM) |
| Radio | <input> | ⏣ Native HTML Element |
| Rating | <l-rating> | ⬢ Custom HTML Element (with Shadow DOM) |
| Select | <l-select> | ⬢ Custom HTML Element (with Shadow DOM) |
| Skeleton | <l-skeleton> | ◇ Custom HTML Element (no Shadow DOM) |
| Slider | <l-slider> | ⬢ Custom HTML Element (with Shadow DOM) |
| Spinner | <l-spinner> | ⬢ Custom HTML Element (with Shadow DOM) |
| Sticky bar | <l-sticky-bar> | ⬢ Custom HTML Element (with Shadow DOM) |
| Stories | <l-stories> | ◇ Custom HTML Element (no Shadow DOM) |
| Stories viewer | <l-stories-viewer> | ⬢ Custom HTML Element (with Shadow DOM) |
| Story | <l-story> | ◇ Custom HTML Element (no Shadow DOM) |
| Switch | <input> | ⏣ Native HTML Element |
| Tabs | <l-tabs> | ⬡ Progressive Custom HTML Element |
| Tag | <l-tag> | ⬢ Custom HTML Element (with Shadow DOM) |
| Textarea | <textarea> | ⏣ Native HTML Element |
| Toast | <l-toast> | ◇ Custom HTML Element (no Shadow DOM) |
| Tooltip | <l-tooltip> | ⬢ Custom HTML Element (with Shadow DOM) |
| Tree | <l-tree> | ⬢ Custom HTML Element (with Shadow DOM) |
| Tree item | <l-tree-item> | ⬢ Custom HTML Element (with Shadow DOM) |
⏣ Native HTML Element · ⬡ Progressive Custom HTML Element · ◇ Custom HTML Element (no Shadow DOM) · ⬢ Custom HTML Element (with Shadow DOM)
Install
npm install luxen-uiUsage
// Import a web component
import 'luxen-ui/tooltip';
// Import a CSS-only element
import 'luxen-ui/css/button';<button
id="my-button"
type="button"
class="l-button"
>
Hover me
</button>
<l-tooltip for="my-button">Hello world</l-tooltip>TypeScript (prefix-aware)
Element classes are exported under luxen-ui/<name>/element as side-effect-free
type entries:
import type { Badge, BadgeVariant } from 'luxen-ui/badge/element';The package does not ship a HTMLElementTagNameMap augmentation by
default — consumers own that file so it always reflects the prefix they
actually use (default l-* or rebranded). The Vite plugin can write it for
you:
// vite.config.ts (or nuxt.config.ts)
import luxen from 'luxen-ui/vite-plugin';
export default defineConfig({
plugins: [
luxen({
elementPrefix: 'pulse',
cssPrefix: 'pulse',
emitTypes: 'types/luxen.d.ts', // ← writes the file on first build
}),
],
});The generated types/luxen.d.ts imports the class types from the
*/element subpaths and augments HTMLElementTagNameMap for every Luxen
element under your prefix. Once written:
- You own the file. Edit it freely — drop elements you don't use, or add ones from your own custom element set.
- The plugin never overwrites an existing file silently. Pass
emitTypes: { path: 'types/luxen.d.ts', force: true }to regenerate. - Drift detection. If your
elementPrefixlater changes, the plugin logs a warning so you can regenerate.
Need a subset only?
luxen({
elementPrefix: 'pulse',
emitTypes: { path: 'types/luxen.d.ts', elements: ['badge', 'dropdown', 'popover'] },
});Vue / Nuxt — strict template checking
HTMLElementTagNameMap types the DOM side (document.querySelector,
el.variant = …) but Vue's template checker treats custom elements as a
permissive surface — typos and bad prop values are not flagged. Use
target: 'vue' to additionally augment Vue's GlobalComponents:
luxen({
elementPrefix: 'pulse',
cssPrefix: 'pulse',
emitTypes: { path: 'types/luxen.d.ts', target: 'vue' },
});Then enable strict templates in your tsconfig (for Nuxt, set it via
nuxt.config.ts → typescript.tsConfig.vueCompilerOptions):
{
"vueCompilerOptions": { "strictTemplates": true },
}Now <pulse-badge variant="bogus"> and <pulse-badge typo="x"> are errors
in the editor and in vue-tsc, while autocomplete stays scoped to each
element's real props. The generated file also re-allows data-* and slot
on native elements (which strictTemplates would otherwise reject) and keeps
@event listeners permissive.
Installed under an npm alias? Pass packageName so the emitted imports
resolve:
// package.json
{ "dependencies": { "pulse-ui": "npm:luxen-ui@^0.5.0" } }emitTypes: { path: 'types/luxen.d.ts', target: 'vue', packageName: 'pulse-ui' }Local Development
Requires Node.js >= 24 and pnpm.
pnpm install
pnpm build# Dev servers
cd packages/ui && pnpm dev # CSS watch
cd packages/ui && pnpm dev:elements # Vite dev server
cd packages/docs && pnpm dev # Docs (VitePress)License
Licensed under the MIT license.
