Skip to main content

Get started

With a bundler or framework

Import LUZ's CSS and script in Astro or any tool that resolves package names, and keep the layer order and the fonts.

The imports

Import the CSS of each package you use, tokens first, and the script if you want behaviour. A bundler resolves these names from node_modules; a <link> in HTML cannot.

JS
import "@luz-ds/tokens/css";
import "@luz-ds/core/css";
import "@luz-ds/core";

// Only if the page uses a catalog element: one import each (here the search)
import "@luz-ds/catalog/components/search.css";
import "@luz-ds/catalog";

// A pattern of a kind of site is a catalog component too: one import each (here the hero of a landing page)
import "@luz-ds/catalog/components/hero.css";
  • The tokens come first: every other package needs them.
  • The navbar, the sidebar, the terminal and the timeline are catalog components: @luz-ds/core/css does not include them, so import @luz-ds/catalog/components/<name>.css for each one you use.
  • import "@luz-ds/core" is the script. It is optional: every LUZ page works without it.
  • To load less CSS, import @luz-ds/core/base.css and then one file per component, for example @luz-ds/core/components/button.css.
  • To load one script module, import it by name and call its function: see JavaScript modules.

For the lines that match what you are building, with the CDN tags and their integrity hashes, choose your snippet.

Every name above is an entry of the exports map of its package. This table lists all of them.

The exports of each package
PackageExportWhat it is
@luz-ds/tokens
.
luz-tokens.css, the same file as ./css, for tools that look for the main stylesheet of a package
@luz-ds/tokens
./css
luz-tokens.css: every token, with the @font-face rules of ET Book
@luz-ds/tokens
./css/min
luz-tokens.min.css: the same, minified
@luz-ds/tokens
./tokens.css
tokens.css: every token, with no @font-face. For when you bring your own font
@luz-ds/tokens
./fonts.css
The @font-face rules alone
@luz-ds/tokens
./tokens.json
The tokens as JSON, for tooling
@luz-ds/tokens
./dtcg
The tokens in the W3C Design Tokens format, for Figma and other design tools
@luz-ds/tokens
./dtcg/resolver
A resolver document for the same tokens, with their three color modes
@luz-ds/tokens
./fonts/*
The ET Book font files that the stylesheets point to
@luz-ds/tokens
./classes.json
Every class and custom property the package defines, with its kind and layer, as JSON for tools and AI agents
@luz-ds/core
.
luz.js: the script, which starts every behaviour on the page. Import it in the browser
@luz-ds/core
./css
luz.css: the reset, the bare elements, the layout objects, the components and the utilities
@luz-ds/core
./css/min
luz.min.css: the same, minified
@luz-ds/core
./base.css
The settings, the reset and the bare elements, for loading components one by one
@luz-ds/core
./print.css
The print styles alone. luz.css already has them
@luz-ds/core
./conditional.css
luz.conditional.css: the same as luz.css, with the reset and the bare elements only inside data-luz, for a page that already has a design
@luz-ds/core
./components/*
One stylesheet per component, such as @luz-ds/core/components/button.css
@luz-ds/core
./themes/*
The template of a look of your own, themes/_template.css
@luz-ds/core
./theme
Looks of your own: initThemeSystem, applyTheme
@luz-ds/core
./theme-mode
The <luz-theme-mode> element, loaded on demand
@luz-ds/core
./theme-boot
The script for the head: the color mode, before the first paint
@luz-ds/core
./clipboard
Copy buttons: initCopyButtons
@luz-ds/core
./modal
The fallback for command and commandfor on a dialog: initModals
@luz-ds/core
./highlight
Code highlighting: initHighlight
@luz-ds/core
./player
Audio and video players: initPlayers
@luz-ds/core
./tabs
Tabs: initTabs
@luz-ds/core
./midi
The MIDI player: MidiPlayer, loaded on demand
@luz-ds/core
./table-sort
Sortable tables: initTableSort, loaded on demand
@luz-ds/core
./utils
Helpers such as formatTime and enableJs
@luz-ds/core
./i18n
The strings of the scripts: t, registerLocale, loadLocales
@luz-ds/core
./i18n/*
The strings of one language LUZ ships, for example @luz-ds/core/i18n/es: its default export is an object of strings, loaded on demand
@luz-ds/core
./classes.json
Every class and custom property the package defines, with its kind and layer, as JSON for tools and AI agents
@luz-ds/core
./custom-elements.json
Every custom element the package defines or styles, with its attributes and events, in the Custom Elements Manifest format, for editors and AI agents
@luz-ds/catalog
.
catalog.js: loads the script of each catalog element the page has, and the sidebar's
@luz-ds/catalog
./search
The <luz-search> element
@luz-ds/catalog
./cookie-consent
The <luz-cookie-consent> element, and getConsent
@luz-ds/catalog
./theme-time
The <luz-theme-time> element
@luz-ds/catalog
./theme-time-bar
The slider of <luz-theme-time>: initThemeTimeBar
@luz-ds/catalog
./theme-time-boot
The script for the head: the moment, before the first paint
@luz-ds/catalog
./sidebar
Scroll spy and the drawer: initSidebar
@luz-ds/catalog
./timeline
The <luz-timeline> element
@luz-ds/catalog
./embed
The <luz-embed> element and playerURL()
@luz-ds/catalog
./chart
The <luz-chart> element and readChart()
@luz-ds/catalog
./components/*
One stylesheet per catalog component, and its only CSS: banner, consent, language switch, color mode by time of day, navbar, search, sidebar, submenu, terminal, timeline and the patterns of a kind of site, such as hero.css. luz.css does not include the navbar, the sidebar, the terminal or the timeline
@luz-ds/catalog
./classes.json
Every class and custom property the package defines, with its kind and layer, as JSON for tools and AI agents
@luz-ds/catalog
./custom-elements.json
Every custom element the package defines or styles, with its attributes and events, in the Custom Elements Manifest format, for editors and AI agents

The layer order survives bundling

Every CSS file LUZ publishes starts with the same @layer statement, which declares the order of all of LUZ's layers. So the order is the same however your tool splits, reorders or joins the files: the first one to load sets it.

In the Astro build below, the layers came out in the declared order, luz.tokens first and luz.print last, and an unlayered rule of the consumer came last.

CSS of your own that is outside any layer wins over every layer of LUZ, in any position, with no !important. Cascade layers has the full list and how to override one.

Astro, step by step

A new Astro project with LUZ's tokens and core, a layout that loads them, and CSS of your own that comes last.

Built with Astro 7.3 on 2026-10-15. LUZ is in beta and Astro changes between releases, so this page is dated: if a step fails on a newer Astro, check that release's notes first.

The build used packages packed from the LUZ repository at version 0.0.0, installed with npm install --legacy-peer-deps, because the peer ranges of ^0.1.0 do not resolve until 0.1.0 is published. After that, the command below needs no flag.

The project

my-site/
├─ package.json
├─ astro.config.mjs            (empty config: defineConfig({}))
└─ src/
   ├─ layouts/Base.astro       the two CSS imports, the script
   ├─ styles/site.css          your own CSS, last, outside any layer
   └─ pages/index.astro

astro.config.mjs stays as Astro creates it. LUZ needed no setting in it.

The commands

Shell
npm create astro@latest my-site
cd my-site
npm install @luz-ds/tokens @luz-ds/core
npm run dev

The layout

Astro
---
// src/layouts/Base.astro
import "@luz-ds/tokens/css";
import "@luz-ds/core/css";
import "../styles/site.css";
---
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>My site</title>
  </head>
  <body>
    <slot />
    <script>
      import "@luz-ds/core";
    </script>
  </body>
</html>

Your CSS and the page

CSS
/* src/styles/site.css */
:root { --luz-type-base: 1.125rem; }
Astro
---
// src/pages/index.astro
import Base from "../layouts/Base.astro";
---
<Base>
  <main class="o-container">
    <h1>My site</h1>
    <button class="luz-btn luz-btn--primary" type="button">Save changes</button>
  </main>
</Base>

The rule in site.css sets the type knob to 1.125rem and wins over LUZ's default without a layer and without !important. Make it yours has the other knobs.

What the build showed

  • astro build and astro dev both worked with the two CSS imports in the frontmatter of a layout and no setting in astro.config.mjs.
  • Astro bundled the <script> with import "@luz-ds/core" into one module file and split the lazy chunks off it.
  • The fonts came out as hashed files, and the layers and your own rule came out in the order described above.

The build output was inspected, and the built page was not opened in a browser. Astro with a view transition router (<ClientRouter />) was not tried, and neither was Windows.

The import of a pattern of a kind of site has the same shape as the search one above, and was not built.

Vite, Next, Nuxt and SvelteKit

Only Astro was built for this page; Vite on its own, Next, Nuxt and SvelteKit were not built or tested. The imports are the same ones, in the root layout or the entry file of the app.

Each tool's own documentation says where CSS from a package goes:

One fact is the same for all of them. A LUZ module has no side effect when it is imported, until you call its init function, and luz.js and catalog.js check that document exists first.

So a framework that renders on the server can import them. In plain Node, with no document, ten modules of core and catalog were imported with no error.

Fonts and assets

@luz-ds/tokens/css is luz-tokens.css, and its @font-face rules point at the ET Book files with url(). Your bundler resolves those paths: Astro emitted the files as hashed woff2 assets.

To skip ET Book, import @luz-ds/tokens/tokens.css instead. It has the same tokens and no @font-face, so no font is downloaded.

Then set --luz-font-body to the font you use: Make it yours shows how.

A CDN in a framework

You can load LUZ from a CDN in a framework layout instead of importing it. In an Astro layout, a <link rel="stylesheet"> in the <head> and a <script type="module" src="https://…">, each with integrity and crossorigin, stay as written.

So does a script with is:inline. Astro only processes a <script> that has no attribute other than src.

Use the CDN or the imports, not both: LUZ would load twice. Choose your snippet gives the tags with their hashes.

If something does not work

  • A warning about a peer dependency. @luz-ds/core asks for @luz-ds/tokens at ^0.1.0 || ^0.2.0 || ^0.3.0 || ^0.4.0. Install both, at the same version: npm install @luz-ds/tokens @luz-ds/core.
  • The tool cannot resolve @luz-ds/core/css. The export is ./css, with no file extension. If your tool reports an unresolved import, add the package to vite.ssr.noExternal, as Astro's styling guide describes.
  • The styles come out in the wrong order. Two copies of LUZ on the page, for example one from a CDN and one from your bundler, put their rules in the same layers: load it once. An @import with layer(luz) nests the names as luz.luz.components: drop the layer(). Cascade layers explains the order.
JS
// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  vite: { ssr: { noExternal: ["@luz-ds/tokens", "@luz-ds/core"] } },
});