@alis-build/design-system
The AlisX design system, written for coding agents (Claude Code, Codex) building product screens in any framework: Svelte, Vue, or plain HTML.
Agents: start at rules/README.md, then rules/quick-start.md.
| Path | What it is |
|---|---|
alisx.css | Every token as a custom property, the type classes (.text-*), the product base (resets * and body) and the .app-* component classes. |
tokens.css | Every token as a custom property, light and dark, and nothing else: for an app with its own component library. |
themes.json | Every product role resolved to a literal, per theme (light, dark): for code that needs values, such as a component library's theme or a diagram renderer. |
fonts.css | Loads Nunito Sans and Material Symbols Outlined from Google Fonts. For prototypes; self-host the fonts in production. |
components/ | One guide per component: the markup to write, the behaviour you supply, its classes. components/README.md has a finished screen skeleton. |
rules/ | What to build and why: one file per decision, plus a quick start and the known gaps. |
tokens.json | The source of every value: each colour tagged palette, product or narrative, each product role with a value per theme. References such as {cyan-600} are left in; read tokens.css or themes.json rather than resolving them. For code written before themes, each product role also keeps a value (its light one), and each shadow its light literal as value beside values per theme. |
marks.json | The capability marks (alis_*): construction, stroke grade by size, geometry, in canonical order. |
words.json | One name per thing: what each means, what it replaces, and the code word where it differs. Read it wherever words are written for people, an AI's instructions included (rules/words.md). |
email/ | The email shell (shell.html), its blocks (blocks.html) and plain-text twin (shell.txt), with every colour written in from the product roles, and the wordmark as PNG (rules/email.md). |
assets/Brand/ | The Alis mark, product lockups, app icon and favicon. |
stylelint.mjs | The style check that restates rules/styling.md, as a stylelint config your own extends. |
classes.json | Every class and custom property alisx.css defines in this version. |
check.mjs | alisx-check: runs the three checks below in your build. |
pnpm add -E @alis-build/design-system
import '@alis-build/design-system/fonts.css' // prototypes only
import '@alis-build/design-system/alisx.css'
import tokens from '@alis-build/design-system/tokens.json' with { type: 'json' }
Light and dark. A page is light unless it asks: set data-theme="dark", or
"system" to follow the operating system, on <html> or on any element
(rules/themes.md). Choosing the theme is your app's job.
Keep an app in line
Three things, set up once, so the next change stays consistent whoever makes it.
1. Point your agents at the rules. In your app's CLAUDE.md or AGENTS.md:
The UI is built from @alis-build/design-system. Before changing a screen, read
node_modules/@alis-build/design-system/rules/README.md and follow its "Where to
start". The rules there are the version this app has installed.
Write the path from where that file sits: if your package.json is in a
subfolder, the rules are under that folder's node_modules. Never copy the
rules into your repo: a copy stops moving with the version.
2. Check your styles. Add stylelint (16.11 or later) as a dev dependency,
with postcss-html if you have Svelte, Vue, HTML or templ files (it reads
their <style> blocks and static style="" attributes) and postcss-scss if
any block is lang="scss" (without it those blocks are skipped, silently).
Then in stylelint.config.js:
export default {
extends: ['@alis-build/design-system/stylelint'],
overrides: [{ files: ['**/*.svelte', '**/*.vue', '**/*.html', '**/*.templ'], customSyntax: 'postcss-html' }],
}
-
A rule you set under the same name replaces the system's: stylelint never merges two lists. To add to one, carry the system's part along. The config exports its lists (
refusedValues,allowedValues,refusedProperties,refusedSelectors) and two builders:import { refuse, refusedValues } from '@alis-build/design-system/stylelint' export default { extends: ['@alis-build/design-system/stylelint'], rules: { 'declaration-property-value-disallowed-list': refuse([ ...refusedValues, { properties: 'cursor', values: ['wait'], message: 'A busy control shows its own spinner.' }, ]), }, } -
A file that keeps one part of a rule picks that part by name:
refused.literalColour,refused.blend,refused.spacing,allowed.radius,allowed.shadow. Never by its message or its place in a list. A name a release changes then stops the check instead of thinning the rule.import { refuse, refused } from '@alis-build/design-system/stylelint' // in overrides: the one file where a client's theme meets the system { files: ['**/theme-bridge.css'], rules: { 'declaration-property-value-disallowed-list': refuse([refused.literalColour]), } } -
When nothing in
rules/styling.mdfits, the system has a gap. Say so on the line above, by rule and with a reason, and name it when you hand the work back./* stylelint-disable-next-line declaration-no-important -- GAP: a hidden element stays hidden */ [hidden] { display: none !important; }A disable is itself reported when it names no rule (it would silence every rule on that line), gives no reason, or silences nothing.
-
It reads what is written in CSS. A value held in a Sass variable or a mixin, or a literal behind a custom property of your own (
--pad: 13px), is not seen, and nor is a bound style (:style,style={ … }). A templcsscomponent can't be read at all, soalisx-check stylesstops on one.
3. Run the checks in your build.
"scripts": {
"lint:names": "alisx-check names src",
"lint:styles": "alisx-check styles \"src/**/*.{css,svelte,vue}\"",
"lint:styles:record": "alisx-check styles \"src/**/*.{css,svelte,vue}\" --update",
"lint:words": "alisx-check words src",
"lint:words:record": "alisx-check words src --update"
}
alisx-check namesfails on a system class (app-*,text-*) or a custom property this version doesn't have: a misspelling, or a name a release removed. Pass--prefix app-if your app hastext-*utilities of its own. For a property that arrives from outside your source, name the file that declares it:--declared e2e/fixtures/theme.jsonreads any text file or folder (a stylesheet inside JSON, server code) and counts its names as known by their exact spelling. The path must exist wherever the build runs: an image build may copy only the app. Where no file declares it (a library sets it from script), pass--allowwith the exact name; a pattern such as--allow '--brand-*'lets a misspelling by.alisx-check stylesis a gate: any violation fails. An app with a backlog records it once (lint:styles:record) and commitsalisx-style-baseline.json; from then on it is a ratchet, failing only when a file gains violations of a rule. The baseline holds the package version it was recorded against: after an upgrade the check says so, because the rules may have changed. Re-record only after an intentional change, never to clear a failure, and say in the commit why the number moved.alisx-check wordsfails on a namewords.jsonhas retired ("idea" for a project, "Access and ownership" for Sharing) in text a person reads: text between tags, and quoted text that holds a space or starts with a capital. In a templ file the Go is code, read as script is: outside a component, itsif,for,switchandcaselines, and an@Component(…)call's arguments. Its baseline,alisx-words-baseline.json, works as the styles one does. A use that must stay is marked on its line or the line above:alisx-words-ignore -- the reason.alisx-check words --text <file>reads a words file instead of markup: the plain text a terminal app writes out of everything it says, in blocks headed== kind place · area, then onefield<TAB>textline per thing read.codeandaliaslines, backticked spans (one a wrapped line carries on), printf verbs (%s) and placeholders (<id>) are code. A line that carries on its field's line above joins it, so a name the wrap breaks still counts. The baseline counts each place, so the block that gains a retired name is the one that fails. The file is generated, so there is no ignore: change the words at their source.- In both, a retired name written with a capital ("Read only") is a label, and counts only with its capital: "read only the parts that matter" is English.
- Each stops with exit code 2, never passes, when it could not run as set up: a rule stylelint rejected, a file that didn't parse, no files matched.
Pin an exact version (pnpm add -E, npm install -E), so the rules, the
classes and the checks move together.
The package root has no JavaScript entry: import the files above by path.
Pin an exact version from a CDN with
https://cdn.jsdelivr.net/npm/@alis-build/design-system@<version>/alisx.css.
Licensed under Apache-2.0. The Alis names and marks are trademarks of Alis
Exchange and are not licensed; see NOTICE.