---
name: "brand_palette_design"
description: "Build a brand color palette that works in practice: five roles from primary to accent, a strong variant and an on color for every filled role, contrast numbers for every declared pairing, light and dark surface checks, and export as editable SVG and CSS a site can use the same day. Use when creating or fixing brand colors, choosing palette roles, checking whether brand text pairings pass WCAG, or shipping palette files to a site or deck."
---

# Brand Palette Design

## Purpose

Most palette work stops at a row of nice swatches. The failures show up later: white button text on a bright accent, grey captions nobody can read, a dark mode built by guessing. This skill ships a palette as a usage system. Every color has a role, every role that carries text has a checked pairing, and the deliverable is a palette file plus generated CSS and an editable SVG sheet, not a screenshot of swatches.

Use a color theory skill for harmony basics, such as how analogous and complementary schemes are built. Use this skill when the palette has to survive buttons, body text, forms, charts, and a dark surface.

## Workflow

1. **Assign the five roles before picking final hex values.**
   - Primary: the recognition color. Logo, key surfaces, primary buttons.
   - Secondary: support. Section bands, secondary buttons, illustration range.
   - Accent: emphasis only. One call to action family, highlights, badges. Sparing use is what makes it read as emphasis.
   - Ink and neutrals: body text, muted text, lines. Neutrals carry most of any real page.
   - Surfaces: a light surface, a slightly tinted alt light surface, and a dark surface, each named.
   - Semantic colors (success, warning, error) sit outside the five roles. They communicate state, so brand colors never double as state colors.

2. **Give every filled role two companions.**
   - A strong variant: same hue, dark enough (or light enough, on dark surfaces) to carry normal text at 4.5:1. Primary gets primary_strong, accent gets accent_strong.
   - An on color: the named color for text and icons placed on that fill, such as on_primary on primary. If a fill has no on color, nobody may put text on it.
   - Bright brand fills are allowed to fail normal text. Their job is graphics, large headings, and badges at 3:1 and up. The strong variant does the text work. Splitting the job is the core move in this skill.

3. **Write the palette file.** Use the JSON format in `scripts/palette.py`: a `roles` map plus a `pairs` list. Each pair names a foreground role, a background role, its use in plain words, and a level: `normal`, `large`, or `nontext`. Declare every pairing the brand will actually use, including captions, footer text on brand bands, input borders, and dark surface text. Two complete starting files ship in `assets/`: `service-palette.json` and `product-palette.json`.

4. **Check every pair with the bundled checker.** Run:
   - `python3 scripts/palette.py contrast '#C75B26' '#FFFFFF'` for a single pair.
   - `python3 scripts/palette.py check assets/service-palette.json` for a whole palette. Exit code 1 means at least one declared pair fails, and the output names it with its ratio.
   - Thresholds, WCAG 2.2 AA: normal text 4.5:1, large text 3:1, non-text elements that carry meaning 3:1. Large text means at least 24px regular or at least 18.66px bold. Compare ratios without rounding up: 4.49 fails 4.5.
   - Fix failures by the method in `references/pairing-rules.md`: keep the hue, move lightness in small steps, stop just past the threshold, and record the fix in the palette file.

5. **Check the dark surface as its own palette.** Dark surfaces get their own text roles (on_dark, on_dark_muted) checked against surface_dark. Never reuse the light ink on a dark surface or assume a pair that passed on white passes on the dark surface. Also check any brand band used in dark contexts, such as on_dark text on primary.

6. **Apply the pairing rules.** Follow `references/pairing-rules.md` for the full matrix. The short version:
   - Text uses ink, ink_muted, primary_strong, accent_strong, or an on color. Raw primary, secondary, and accent carry text only at large size with a passing ratio.
   - Semantic colors always travel with an icon or a word. Color alone never carries state, links, or chart meaning. Underline text links instead of relying on a color shift.
   - Proportions start near 60 percent surfaces and neutrals, 30 percent primary and secondary, 10 percent accent. Treat this as a discipline heuristic, not a standard.

7. **Export the deliverables.** Run `python3 scripts/palette.py export assets/service-palette.json out/` to write a CSS file with one custom property per role plus a dark theme block, and an SVG swatch sheet with real text elements a designer can edit. Edit the JSON and regenerate. Hand-editing the CSS forks the palette.

8. **Hand over the system, not the swatches.** The handoff is the palette file, the checker output showing every pair and ratio, the CSS, and the SVG sheet. State which pairs are restricted (large only, graphics only) in the handoff notes.

## Worked palettes

Both files in `assets/` pass every declared pair with the bundled checker. Numbers below come from checker runs on these exact files.

**Harbor and Brick, for a local service business.** Deep harbour blue primary `#1B4F72` with white button text at 8.72:1, green secondary `#1F6F5B` at 6.03:1 with white, burnt orange accent `#C75B26` kept to large badges at 4.24:1 while its strong variant `#9A4419` carries call to action labels at 6.53:1 with white. Ink `#1A2027` on white measures 16.41:1, body text on the dark surface `#12222E` measures 14.52:1. The feel is steady and trade-credible, and the accent stays rare enough to mean "call now".

**Violet Circuit, for a software product.** Violet primary `#4A2FB5` with white at 8.88:1, teal secondary `#0E6B6E` with white at 6.27:1, hot orange accent `#D9480F` restricted to large use at 4.30:1 with its strong variant `#B23A0B` carrying upgrade button labels at 5.99:1. Ink on the tinted alt surface `#F1EEFA` measures 15.74:1, and text on the dark surface `#171126` measures 16.49:1, so the same file covers the marketing page, the app, and dark mode.

## Output Contract

A finished palette ships:

- A palette JSON file with all roles named and every real pairing declared.
- Checker output with a ratio and a pass for every declared pair, on light and dark surfaces.
- A generated CSS file with one custom property per role and a dark theme block.
- An editable SVG swatch sheet generated from the same file.
- Handoff notes listing any restricted pairs (large only, graphics only, never text).

## Operating Rules

1. No pair ships on a vibe. If a pairing is not in the palette file with a passing ratio, it does not go out.
2. Every filled role has a named on color and a strong variant before the palette is called done.
3. Fix the color or change the pair. Never lower a threshold, relabel normal text as large, or round a ratio up to make a failure pass.
4. Semantic state is never carried by color alone. Pair it with an icon, a label, or a pattern, and check chart series with labels or patterns as well.
5. One palette file is the source. CSS, SVG, slide decks, and site code all generate from it or copy from it, and changes land in the file first.
6. Logos are exempt from WCAG text contrast, but a logo used on light and dark backgrounds still ships in a light version and a dark version.
7. Print values are proven, not converted. Record an approved CMYK build only after a physical proof, and say so when a value is unproven.
