Design System — Onboarding + Shared Brand Tokens
SkillMediaAnswer ten questions about your brand once, and every document your AI converts to HTML will follow the same look. It stores your primary and accent colors, heading and body fonts, a design style, and optional logo or company details. It also checks that body text and links stay readable against the colors you picked.
Available today. Use it from your connected AI after setup.
No other account needed.
After adding it, answer the ten setup questions to record your colors, fonts, and style. Then convert any document to HTML and it will come out matching your brand.
Then ask your AI: use the Design System — Onboarding + Shared Brand Tokens skill
What your AI can do with it
- Set up brand colors, fonts, and style through a ten-question walkthrough
- Apply your primary and accent colors to converted documents
- Use your chosen Google Fonts for headings and body text
- Match one of four design styles: editorial, technical, minimal, or playful
- Check that body text and links are readable against your colors
- Remember your default output folder, syntax theme, and table-of-contents preference
What this skill tells your AI
The instructions your AI receives, as published by affaan-m/ecc in skills/design-system/SKILL.md and read by ahel’s review.
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:
onboard.py— interactive (or--defaults/--set/--show/--reset) wizard.config_loader.py— importable customization loader with project > global > defaults precedence andMARKDOWN_HTML_NO_CONFIG=1bypass.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
- 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.pyrefuses to save (exit code 4) and tells the user to pick a darker primary, blankbrand.bg/brand.textto let derivation pick a safe pair, or overridebrand.textdirectly. Canon: WCAG 2.2 §1.4.3. - Output directory must be writable.
onboard.pywalks up the path to find an existing ancestor and checksos.W_OK. Empty or unwritable path → exit code 3. The orchestrator'soutput_path_resolver.pyhonors the same rule per-conversion. - 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, ortoc.behavior. Decorative-only fields fail the design discipline. - Precedence is fixed. Project > global > defaults. The deep-merge preserves nested keys (e.g. you can override
brand.primaryin a project config without losingtypography.heading_fontfrom global). - Bypass env exists for a reason.
MARKDOWN_HTML_NO_CONFIG=1is 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 <style> block.
| Token | Purpose | Derivation |
|---|---|---|
--md-bg | Document background | Primary if dark, near-neutral if vibrant |
--md-surface | Card / callout / blockquote background | Bg ± 4-6% luminance |
--md-border | Hairline dividers, table borders | Bg ± 8-12% luminance |
--md-text | Body text | Off-white on dark bg, near-black on light bg |
--md-text-muted | Captions, metadata, footers | rgba(text, 0.68) |
--md-accent | Primary CTA, callout headers, link emphasis | Primary if vibrant, hue-shifted lighter if dark |
--md-accent-soft | Accent backgrounds, hover states | rgba(accent, 0.14) |
--md-code-bg | Inline code, fenced block bg | Bg ± 4-5% luminance |
--md-link | Hyperlinks | Iteratively walked to reach 4.5:1 contrast on bg |
--md-link-hover | Hover state | Link ± 6-8% luminance |
--md-success | OK / approved / passed | Green anchored, luminance-matched |
--md-warn | Caution / nit / TODO | Amber anchored, luminance-matched |
Forcing-question library (Matt Pocock grill-with-docs pattern)
One question per turn, recommended answer, canon citation.
- What's your brand primary color? Recommended: a HEX you already use in your product or docs — not a stock blue. Canon: Aarron Walter, Designing for Emotion (color carries brand affect).
- Should accent be derived or set? Recommended: derive on first run (hue-shift + lighten produces a coherent companion); set explicitly only if your brand kit specifies one. Canon: Adobe Spectrum, Color Foundations.
- Editorial, technical, minimal, or playful? Recommended:
technicalfor engineering specs/reports,editorialfor long-read narratives,minimalfor sparse reference docs,playfulfor marketing/landing content. Canon: Ellen Lupton, Thinking with Type (style serves the rhetorical purpose). - Sticky-sidebar TOC, or inline? Recommended:
sticky-sidebarfor documents over 800 words,inlinefor short reads. Canon: Nielsen-Norman, Table of Contents Best Practices (2023). - Save to global or per-project? Recommended: global by default (consistent across your work); use
--scope projectonly when this repo has a different brand. Canon: research-ops onboarding pattern,research-ops/CLAUDE.md§8.
Customization in use (worked example)
# First-run onboarding (interactive, walks all 10 questions)
python3 markdown-html/skills/design-system/scripts/onboard.py
# Zero-touch defaults for CI / first-test
python3 .../onboard.py --defaults
# Change just the primary color and design style
python3 .../onboard.py --set brand.primary=#FF6B35 --set design_style=editorial
# Per-repo override
python3 .../onboard.py --scope project --set design_style=minimal
# Reset and re-onboard
python3 .../onboard.py --reset
python3 .../onboard.py
# Inspect the effective config (project > global > defaults)
python3 .../config_loader.py --show
python3 .../config_loader.py --status
# Bypass saved config (returns DEFAULTS only)
MARKDOWN_HTML_NO_CONFIG=1 python3 .../config_loader.py --show
# Spot-check WCAG contrast before committing to a brand
python3 .../brand_palette_validator.py --primary "#FF6B35" --accent "#00D4AA"
Assumptions
- User has at least one brand HEX they want consistent across their HTML conversions.
- User accepts a 1-2 minute one-time setup.
- User is OK with Google Fonts as the typography source (CDN, no local font hosting).
- WCAG 2.2 AA is the accessibility floor (4.5:1 body, 3:1 large/UI). AAA (7:1) is out of scope.
Non-goals
- Not a full design-token system (Style Dictionary, Theo). Twelve tokens, not a hundred.
- Not a custom-font hosting solution. Google Fonts only.
- Not a dark/light mode switcher in the converters.
code_theme: autohandles the prefers-color-scheme case for syntax highlighting; layout palette is single-mode per onboarding. - Not an accessibility audit suite (use axe-core / pa11y for that). We enforce contrast only.
- Does not transform existing CSS — the derived palette is injected into freshly generated HTML.
Distinct from
marketing/landing/skills/landing/scripts/brand_palette_validator.py— that script'sderive_palette()produces 8 tokens shaped for hero-page rendering (--navy,--teal,--card-bg,--card-border). This script produces 12 tokens shaped for document rendering (sticky surface, hairline border, code bg, link, link-hover, success, warn). Same WCAG + HSL math, different token taxonomy.research-ops/skills/clinical-research/scripts/onboard.py— same pattern (interactive +--defaults/--set/--show/--reset/--scope), different question set (clinical alpha/power/dropout vs. brand palette/typography/layout).
Output artifact
~/.config/markdown-html/design-system.json (global) or ./.markdown-html/design-system.json (project). JSON schema lives at assets/design_system_schema.json.
Anti-patterns (do not)
- ❌ Skip onboarding and run a converter with placeholder defaults — output looks unbranded.
- ❌ Pick a vibrant brand primary as
brand.bgdirectly (low text contrast). Use it as accent instead. - ❌ Set
MARKDOWN_HTML_NO_CONFIG=1silently for an interactive user — they'll wonder why their tokens disappeared. - ❌ Encode brand semantics in
derived_paletteoutside the 12-token taxonomy. Add a new token only with a deliberate name + purpose + derivation rule.
References
- WCAG 2.2 — §1.4.3 (contrast), §1.4.4 (resize), §1.4.11 (non-text contrast)
- Aarron Walter — Designing for Emotion (A Book Apart)
- Ellen Lupton — Thinking with Type
- Adobe Spectrum — Color Foundations
- Nielsen-Norman — Table of Contents Best Practices (2023)
- research-ops onboarding pattern:
research-ops/CLAUDE.md§8 - Brand palette math source:
marketing/landing/skills/landing/scripts/brand_palette_validator.py
Signals
- GitHub stars
- 256k
- Forks
- 38k
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
design-system- Source
- github.com/affaan-m/ecc