# How LUZ is organised

Packages that depend in one direction, CSS in cascade layers inside one called luz, and one optional script.

### Package boundaries

Each package depends on the ones above it in this list, never on the ones below.

The name stands for Light, Usable & Zero-dependency: light in weight (plain CSS and ES modules, nothing to build for the people who use it), usable by everyone (native HTML, three color modes, tested for accessibility), and with zero runtime dependencies.

Package dependency chain
```text
@luz-ds/tokens   -> primitive and semantic tokens (standalone)
      ↓ required by
@luz-ds/core     -> components, objects, bare-element styles, optional JS
      ↓ required by
@luz-ds/catalog     -> search, cookie-consent, theme-time, lang-switch, submenu, announcement, and the patterns of a landing page, a blog or newspaper, a web application and a documentation site
```

- The catalog needs core and tokens; core needs tokens; tokens needs nothing.
- Tokens, core and catalog share one version. There are no packages for one kind of site: the patterns of a landing page, a blog, an application or a documentation site are components of the catalog, one file each.
- The packages say so as peer dependencies. The core and catalog scripts warn in the console when a level is missing.

The documentation follows the packages, with one area more: [Kinds of site](https://nandovejer.github.io/luz/docs/kinds/) puts together, for each kind, the prompt for an LLM and the recipes of its parts, made of the packages above and of the patterns of that kind in the catalog.

### What each package needs

Find the part you want, and install the package of its row and the ones below it.

Which package holds each part of a page

- Color modes, semantic tokens, ET Book: `--luz-*`, `data-theme`: tokens: Nothing else is needed
- Reset and styles of bare HTML elements: `h1`, `p`, `a`, `ul`…: core: In `base.css` and `luz.css`
- Components: `.luz-btn`, `.luz-field`, `.luz-card`, `.luz-table`, `.luz-tabs`…: core: Also one file each, in `components/`
- Modal: `.luz-modal` on a `<dialog>`: core: The script is a fallback for browsers without `command` and `commandfor`
- Layout objects: `.o-container`, `.o-grid`, `.o-stack`, `.o-media`: core: Spacing comes from the space tokens
- An app layout with a sidebar column: `.o-shell`, `.o-main`: core: A plain `<footer>` as a direct child of `.o-shell` goes across the bottom; its look is yours, or a kind of site's footer
- A page with no sidebar, a sticky bar, an action group: `.o-page`, `.o-sticky`, `.o-actions`: core: Instead of `.o-shell`, for a page with no sidebar: [the page and its bars](https://nandovejer.github.io/luz/docs/components/layout/#objects-page)
- Color mode: Auto, light, dark and high contrast: `<luz-theme-mode>`: core: `luz.js` loads its script, `theme-mode.js`, when the page has one; without it the radios still work and Auto follows the system: [Color mode](https://nandovejer.github.io/luz/docs/components/theme-mode/)
- Skip link, band, image frame: `.luz-skip-link`, `.luz-band`, `.luz-image`: core: The image frame keeps a ratio and crops the picture to it; with no picture it is a flat surface
- The header, hero, sections and footer of a landing page: `.luz-site-header`, `.luz-hero`, `.luz-main`, `.luz-section`, `.luz-stat`, `.luz-site-footer`, `.luz-glow`: catalog: One file each, `components/<block>.css`. On core's objects (`.o-page`, `.o-sticky`, `.o-container`, `.o-grid--split`) and the catalog's navbar. A feature grid is `ul.o-grid.o-grid--split`, core's
- The header, page header, article header, table of contents and footer of a blog or a newspaper: `.luz-site-header` (`--publication`), `.luz-masthead`, `.luz-page-header`, `.luz-article-header`, `.luz-article`, `.luz-toc`, `.luz-site-footer`: catalog: One file each, `components/<block>.css`; the table of contents is in `article.css`. On core's objects and the catalog's navbar. Post cards, listings and the author box are core's card: see [Blog or newspaper](https://nandovejer.github.io/luz/docs/kinds/blog/)
- The header and the dashboard of a web application: `.luz-app-bar` (`__product`, `__actions`, `__account`), `.luz-dashboard` (`__head`, `__title`, `__panel`): catalog: One file each, `components/app-bar.css` and `components/dashboard.css`. On core's shell, sticky bar, grid and card, and the catalog's sidebar. No script. See [App header](https://nandovejer.github.io/luz/docs/catalog/app-bar/) and [Dashboard](https://nandovejer.github.io/luz/docs/catalog/dashboard/)
- The shell, page head, rail, example, do and don't, pager and page foot of a documentation site: `.luz-docs-layout`, `.luz-doc-header`, `.luz-page-rail`, `.luz-example`, `.luz-do-dont`, `.luz-pager`, `.luz-page-footer`: catalog: One file each, `components/<block>.css`. On core's shell, code block and tables, and the catalog's sidebar. No script. See [Shell](https://nandovejer.github.io/luz/docs/catalog/docs-layout/)
- The links of a header: `.luz-navbar`: catalog: One file, `components/navbar.css`. A popover panel at 768 px and below, opened by a `.luz-nav-toggle`, with no script. `luz.css` does not include it
- A sidebar, its brand and its menu button: `.luz-sidebar` (`__brand`, `__logo`, `__brand-name`, `__version`), `.luz-nav-toggle`: catalog: One file, `components/sidebar.css`. The drawer is a native popover and needs no script. `catalog.js` loads `sidebar.js`, the scroll spy, when the page has a sidebar; a page that loads only core has no scroll spy
- A command in a framed block: `.luz-terminal`: catalog: One file, `components/terminal.css`. No script of its own: the copy button is core's
- A timeline of dated events: `<luz-timeline>`, `.luz-timeline`: catalog: `catalog.js` loads its script, `timeline.js`, when the page has one; without it the timeline is a list
- Submenu, announcement: `<luz-submenu>`, `<luz-announcement>`: catalog: Tags with no script and no class: the submenu is a native `<details>` in a header's links, the announcement a `<section>`
- Cookie consent: `<luz-cookie-consent>`, `.luz-cookie-consent`: catalog: `catalog.js` loads its script, `cookie-consent.js`, when the page has one; a band fixed at the bottom of the window; without the script it stays hidden
- Color mode with the time of day, and the slider of the day: `<luz-theme-time>`: catalog: `catalog.js` loads its script, `theme-time.js`, when the page has one, and `theme-time-bar.js` for a `variant="bar"`; without them core's radios still work and Auto follows the system
- Language switch: `<luz-lang-switch>`: catalog: No script
- Site search: `<luz-search>`, `.luz-search`: catalog: Needs the script (`catalog.js`, which loads `search.js` when the page has one) and an index file; without them it is a link
- Print styles: Nothing to add: core: In `luz.css`; catalog adds its own print rules
- A zone in a page that already has a design: `data-luz`: core: `luz.conditional.css`, instead of `luz.css`
- Checking what you or an AI agent wrote: `npx @luz-ds/check@0.3.0`, the `luz-check` command: check: A tool with no CSS and no layer, and no other package needed: it reads your HTML and CSS offline. See [Working with AI](https://nandovejer.github.io/luz/docs/get-started/ai/)

- **Two ways to lay out a page, both core's.** `.o-shell` with `.o-main`: a sidebar column beside the main area. `.o-page`: a header, the content and a footer in one column. Pick one for the grid of the page: the first when it has a sidebar, the second when it has none. A landing or CMS header and footer work with both; these docs use `.o-shell`.
- The catalog needs core, and core needs tokens, so a row at a level also needs every package under it. The patterns of a landing page, a blog, an app and a documentation site are catalog components, so they need core and tokens too.

### CSS layers

Core's CSS comes in native cascade layers (`@layer`), in ITCSS order, inside one container layer called `luz`. The six below are the ones that hold its rules; [the layers page](https://nandovejer.github.io/luz/docs/get-started/layers/) lists them all, with tokens, catalog and print.

A later layer wins over an earlier one, and any CSS of yours that is not in a layer wins over all of LUZ, with no `!important`. [How to order and override them](https://nandovejer.github.io/luz/docs/get-started/layers/).

Core cascade layers
```text
packages/core/src/
  settings/_settings.css      luz.settings    z-index, layout widths, field text
  generic/_reset.css          luz.generic     the reset
  elements/*.css              luz.elements    bare HTML elements, zero specificity
  objects/_objects.css        luz.objects     layout: .o-*
  components/*.css            luz.components  the components: .luz-*
  utilities/_utilities.css    luz.utilities   single-purpose helpers: .u-*
```

- The components layer holds 29 components.
- `dist/luz.css` holds these six layers and `luz.print`. Tokens are in `luz.tokens`, catalog, with the navbar, the sidebar, the terminal, the timeline and the patterns of a kind of site, in `luz.catalog`; `luz.themes` is empty, kept for a look of your own. For a page that needs a few components only, load `dist/base.css` (settings, reset and elements), then `dist/components/<name>.css` for each one.
- Rules for bare elements are wrapped in `:where()`, so any class wins over them.

### The script

One optional entry point, `luz.js`, starts every behaviour on the page: color mode, copy buttons, dialogs, highlighting, players and tabs.

Load the core script
```html
<script type="module" src="https://cdn.jsdelivr.net/npm/@luz-ds/core@0.3.0/dist/luz.min.js"></script>
```

To load less, import one module and call its `init` function. See [JavaScript modules](https://nandovejer.github.io/luz/docs/get-started/javascript/).

### Minimal page

Minimal page skeleton
```html
<!doctype html>
<html lang="en" data-theme="light">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Sign up – Acme</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/tokens@0.3.0/dist/luz-tokens.min.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/core@0.3.0/dist/luz.min.css">
</head>
<body>
  <main class="o-container">
    <h1>Sign up</h1>
    <form action="/signup/" method="post">
      <div class="luz-field">
        <label class="luz-field__label" for="email">Email</label>
        <input class="luz-input" id="email" name="email" type="email" autocomplete="email" required>
      </div>
      <button class="luz-btn luz-btn--primary" type="submit">Create account</button>
    </form>
  </main>
</body>
</html>
```
