--- title: "Design System — Onboarding + Shared Brand Tokens — Agent Skill for HTML Output" description: "Captures the user's brand identity once via a 10-question onboarding wizard (primary/accent HEX + heading + body Google Fonts + design style. Agent skill for Claude Code, Codex CLI, Gemini CLI, OpenClaw." --- # Design System — Onboarding + Shared Brand Tokens
:material-language-html5: Markdown to HTML :material-identifier: `design-system` :material-github: Source
Install: claude /plugin install markdown-html-skills
The design-system skill is the **shared brand owner** for the markdown-html plugin. Run its onboarding once. Every converter (`md-document`, `md-review`, `md-slides`) reads the resulting config via `config_loader.py` and applies the same 12 CSS custom properties to its output. Without this, conversions render with placeholder defaults — technically functional but unbranded. This skill ships exactly three Python tools: 1. **`onboard.py`** — interactive (or `--defaults` / `--set` / `--show` / `--reset`) wizard. 2. **`config_loader.py`** — importable customization loader with project > global > defaults precedence and `MARKDOWN_HTML_NO_CONFIG=1` bypass. 3. **`brand_palette_validator.py`** — WCAG-AA contrast checker + HSL palette deriver. All three are stdlib-only and contain no LLM calls (deterministic per Path-B discipline). ## When to invoke | Symptom | Action | |---|---| | User says "convert this markdown to HTML" for the first time in this workspace | Run `python3 markdown-html/skills/design-system/scripts/onboard.py` | | `~/.config/markdown-html/design-system.json` doesn't exist OR `setup_completed_at` is null | Refuse conversion, surface onboarding | | User wants per-repo brand override | `python3 .../onboard.py --scope project` | | User wants to change a single field non-interactively | `python3 .../onboard.py --set brand.primary=#FF6B35` | | User wants to reset and re-onboard | `python3 .../onboard.py --reset` then re-run | | User wants zero-touch defaults (CI, ephemeral session) | `python3 .../onboard.py --defaults` | | Headless / containerized run that should ignore saved config | `MARKDOWN_HTML_NO_CONFIG=1 ...` | ## Onboarding question set (10 questions) | # | Key | Choices / Validator | Default | |---|---|---|---| | 1 | `default_output_dir` | path; `os.access(parent, os.W_OK)` | `./markdown-html-out/` | | 2 | `brand.primary` | HEX `^#?[0-9a-fA-F]{6}$` | `#0A1628` | | 3 | `brand.accent` | HEX or blank (auto-derive) | derive from primary | | 4 | `typography.heading_font` | Google Font name (12 safe defaults) | `Inter` | | 5 | `typography.body_font` | Google Font name | `Inter` | | 6 | `design_style` | `editorial / technical / minimal / playful` | `technical` | | 7 | `code_theme` | `light / dark / auto` | `auto` | | 8 | `toc.behavior` | `sticky-sidebar / collapsible-top / inline / none` | `sticky-sidebar` | | 9 | `company_name` | string (may be empty) | `""` | | 10 | `logo_url` | URL or empty (base64-embedded at render) | `""` | ## Hard rules 1. **WCAG AA body-text contrast must pass.** `brand_palette_validator.validate()` runs after every change. Body text on bg must reach 4.5:1; link on bg must reach 4.5:1. If either fails, `onboard.py` refuses to save (exit code 4) and tells the user to pick a darker primary, blank `brand.bg`/`brand.text` to let derivation pick a safe pair, or override `brand.text` directly. Canon: WCAG 2.2 §1.4.3. 2. **Output directory must be writable.** `onboard.py` walks up the path to find an existing ancestor and checks `os.W_OK`. Empty or unwritable path → exit code 3. The orchestrator's `output_path_resolver.py` honors the same rule per-conversion. 3. **Customization must change behavior, not sit as decoration.** Every consumer (md-document, md-review, md-slides) must read the config and render differently when the user changes `design_style`, `brand.primary`, `code_theme`, or `toc.behavior`. Decorative-only fields fail the design discipline. 4. **Precedence is fixed.** Project > global > defaults. The deep-merge preserves nested keys (e.g. you can override `brand.primary` in a project config without losing `typography.heading_font` from global). 5. **Bypass env exists for a reason.** `MARKDOWN_HTML_NO_CONFIG=1` is for headless CI, ephemeral test containers, and the autoresearch-style evaluator loops. Never set it silently for an interactive user. ## Derived 12-token palette Once the user's brand is captured, `brand_palette_validator.derive_palette()` produces 12 CSS custom properties stored under `derived_palette` in the same config file. Every converter inlines these into its `