Get started
Working with AI
What an AI coding agent can read, how to check what it wrote, and the accessibility reviewer you can give it.
Give your agent LUZ
LUZ is plain HTML and CSS, so an AI coding agent can write it when it knows which class names exist. Paste this prompt into your agent.
It points the agent at the files below and asks it to run the checker on what it wrote. What the checker does not do is in What this does not do.
Use LUZ for the markup. Read https://nandovejer.github.io/luz/llms.txt first and use only the classes in each package's classes.json. After you write HTML or CSS, run `npx @luz-ds/check@0.3.0` on the files and fix every finding before you answer.
Start a site
For a new site, or to move one to LUZ, give an LLM the longer prompt. It asks you about the project in short rounds, shows you the plan, and writes nothing until you say "go".
For one kind of site, its page has a prompt made for it and the recipes of its parts: a landing page, a blog or a newspaper, a web application or a documentation site.
What an agent can read
Every file below is written by the build of this site from the pages you are reading, so an agent and a person get the same facts. Give an agent the URL of llms.txt: it lists the rest.
| File | URL | What it is | Made by |
|---|---|---|---|
llms.txt | https://nandovejer.github.io/luz/llms.txt | The index of the documentation for an agent: every page with its description, in reading order. | scripts/docs-site/ai.js, in the pages build |
llms-full.txt | https://nandovejer.github.io/luz/llms-full.txt | The same pages as plain text: the markup, the variants, when to use and the keyboard of each one. | scripts/docs-site/ai.js, in the pages build |
prompts/start.txt | https://nandovejer.github.io/luz/prompts/start.txt | The general prompt: paste it into any LLM to start a site or move one to LUZ; it asks about the project in rounds before it writes anything. | scripts/docs-site/prompts.js, in the pages build |
prompts/landing.txt | https://nandovejer.github.io/luz/prompts/landing.txt | The prompt for a landing page: the general one, with the parts and classes of a landing page. | scripts/docs-site/prompts.js, in the pages build |
prompts/blog.txt | https://nandovejer.github.io/luz/prompts/blog.txt | The prompt for a blog or a newspaper, on any CMS: the general one, with the parts and classes of a blog. | scripts/docs-site/prompts.js, in the pages build |
prompts/app.txt | https://nandovejer.github.io/luz/prompts/app.txt | The prompt for a web application: the general one, with the parts and classes of an app. | scripts/docs-site/prompts.js, in the pages build |
prompts/docs.txt | https://nandovejer.github.io/luz/prompts/docs.txt | The prompt for a documentation site: the general one, with the parts and classes of a documentation site. | scripts/docs-site/prompts.js, in the pages build |
examples/<area>/<slug>.html | https://nandovejer.github.io/luz/examples/components/button.html | The markup of one component or pattern page, styled by LUZ; every page that has a markup section has its own under /examples/. | scripts/docs-site/ai.js, in the pages build |
manifests/tokens/classes.json | https://nandovejer.github.io/luz/manifests/tokens/classes.json | Every class, custom property and attribute hook of the package: the names an agent may use. Package: @luz-ds/tokens. | scripts/ai-manifests.js, in the build of each package |
manifests/core/classes.json | https://nandovejer.github.io/luz/manifests/core/classes.json | Every class, custom property and attribute hook of the package: the names an agent may use. Package: @luz-ds/core. | scripts/ai-manifests.js, in the build of each package |
manifests/core/custom-elements.json | https://nandovejer.github.io/luz/manifests/core/custom-elements.json | The custom elements the package defines or declares, with the attributes each one reads. Package: @luz-ds/core. | scripts/ai-manifests.js, in the build of each package |
manifests/catalog/classes.json | https://nandovejer.github.io/luz/manifests/catalog/classes.json | Every class, custom property and attribute hook of the package: the names an agent may use. Package: @luz-ds/catalog. | scripts/ai-manifests.js, in the build of each package |
manifests/catalog/custom-elements.json | https://nandovejer.github.io/luz/manifests/catalog/custom-elements.json | The custom elements the package defines or declares, with the attributes each one reads. Package: @luz-ds/catalog. | scripts/ai-manifests.js, in the build of each package |
Check what an agent wrote
npx @luz-ds/check@0.3.0 reads the HTML and CSS of a project, offline, and says which LUZ classes, elements, load order and color tokens are wrong. It reads files and writes nothing.
It needs Node 20 or later.
npx @luz-ds/check@0.3.0
npx @luz-ds/check@0.3.0 index.html css/site.css
npx @luz-ds/check@0.3.0 src --json
Say npx @luz-ds/check@0.3.0, with the scope. A path is a file, a folder (read recursively: .html, .htm and .css) or a quoted glob such as "src/**/*.html".
With no path it reads the current folder.
| Option | What it does |
|---|---|
--json | Prints the report as JSON |
--strict | Fails on a warning as well as on a finding that blocks |
--config <file> | Reads this configuration instead of luz-check.json |
--theme <file.css> | Names a stylesheet that sets color tokens, whose colors are measured; repeat it for each file, in the order the page loads them |
--ext <list> | Also reads these extensions as HTML, such as .astro,.vue |
--version | Prints the version |
--help | Shows the options and the limits of the command |
A luz-check.json in the folder you run it from sets ignore, rules, themes and strict. A rule that warns can be switched off there; a rule that blocks is silenced only with ignore, and the summary says how many findings were ignored and how many rules are off.
Exit codes
| Code | Meaning |
|---|---|
0 | Nothing blocks. Warnings may still have been printed |
1 | At least one finding blocks, or, with --strict, a warning was found |
2 | The command was used wrongly, a configuration cannot be read, or there is nothing to read |
What it prints
One line for each finding, in the order of the files and lines: a mark (✗ blocks, ! warns), the place, the rule and one sentence that says what is wrong and what to do. A color token finding is followed by the lines where the colors were set.
The last line counts what it found in the files it read.
✗ index.html:14:8 class/unknown .luz-btn-primary is not a LUZ class; the closest is .luz-btn--primary
✗ index.html:22:5 load/package-missing <luz-announcement> is in @luz-ds/catalog/components/announcement.css; add it after core
! site.css:8:3 css/literal-colour color is set to #1a5fb4; use a token, such as var(--luz-brand-fill)
✗ theme.css:3:3 theme/contrast light: --luz-text-secondary on surface-page is 2.8:1, needs 4.5:1 (text); change one of the two
--luz-text-secondary: #999999 is set in theme.css:3
✗ 3 blocking and 1 warning in 3 files (manifests: @luz-ds/core 0.1.0 from node_modules)
The (manifests: …) part says which LUZ version the names were checked against, and from where: your node_modules or the copy that ships with the command.
The JSON report
--json prints one object with these fields. A field that is not in this table is not part of the report.
| Field | Type | What it is |
|---|---|---|
tool | string | Always @luz-ds/check |
version | string | The version of the checker |
ok | boolean | True when nothing blocks and, with --strict, nothing warns. It is not an accessibility result |
files | list of strings | The files it read, sorted |
manifests | list of objects | The LUZ data it checked against, one entry for each package |
manifests[].package | string | The package, such as @luz-ds/core |
manifests[].version | string | The version of that package's data |
manifests[].source | string | node_modules when your project has the package, bundled when the command's own copy was used |
summary | object | The counts of the run |
summary.files | number | How many files it read |
summary.blocking | number | How many findings block |
summary.warnings | number | How many findings warn |
summary.skipped | number | How many paths it did not read |
summary.ignored | number | How many findings the configuration's ignore removed |
summary.off | number | How many rules the configuration switched off |
summary.truncated | boolean | True when the list of findings was cut; the counts above are complete |
config | object | The configuration that was read |
config.file | string or null | The configuration file, or null when there was none |
config.off | list of strings | The rules the configuration switched off |
findings | list of objects | What it found, in the order of files and lines |
findings[].rule | string | The rule, such as class/unknown |
findings[].severity | string | block or warn |
findings[].file | string | The path, relative to where you ran the command |
findings[].line | number or null | The line, when the finding has a place in the file |
findings[].column | number or null | The column, when the finding has a place in the file |
findings[].message | string | The sentence that says what is wrong and what to do, in English |
findings[].suggestion | string or null | The closest LUZ name, when there is one close enough |
findings[].docs | string | The URL of the page that explains the rule |
findings[].details | object | Only on some findings. A color token finding names the mode, the colors, the ratio and the places where the colors were set |
skipped | list of objects | The paths it did not read |
skipped[].file | string | The path |
skipped[].reason | string | Why: a link, a file that is not a regular file, a file over 1 MiB, one it could not open, or a limit of the run |
The same two things hold for the text and for the JSON: a path or a name that is not made of ordinary characters is written escaped, and the command never quotes the text of your files.
What check blocks and what it warns about
Each rule has an id such as class/unknown, shown in every finding. A rule that blocks makes the exit code 1.
A rule that warns does not, unless you pass --strict. The two tables are written from the list of rules the command has.
Catalog 0.3 renamed its classes, elements, files and chunks, and the old names do nothing. names/renamed finds an old class, an old tag, a link to an old component file or an old chunk, and gives the new name. It says nothing on a page that links catalog 0.0 to 0.2, where the old names are the right ones.
| Rule | What it finds | Severity |
|---|---|---|
a11y/slider-no-name | A <luz-theme-time variant="bar"> with no data-luz-label-scrub on it or an ancestor, so its slider has no accessible name: the script writes no English text. | block |
class/unknown | A class starting with luz- that no classes.json lists, or an o- or u- class a typo away from one LUZ has. | block |
element/unknown | A <luz-*> tag that no custom-elements.json declares and that is not the block of a class. | block |
element/unknown-attribute | An attribute on a LUZ element that its custom-elements.json does not declare; global, aria-*, data-* and framework attributes are left alone. | block |
load/layer-statement | A <style> that declares the layers after the first LUZ stylesheet, when the order of the layers is already fixed. | block |
load/package-missing | A class or tag from a package whose stylesheet the page does not load, such as a site class with no site stylesheet. | block |
load/tokens-missing | A page that links core, catalog or a package for one kind of site and does not link @luz-ds/tokens. | block |
misuse/host-element | A class on an element it is not made for, such as a button class on a <div>; classes.json lists each class's elements. | block |
misuse/modifier-alone | A modifier such as luz-btn--primary on an element that does not also have the class it modifies. | block |
names/renamed | A class, element or file of catalog that has its old name from before 0.3 (luz-landing-hero, <luz-mode>, <luz-color-mode>, luz-mode-btn, landing-hero.css): the finding gives the new name. | block |
parse/error | A file that ends inside a comment, tag, string or brace, so the rest of it cannot be read. | block |
theme/contrast | A foreground and background pair in your colour token files that is under its WCAG contrast minimum in light, dark or high contrast. | block |
theme/unreadable | A LUZ colour token, in a file that sets colour tokens, whose value cannot be read as an sRGB colour, so its contrast cannot be measured. | block |
| Rule | What it finds | Severity |
|---|---|---|
a11y/link-no-href | An <a> that has a class made for links and no href, so it is not a link and a keyboard cannot reach it. | warn |
a11y/role-on-generic | A widget role, or onclick, on a <div> or <span> where a native element such as <button> does the same with the keyboard. | warn |
attr/unknown-hook | A data-luz-* attribute on an element that no package reads: usually a misspelling of one it does. | warn |
class/reserved-prefix | A stylesheet of yours that defines a class starting with luz-, the prefix LUZ keeps for itself. | warn |
class/unknown-in-css | A selector in your stylesheet that names a .luz-* class no classes.json lists. | warn |
css/important | !important in a declaration, in a custom property too; a rule outside the layers already wins over LUZ. | warn |
css/knob-in-px | --luz-type-base or --luz-space-unit set in px, which ignores the text size the reader chose. | warn |
css/literal-colour | A colour written out in a declaration (hex, rgb, hsl and the other functions) instead of a token. | warn |
css/literal-size | A px or rem length in font-size, margin, padding, gap or border-radius instead of a space, text or radius token. | warn |
css/private-token | A --luz-* property that one component sets for itself, set or read outside that component. | warn |
css/unknown-token | A --luz-* property that no package declares, set or read: usually a misspelling. | warn |
file/skipped | A path the check did not read, such as a symlink or a file that is too large; the finding says why. | warn |
load/no-stylesheet | Checked files that load no LUZ stylesheet at all, so a class can only be told from your own by its name. | warn |
load/order | LUZ stylesheets linked out of order; the order is tokens, core, catalog, then the package for your kind of site. | warn |
misuse/part-orphan | A part such as luz-card__title with no element of its block around it. | warn |
misuse/state-class | An is-* class that LUZ's own script sets, written in the markup. | warn |
theme/advisory | A note on how the colour check ran: the file order it used, a token no colour mode can place, a wide-gamut colour it does not measure, or sets of colour tokens past its limit. | warn |
The accessibility reviewer
The same package prints an agent definition for an AI coding assistant: a reviewer that reads the source of a page and gives advice on accessibility. Copy it into your project.
npx @luz-ds/check@0.3.0 agent > .claude/agents/luz-accessibility-reviewer.md
- It overwrites. The
>replaces a file that is already there. Use another path, or put your copy under version control first, if you edited it. - It is read-only. It has three tools,
Read,GrepandGlob: it reads, searches and lists files, and writes nothing. Read-only does not mean harmless, becauseReadhas no path limit. Read the file before you use it, and review any change to it as you would code. - It gives advice. It reviews source against WCAG 2.2 level AA and EN 301 549, and labels each finding as confirmed in the source or as one that needs a browser or assistive technology. Its findings are suggestions from reading code, not test results.
- It does not use assistive technology. It does not open a page or run a script, and it does not look at how a page renders, so it does not replace a test with a keyboard and a screen reader.
The file is also in the package as @luz-ds/check/agents/accessibility.md, for a build step that copies it.
What this does not do
A run with no findings says what the checker did not find in the files it read. It is not a test of the page.
- It does not run a browser. It reads files, so anything that depends on how a page renders, such as layout, focus order or the colors a browser works out, is not seen.
- Contrast is measured for your tokens only. It measures the
--luz-*colors you set in your own color tokens file. A color written straight into a rule is not measured against anything. How the pairs are read is on Contrast check. - A class built at run time is not seen. A name put together in a script, such as
"luz-" + name, or written by a template, is not in the file, so it is not checked. - It does not check names, labels, alt text, headings and language. Whether a button has a name, an image a useful alternative, the headings an order and the page a language is not read, and a run with no findings says nothing about them.
- It does not replace testing with a screen reader. No assistive technology is used, by the command or by the reviewer. What has not been tested is listed in the accessibility statement.
- It does not call a network. It runs offline, with the data in your
node_modulesor in its own package. - Discovery by
llms.txtis by link. This site is not at the root of a host, so an agent does not find the file by itself: give it the URL, as the prompt above does. - Exit code 0 is not an accessibility result. It says that no rule above blocks in the files that were read.
- The messages are in English. They are not translated, whatever the language of the page.