A portable design system kit — the look of lazyway.io as tokens, ready-made CSS component classes, zero-dependency JS behaviors and charts, demo pages, and favicon tooling. Works on any stack: plain HTML, React, Astro, Next, SvelteKit.
If you are an AI agent (or in a hurry): read CHEATSHEET.md instead. It is a self-contained one-pager with the rules, the page skeleton, and every class name. This README is the full reference.
See it before reading: open demos/index.html in a browser. Every demo is plain HTML built only from the classes in this kit — view source and copy.
- Use the classes in
design/components.css. Do not write new CSS while a documented class does the job. If you must write CSS, use onlyvar(--…)tokens — never a hardcoded hex, px, or shadow. - One amber per page. Exactly one element per page may be amber: a kicker, or the active nav link, or one
.stat-highlight, or one.alert-warn. Status-dot badges and chart series colors do not count. - Flat backgrounds only: white (
--base) or solid brand blue (--surface-invert). No gradients, ever. - Two typefaces, one split: IBM Plex Sans for headings/body; IBM Plex Mono UPPERCASE for labels, kickers, tags, dates, nav links, code. Never mono for body text.
- Hairline borders (1px
--line), modest radii (6–13px), never pill-shaped. - Every page has favicons (§4). No exceptions — generate them if missing.
- Logo marks keep the structure: rounded blue tile, white linework, exactly one amber dot.
- Copy tone: plain, understated, British/Commonwealth spelling (
organise,centre). No hype.
Copy the design/ folder into your web root (served at /design/), then start every page from this skeleton:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page title — Site Name</title>
<meta name="description" content="One plain sentence.">
<!-- Favicons: required on every page (§4) -->
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<!-- The design system: tokens first, then component classes -->
<link rel="stylesheet" href="/design/styles.css">
<link rel="stylesheet" href="/design/components.css">
</head>
<body>
<!-- nav / hero / sections built from component classes (§5) -->
<!-- Only if the page uses modals, tabs, or pickers: -->
<script src="/design/components.js"></script>
<!-- Only if the page has charts: -->
<script src="/design/charts.js"></script>
</body>
</html>That's the whole setup. styles.css pulls Google Fonts + all token files; components.css provides every class in §5.
Which page shape? Pick the demo that matches your site and copy its structure:
| You are building | Copy from | Shape |
|---|---|---|
| A single-function app (shortener, checker, converter) | demos/tool.html |
Blue band + action card (the url-shortener pattern) |
| A product / marketing page | demos/landing.html |
Nav + hero + feature cards + stat band + blue CTA |
| A dashboard / admin | demos/dashboard.html |
Nav + filters + stat row + charts + table |
| A blog | demos/blog.html |
Post list + search + tags; article in .prose |
| Documentation | demos/docs.html |
Sidebar nav + .prose + tabs + alerts |
| Anything else | demos/components.html |
Every component, one page |
lazyway-io-design/
├── README.md ← full reference (this file)
├── CHEATSHEET.md ← one-page version for agents / small models
├── design/ ← THE SYSTEM — copy this folder verbatim
│ ├── styles.css ← entry point: fonts + tokens (link this first)
│ ├── components.css ← every component class (link this second)
│ ├── components.js ← modal, tabs, date picker, time picker (data-* auto-init)
│ ├── charts.js ← dependency-free SVG charts (window.lwCharts)
│ ├── tokens/ ← colors.css, typography.css, spacing.css, charts.css
│ └── assets/ ← logo-mark(.invert).svg, mesh-blue/white.svg, favicons/
├── components/ ← framework reference implementations
│ ├── react/ ← Modal, Tabs, DatePicker, TimePicker, DataTable, StatTile, Chart
│ └── *.astro, *.tsx ← Astro components (Nav, Footer, Hero, PostCard) + legacy React refs
├── layouts/ ← Astro page shells (Base.astro, Prose.astro)
├── starter/
│ ├── astro/ ← drop-in Astro 5 config for a new site
│ └── react/ ← drop-in React (Vite + TS) project for a new app
├── demos/ ← the demo pages + sample brand logos/favicons
├── scripts/ ← make-favicons.sh / .mjs (favicon generator)
├── .agents/skills/ ← authoritative portable Agent Skill
├── .claude/skills/ ← generated byte-identical Claude/Cline mirror
├── .clinerules/workflows/← generated thin Cline invocation shim
├── rules/shared/ ← always-on agent rule (shared body, installed per harness)
└── AGENT-INSTALL.md ← the agent-guided user-global install contract
The kit ships a portable Agent Skill (.agents/skills/lazyway-io-design/) that teaches coding agents to build pages with the system, plus an optional always-on rule (rules/shared/lazyway-io-design.md) for projects built entirely on it.
Installation is agent-guided only — there are no install scripts. Paste this into your coding agent:
Fetch https://raw.githubusercontent.com/jpbaking/lazyway-io-design/main/AGENT-INSTALL.md and follow its instructions exactly to install lazyway-io-design. Merge with — never blindly overwrite — any existing global instruction files, and report every file you created or changed.
The install is user-global—nothing is added to your repos. The agent
acquires the sources itself (git clone, repo zip, or gh) and installs
byte-identical skill copies to ~/.agents/skills/ (Codex and Pi),
~/.claude/skills/ (Claude Code),
~/.gemini/antigravity-cli/skills/ (Antigravity CLI), and
~/.cline/skills/ (Cline). It stores the self-gating rule once under
~/.agents/rules/ and points each selected host's global instruction file to
it. The full kit library lives separately at
~/.agents/resources/lazyway-io-design/, so it does not make one skill copy
different from the others.
Projects carry only what they use: the skill copies design/, favicons, and
selected demo/starter/component files into the project when UI work begins,
and those copies are committed. Invoke it portably by asking the agent to use
the lazyway-io-design skill, or let it activate from the request.
The standard set (all five files, at the web root):
| File | What |
|---|---|
favicon.svg |
the logo mark itself |
favicon.ico |
16+32px, for legacy agents |
apple-touch-icon.png |
180px, flattened on white |
icon-192.png, icon-512.png |
PWA/manifest sizes |
site.webmanifest |
minimal manifest pointing at the two icons |
Generate all of them from any square SVG mark:
./scripts/make-favicons.sh path/to/logo.svg path/to/webroot "Site Name"(First run installs sharp locally inside scripts/.) Then paste the three <link> lines from the §2 skeleton into <head>. A ready-made default set for the kit's own mark lives in design/assets/favicons/. If a site has no logo yet, start from design/assets/logo-mark.svg (or a demo logo in demos/assets/) and edit the white linework only — keep the blue tile + one amber dot.
Every class lives in design/components.css, numbered by section (§N in the file). Copy-paste HTML for each is in the demo pages — demos/components.html shows all of them.
| Component | Classes | CSS § | Demo |
|---|---|---|---|
| Page container | .container, .container-narrow |
1 | all |
| Kicker / headline / lead | .kicker, .headline, .lead |
2 | landing |
| Buttons | .btn + .btn-primary/-secondary/-quiet/-danger/-sm, .btn-on-blue, .btn-outline-on-blue |
3 | components |
| Nav bar | .nav-wrap > .nav > .brand + .nav-links > .nav-link(.active) |
4 | landing |
| Footer | .footer > .footer-inner > .foot-brand/.foot-links/.foot-copy |
5 | landing |
| Hero | .hero(.hero-invert) > .hero-mesh + .hero-inner |
6 | landing |
| Section head | .section > .section-head > .section-kicker + .section-title + .section-all |
7 | all |
| Grids | .grid-2/-3/-4, .grid-cards, .grid-stats |
8 | landing |
| Cards & tiles | .card > .card-meta/.card-title/.card-desc; a.tile |
9 | index |
| Stat (KPI) tiles | .stat(.stat-highlight) > .stat-value/.stat-label/.stat-delta(.up/.down) |
10 | dashboard |
| Tags & badges | .tag(.active), .tag-row; .badge-live/-pilot/-shipped/-planned/-danger |
11 | components |
| Data table | .table-wrap > .table (.num for numeric cells) |
12 | dashboard |
| Key/value list | .data-list > .data-row > .data-key + .data-value |
12 | tool |
| Forms | .field > .label + .input/.select/.textarea, .field-hint/.field-error, .search-input, .checkbox, .radio, .switch |
13 | components |
| Alerts | .alert + .alert-info/-success/-warn/-danger (.alert-title inside) |
14 | docs |
| Modal | <dialog class="modal"> + data-modal-open="#id" / data-modal-close |
15 | components |
| Tabs | [data-tabs] > .tab-list > .tab[data-tab-target] + .tab-panel |
16 | docs |
| Date picker | <input class="input" data-datepicker> → value YYYY-MM-DD |
17 | dashboard |
| Time picker | <input class="input" data-timepicker="15"> → value HH:MM |
17 | dashboard |
| Progress | .progress > .progress-fill(.warn/.danger), .progress-label |
18 | components |
| Prose (markdown) | .prose wraps rendered long-form HTML |
19 | blog, docs |
| Post card | .post-card > .post-meta/.post-title/.post-desc |
20 | blog |
| Chart chrome | .chart > .chart-title/.chart-sub + a target <div> |
21 | dashboard |
| Utilities | .mono-label, .muted, .center, .stack-1..4 |
22 | — |
| App shell | .app-page > .app-hero (.app-brand/.app-kicker/.app-title/.app-tagline) + .action-card (.input-row/.option-row/.result-block/.result-row/.result-link/.copy-btn) + .item-list (.item-card/.item-title/.item-sub/.item-meta) + .app-foot |
23 | tool |
| Spinner | .spinner (in a blue button), .spinner-dark |
24 | components |
Interactive behaviors (modal, tabs, pickers) need one <script src="/design/components.js"></script>; everything auto-initialises from the data-* attributes — no init code to write.
demos/tool.html is the template for tools like the url-shortener: hero + brand on a solid blue band, one white action card doing the app's single job, an optional list of previous items, one mono footer line. Use it whenever the site is one form.
<div class="chart">
<h3 class="chart-title">Requests per day</h3>
<p class="chart-sub">Last 14 days</p>
<div id="my-chart"></div>
</div>
<script src="/design/charts.js"></script>
<script>
lwCharts.bar("#my-chart", { labels: ["Q1","Q2"], series: [{ name: "Plan", values: [40,55] }] });
lwCharts.line("#my-chart", { labels: [...], series: [...], area: true }); // area only for 1 series
lwCharts.donut("#my-chart",{ slices: [{ label: "API", value: 41 }, ...], totalLabel: "total" });
lwCharts.sparkline("#el", { values: [3,5,4,7] }); // inside a .stat tile
</script>Chart rules (already enforced by the library — don't fight them):
- Series colors come from
--chart-1..5in order — never pass your own colors. The palette is CVD-validated; changing a slot requires re-validation. - Max 5 series/slices. A donut folds extras into a gray "Other"; bar/line will refuse — split into two charts.
- Never two y-axes. Two different scales → two charts.
- Legends, hover tooltips, and a screen-reader data table are automatic.
- Amber in charts is
--chart-3(a deepened, contrast-passing amber) and does not count toward the one-amber rule. - React: use
components/react/Chart.tsx(wraps the samecharts.js).
Load mermaid from a CDN and pass the standard theme (exact snippet in demos/docs.html): theme: "base" with themeVariables set to the token hexes (#F4F5FB surface, #12279E border, #16182E text, #61657F lines, #FDF6EC secondary). Put the diagram inside a .card.
- React (the starter):
starter/react/is a complete Vite + React 19 + TypeScript project whoseApp.tsximplements the single-function app shell. Steps (from the kit root;my-appis your new project):cp -r starter/react my-app && cd my-appmkdir -p public && cp -r ../design public/designcp ../design/assets/favicons/* public/(or generate your own set — §4)mkdir -p src/components && cp ../components/react/*.tsx src/components/npm install && npm run dev— then replace everyREPLACE-METhe React components (Modal,Tabs,DatePicker,TimePicker,DataTable,StatTile,Chart) are thin wrappers over the same classes. Don't load/design/components.jsin a React app (the components own that behavior); uncomment the/design/charts.jsline inindex.htmlwhen usingChart. In an existing React/Next app: link the two stylesheets in the root layout and copy the.tsxfiles in.
- Astro: use
starter/astro/(package.json, astro.config.mjs, tsconfig.json) +layouts/Base.astro(links both stylesheets, favicon block, meta/OG/JSON-LD) + the.astrocomponents. Steps:npm create astro@latest my-site -- --template minimal --no-install --no-git --typescript strict- copy
starter/astro/*over the generated config; setsiteandname - copy
design/→public/design/; copydesign/assets/favicons/*→public/(or generate — §4); copycomponents/,layouts/→src/ npm install && npm run dev
- Anything else: it's just two CSS links and two optional scripts. Rebuild markup with the same classes.
All tokens are :root custom properties, loaded by design/styles.css. Use these when a documented class genuinely doesn't cover the need.
| Token | Value | Use |
|---|---|---|
--brand-blue / --brand-blue-deep |
#12279E / #0B1660 |
primary / hover |
--accent-amber / --accent-amber-deep |
#D9821F / #9A5A12 |
THE accent (one per page) |
--ink / --muted |
#16182E / #61657F |
text / secondary text |
--line / --surface / --base |
#E5E7F1 / #F4F5FB / #FFFFFF |
hairlines / tint / page |
--amber-surface |
#FDF6EC |
faint amber wash |
--surface-invert |
= brand blue | blue sections |
--text-on-blue / --text-on-blue-dim |
white / 78% white | on blue |
--border-on-blue |
20% white | hairlines on blue |
--focus-wash |
rgba(18,39,158,0.08) |
focus glow |
--spinner-track |
30% white | spinner on blue |
--status-live/-pilot/-shipped/-planned |
blue/amber/#2E7D5B/#B7BBD0 |
badge dots |
--success / --success-surface |
#2E7D5B / #EDF6F1 |
success alerts |
--danger / --danger-surface |
#B3383D / #FBEFEF |
destructive/errors |
--font-sans (IBM Plex Sans), --font-mono (IBM Plex Mono); sizes --ui-h1/h2/body/small/kicker = 32/24/16/14/13px; weights --fw-regular/medium/semibold/bold; tracking --track-tight/snug (headings), --track-kicker/label (uppercase mono).
--space-1..8 = 8/12/18/26/40/48/64/88px; --radius-sm/md/lg/xl = 6/8/10/13px; --border-hair/accent = 1/3px; --shadow-card (barely there), --shadow-pop (popovers/modals/action card); --mesh-opacity-light/dark = 0.11/0.16.
--chart-1..5 (validated categorical: blue/green/amber/violet/rose), --chart-other (gray fold), --chart-seq-1..7 (blue ramp), --chart-div-neg/mid/pos, --chart-grid/axis/label/value. Do not eyeball-edit — the header comment explains the validation requirement.
- Swap the linework in
design/assets/logo-mark*.svg(keep blue tile + one amber dot); regenerate favicons (§4). - Astro only: update
layouts/Base.astro(JSON-LD, og:site_name, description),components/Nav.astro+Footer.astro(names, links, inline SVG marks),astro.config.mjssite,starter/astro/package.jsonname. - Different brand colors? Edit only
design/tokens/colors.css(--brand-blue,--accent-amber) — everything re-themes except: inline SVGs in Nav/Footer, the.svgassets, and mermaidthemeVariables(update by hand), and--chart-1..5(re-validate; seetokens/charts.cssheader).
- Keep this README,
CHEATSHEET.md, and the AGENTS.md tree in sync when tokens, classes, or rules change. - The only external runtime dependency is Google Fonts (top of
design/styles.css). Self-hosting the fonts: replace that@import, keep the family names.