@bompus/house-rules 0.0.0-stage → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +54 -0
- package/LICENSE +21 -0
- package/README.md +378 -2
- package/THIRD_PARTY_NOTICES.md +188 -0
- package/compose.mjs +125 -0
- package/composition.mjs +361 -0
- package/config-view.mjs +209 -0
- package/config.mjs +381 -0
- package/docs/assets/banner-dark.png +0 -0
- package/docs/assets/banner-light.png +0 -0
- package/docs/assets/overview.png +0 -0
- package/docs/configuration.md +269 -0
- package/docs/skill-names.md +50 -0
- package/examples/person/house-rules.json +15 -0
- package/examples/person/rules/10-tooling.md +8 -0
- package/examples/person/rules/20-reporting.md +9 -0
- package/examples/project/AGENTS.md +18 -0
- package/package.json +42 -4
- package/rule-references.mjs +91 -0
- package/rules/core.md +340 -0
- package/rules/modifiers/backlog-outline.md +25 -0
- package/rules/modifiers/backlog-visual.md +16 -0
- package/rules/modifiers/coded-offers.md +70 -0
- package/rules/modifiers/cross-session-relay.md +23 -0
- package/rules/modifiers/effort-estimates.md +41 -0
- package/rules/modifiers/land-when-done.md +12 -0
- package/rules/modifiers/low-quota-handoff.md +20 -0
- package/rules/modifiers/model-default-questions.md +33 -0
- package/rules/modifiers/multi-agent.md +38 -0
- package/rules/modifiers/no-attribution.md +16 -0
- package/rules/modifiers/periodic-updates.md +25 -0
- package/rules/modifiers/plan-files.md +82 -0
- package/rules/modifiers/question-cards.md +48 -0
- package/rules/modifiers/release-batching.md +15 -0
- package/rules/modifiers/scratch-on-disk.md +15 -0
- package/rules/modifiers/shared-host-load.md +19 -0
- package/rules/modifiers/solo-operator.md +13 -0
- package/rules/modifiers/squash-landing.md +22 -0
- package/rules/modifiers/upstream-contributions.md +45 -0
- package/rules/modifiers/web-image-formats.md +35 -0
- package/rules/references/backlog-view.md +154 -0
- package/rules/references/github-actions.md +45 -0
- package/rules/references/landing.md +39 -0
- package/rules/references/multi-agent.md +51 -0
- package/rules/references/release-batching.md +66 -0
- package/rules/references/squash-landing.md +50 -0
- package/rules/references/thread-visuals.md +45 -0
- package/setup.mjs +445 -0
- package/skills/hr-agent-guidance-audit/LICENSE +21 -0
- package/skills/hr-agent-guidance-audit/SKILL.md +91 -0
- package/skills/hr-agent-guidance-audit/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-audit/references/prune-workflow.md +72 -0
- package/skills/hr-agent-guidance-audit/references/records-and-lessons.md +60 -0
- package/skills/hr-agent-guidance-audit/references/retirement-verdicts.md +49 -0
- package/skills/hr-agent-guidance-refresh/LICENSE +21 -0
- package/skills/hr-agent-guidance-refresh/SKILL.md +36 -0
- package/skills/hr-agent-guidance-refresh/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-refresh/references/verification.md +47 -0
- package/skills/hr-api-exposure-check/SKILL.md +159 -0
- package/skills/hr-api-exposure-check/agents/openai.yaml +3 -0
- package/skills/hr-audit-choices/SKILL.md +67 -0
- package/skills/hr-audit-choices/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/NOTICE.md +36 -0
- package/skills/hr-benchmarking/SKILL.md +279 -0
- package/skills/hr-benchmarking/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/references/javascript.md +130 -0
- package/skills/hr-benchmarking/references/tools.md +165 -0
- package/skills/hr-benchmarking/scripts/process-attribution.ps1 +20 -0
- package/skills/hr-benchmarking/scripts/process-attribution.py +107 -0
- package/skills/hr-better-accessibility/LICENSE +22 -0
- package/skills/hr-better-accessibility/NOTICE.md +46 -0
- package/skills/hr-better-accessibility/SKILL.md +119 -0
- package/skills/hr-better-accessibility/agents/openai.yaml +3 -0
- package/skills/hr-better-accessibility/focus-and-keyboard.md +130 -0
- package/skills/hr-better-accessibility/forms.md +82 -0
- package/skills/hr-better-accessibility/hit-areas.md +98 -0
- package/skills/hr-better-accessibility/motion-and-zoom.md +81 -0
- package/skills/hr-better-accessibility/screen-readers.md +102 -0
- package/skills/hr-better-accessibility/semantics-and-aria.md +84 -0
- package/skills/hr-better-colors/LICENSE +21 -0
- package/skills/hr-better-colors/NOTICE.md +37 -0
- package/skills/hr-better-colors/SKILL.md +114 -0
- package/skills/hr-better-colors/agents/openai.yaml +3 -0
- package/skills/hr-better-colors/color-formats.md +90 -0
- package/skills/hr-better-colors/color-usage.md +140 -0
- package/skills/hr-better-colors/contrast.md +80 -0
- package/skills/hr-better-colors/palette-generation.md +104 -0
- package/skills/hr-better-colors/palette-structure.md +76 -0
- package/skills/hr-better-colors/token-naming.md +97 -0
- package/skills/hr-better-layout/LICENSE +22 -0
- package/skills/hr-better-layout/NOTICE.md +48 -0
- package/skills/hr-better-layout/SKILL.md +92 -0
- package/skills/hr-better-layout/agents/openai.yaml +3 -0
- package/skills/hr-better-layout/grouping-and-alignment.md +144 -0
- package/skills/hr-better-layout/spacing-and-adaptivity.md +215 -0
- package/skills/hr-better-typography/LICENSE +22 -0
- package/skills/hr-better-typography/NOTICE.md +46 -0
- package/skills/hr-better-typography/SKILL.md +172 -0
- package/skills/hr-better-typography/agents/openai.yaml +3 -0
- package/skills/hr-better-typography/choosing-fonts.md +69 -0
- package/skills/hr-better-typography/css-cheat-sheet.md +65 -0
- package/skills/hr-better-typography/details-and-accessibility.md +119 -0
- package/skills/hr-better-typography/spacing-and-sizing.md +131 -0
- package/skills/hr-better-typography/variable-fonts-and-opentype.md +105 -0
- package/skills/hr-better-typography/wrapping-and-punctuation.md +57 -0
- package/skills/hr-better-writing/LICENSE +22 -0
- package/skills/hr-better-writing/NOTICE.md +46 -0
- package/skills/hr-better-writing/SKILL.md +122 -0
- package/skills/hr-better-writing/agents/openai.yaml +3 -0
- package/skills/hr-code-review/LICENSE +21 -0
- package/skills/hr-code-review/NOTICE.md +26 -0
- package/skills/hr-code-review/SKILL.md +163 -0
- package/skills/hr-code-review/agents/openai.yaml +3 -0
- package/skills/hr-code-review/references/acp-client.md +43 -0
- package/skills/hr-code-review/references/evidence.md +57 -0
- package/skills/hr-code-review/references/impact.md +103 -0
- package/skills/hr-code-review/references/panel-adjudication.md +58 -0
- package/skills/hr-code-review/references/panel-plan.md +61 -0
- package/skills/hr-code-review/references/panel-recommendations.json +105 -0
- package/skills/hr-code-review/references/panel-recommendations.md +46 -0
- package/skills/hr-code-review/references/panel-setup.md +54 -0
- package/skills/hr-code-review/references/reviewer-role.md +31 -0
- package/skills/hr-code-review/scripts/acp-panel-client.mjs +299 -0
- package/skills/hr-code-review/scripts/check-receipts.mjs +263 -0
- package/skills/hr-code-review/scripts/panel-plan.mjs +69 -0
- package/skills/hr-code-review/scripts/panel-preferences.mjs +220 -0
- package/skills/hr-design-exploration/SKILL.md +105 -0
- package/skills/hr-design-exploration/agents/openai.yaml +4 -0
- package/skills/hr-diagnosing-bugs/NOTICE.md +45 -0
- package/skills/hr-diagnosing-bugs/SKILL.md +108 -0
- package/skills/hr-diagnosing-bugs/agents/openai.yaml +3 -0
- package/skills/hr-diagnosing-bugs/references/reporting.md +97 -0
- package/skills/hr-diagnosing-bugs/scripts/check-pr-offer.mjs +47 -0
- package/skills/hr-explain-code/NOTICE.md +37 -0
- package/skills/hr-explain-code/SKILL.md +110 -0
- package/skills/hr-explain-code/agents/openai.yaml +5 -0
- package/skills/hr-explain-code/references/helper-brief.md +39 -0
- package/skills/hr-extract-shared-steps/SKILL.md +134 -0
- package/skills/hr-extract-shared-steps/agents/openai.yaml +3 -0
- package/skills/hr-forgotten-work-audit/SKILL.md +130 -0
- package/skills/hr-forgotten-work-audit/references/brief-template.md +56 -0
- package/skills/hr-forgotten-work-audit/references/result-format.md +46 -0
- package/skills/hr-handoff/LICENSE +21 -0
- package/skills/hr-handoff/NOTICE.md +29 -0
- package/skills/hr-handoff/SKILL.md +114 -0
- package/skills/hr-handoff/agents/openai.yaml +5 -0
- package/skills/hr-handoff/assets/ledger.example.json +10 -0
- package/skills/hr-handoff/assets/receipt.example.json +76 -0
- package/skills/hr-handoff/assets/source.example.json +20 -0
- package/skills/hr-handoff/references/reconciliation.md +124 -0
- package/skills/hr-handoff/references/successor-thread.md +76 -0
- package/skills/hr-handoff/scripts/check-reconciliation.mjs +324 -0
- package/skills/hr-house-rules-setup/SKILL.md +114 -0
- package/skills/hr-house-rules-setup/agents/openai.yaml +5 -0
- package/skills/hr-issue-tracker-setup/SKILL.md +49 -0
- package/skills/hr-issue-tracker-setup/agents/openai.yaml +5 -0
- package/skills/hr-lean-plan/SKILL.md +146 -0
- package/skills/hr-lean-plan/agents/openai.yaml +5 -0
- package/skills/hr-maintainability-review/SKILL.md +186 -0
- package/skills/hr-maintainability-review/agents/openai.yaml +5 -0
- package/skills/hr-manual-qa/NOTICE.md +36 -0
- package/skills/hr-manual-qa/SKILL.md +37 -0
- package/skills/hr-manual-qa/agents/openai.yaml +3 -0
- package/skills/hr-navigation-retro/LICENSE +21 -0
- package/skills/hr-navigation-retro/NOTICE.md +5 -0
- package/skills/hr-navigation-retro/SKILL.md +86 -0
- package/skills/hr-navigation-retro/agents/openai.yaml +4 -0
- package/skills/hr-navigation-retro/references/inputs.md +67 -0
- package/skills/hr-navigation-retro/scripts/retro.py +552 -0
- package/skills/hr-navigation-retro/scripts/transcripts.py +205 -0
- package/skills/hr-ordering-tests/SKILL.md +51 -0
- package/skills/hr-ordering-tests/agents/openai.yaml +2 -0
- package/skills/hr-plain-prose/SKILL.md +264 -0
- package/skills/hr-plain-prose/agents/openai.yaml +3 -0
- package/skills/hr-plain-prose/scripts/check.mjs +331 -0
- package/skills/hr-pr-followup/LICENSE +21 -0
- package/skills/hr-pr-followup/NOTICE.md +9 -0
- package/skills/hr-pr-followup/SKILL.md +74 -0
- package/skills/hr-pr-followup/agents/openai.yaml +2 -0
- package/skills/hr-pr-followup/references/review-helpers.md +111 -0
- package/skills/hr-pr-followup/scripts/check-landing.py +204 -0
- package/skills/hr-pr-followup/scripts/check-pr-review.ts +510 -0
- package/skills/hr-pr-followup/scripts/merge-pr.sh +100 -0
- package/skills/hr-pr-followup/scripts/resolve-pr-thread.ts +219 -0
- package/skills/hr-pr-followup/scripts/verify-landed.sh +184 -0
- package/skills/hr-progress-report/SKILL.md +49 -0
- package/skills/hr-progress-report/agents/openai.yaml +5 -0
- package/skills/hr-read-reddit/SKILL.md +44 -0
- package/skills/hr-read-reddit/agents/openai.yaml +2 -0
- package/skills/hr-read-reddit/scripts/reddit.ts +252 -0
- package/skills/hr-read-x-links/SKILL.md +42 -0
- package/skills/hr-read-x-links/agents/openai.yaml +2 -0
- package/skills/hr-read-x-links/scripts/x-post.ts +264 -0
- package/skills/hr-research-shared-links/SKILL.md +103 -0
- package/skills/hr-split-to-prs/LICENSE +21 -0
- package/skills/hr-split-to-prs/NOTICE.md +9 -0
- package/skills/hr-split-to-prs/SKILL.md +61 -0
- package/skills/hr-split-to-prs/agents/openai.yaml +2 -0
- package/skills/hr-sqlite/SKILL.md +174 -0
- package/skills/hr-sqlite/agents/openai.yaml +2 -0
- package/skills/hr-sqlite/references/evidence.md +94 -0
- package/skills/hr-sqlite/references/tool-decisions.md +39 -0
- package/skills/hr-sqlite/scripts/inspect-sqlite.py +245 -0
- package/skills/hr-stock-ui-audit/SKILL.md +132 -0
- package/skills/hr-stock-ui-audit/agents/openai.yaml +3 -0
- package/skills/hr-stock-ui-audit/scripts/scan.mjs +619 -0
- package/skills/hr-tdd/LICENSE +21 -0
- package/skills/hr-tdd/NOTICE.md +10 -0
- package/skills/hr-tdd/SKILL.md +40 -0
- package/skills/hr-tdd/agents/openai.yaml +3 -0
- package/skills/hr-tdd/mocking.md +39 -0
- package/skills/hr-tdd/tests.md +77 -0
- package/skills/hr-test-audit/LICENSE +21 -0
- package/skills/hr-test-audit/NOTICE.md +67 -0
- package/skills/hr-test-audit/SKILL.md +146 -0
- package/skills/hr-test-audit/agents/openai.yaml +3 -0
- package/skills/hr-test-audit/scripts/floor-guard.ts +137 -0
- package/skills/hr-usage-report/SKILL.md +90 -0
- package/skills/hr-usage-report/agents/openai.yaml +2 -0
- package/skills/hr-usage-report/assets/example-report.json +125 -0
- package/skills/hr-usage-report/assets/report-template.html +377 -0
- package/skills/hr-usage-report/scripts/render-report.ts +86 -0
- package/skills/hr-usage-report/scripts/usage-by-model.ts +499 -0
- package/skills/hr-what-next/SKILL.md +151 -0
- package/skills/hr-what-next/agents/openai.yaml +4 -0
- package/skills/hr-what-next/references/reporting.md +59 -0
- package/skills/hr-what-next/scripts/check-open-work.mjs +711 -0
- package/skills/hr-writing-for-agents/NOTICE.md +46 -0
- package/skills/hr-writing-for-agents/SKILL-MECHANICS.md +83 -0
- package/skills/hr-writing-for-agents/SKILL.md +108 -0
- package/skills/hr-writing-for-agents/agents/openai.yaml +3 -0
- package/skills/hr-writing-pr/NOTICE.md +48 -0
- package/skills/hr-writing-pr/SKILL.md +119 -0
- package/skills/hr-writing-pr/agents/openai.yaml +2 -0
- package/skills/hr-writing-pr/scripts/benefit-chart.py +200 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Color usage
|
|
2
|
+
|
|
3
|
+
Deploying color once the system exists: meaning, emphasis, gradients and appearance variants. For picking values see [palette-generation.md](palette-generation.md), for naming them [token-naming.md](token-naming.md), for checking pairs [contrast.md](contrast.md).
|
|
4
|
+
|
|
5
|
+
## One color, one meaning
|
|
6
|
+
|
|
7
|
+
Users read a near-miss in hue as a slightly different shade, not as a different color.
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
/* Bad: the accent means both "link" and "decorative heading" */
|
|
11
|
+
a {
|
|
12
|
+
color: #3b82f6;
|
|
13
|
+
}
|
|
14
|
+
.section-title {
|
|
15
|
+
color: #4f8ef7;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Good: interactive elements own the accent; headings stay neutral */
|
|
19
|
+
a {
|
|
20
|
+
color: var(--color-accent-text);
|
|
21
|
+
}
|
|
22
|
+
.section-title {
|
|
23
|
+
color: var(--color-text-primary);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The rule runs both ways. A color must not be _absent_ where its meaning occurs. If the accent means interactive, an interactive element rendered neutral is just as misleading.
|
|
28
|
+
|
|
29
|
+
Color is never the only carrier of meaning. Pair it with an icon, a label, or a shape. `hr-better-accessibility` owns that requirement.
|
|
30
|
+
|
|
31
|
+
## Use tokens in their role
|
|
32
|
+
|
|
33
|
+
Apply a semantic token only for the role it names. `--color-text-secondary` is muted foreground text. Use it as a background and every future theme change that assumes the role breaks, because a value that happened to work as both stops working as both.
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
/* Bad: separator token repurposed as a text color because it looked right */
|
|
37
|
+
.caption {
|
|
38
|
+
color: var(--color-border);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Bad: text token repurposed as a background */
|
|
42
|
+
.tag {
|
|
43
|
+
background: var(--color-text-secondary);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The role inventory in [token-naming.md](token-naming.md) is the list of roles a system needs.
|
|
48
|
+
|
|
49
|
+
## One colored action per view
|
|
50
|
+
|
|
51
|
+
Preserve an established component hierarchy that communicates emphasis another way. Do not recolor controls merely to impose this recipe.
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<!-- Good: one filled primary action, neutral secondaries -->
|
|
55
|
+
<button class="bg-accent-solid text-white">Save</button>
|
|
56
|
+
<button class="text-neutral-700">Cancel</button>
|
|
57
|
+
|
|
58
|
+
<!-- Bad: every action colored, so nothing is primary -->
|
|
59
|
+
<button class="bg-accent-solid text-white">Save</button>
|
|
60
|
+
<button class="bg-accent-solid text-white">Duplicate</button>
|
|
61
|
+
<button class="bg-accent-solid text-white">Export</button>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Selected states may use the accent on the glyph and label. An active tab or a checked segment is state, not emphasis.
|
|
65
|
+
|
|
66
|
+
## Gradients
|
|
67
|
+
|
|
68
|
+
**The interpolation space is a look, not a correctness setting.** Three are worth knowing, and the difference between them is most visible in the middle of the gradient:
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
/* sRGB: the default and the classic. Midpoint darkens and mutes. */
|
|
72
|
+
background: linear-gradient(#3b82f6, #ec4899);
|
|
73
|
+
|
|
74
|
+
/* oklab: even brightness across the transition. The best default. */
|
|
75
|
+
background: linear-gradient(in oklab, #3b82f6, #ec4899);
|
|
76
|
+
|
|
77
|
+
/* oklch: travels around the hue wheel, staying vivid throughout. */
|
|
78
|
+
background: linear-gradient(in oklch, #3b82f6, #ec4899);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`oklab` and sRGB are **rectangular**, interpolating in a straight line through the color space. `oklch` is **polar**, interpolating the hue angle, so it arcs around the wheel through every hue between the stops. That is why it stays saturated and why it can produce hues nobody asked for. A blue-to-pink gradient routes through purple, which is either the look or a surprise.
|
|
82
|
+
|
|
83
|
+
**The gray dead zone is a rectangular-space problem.** Two hues on opposite sides of the wheel sit either side of the neutral axis. A straight line between them passes near gray, and the middle goes lifeless. Either switch to a polar space, which routes around the axis, or add a third stop between the two and keep the space you have.
|
|
84
|
+
|
|
85
|
+
With a polar space you also control which way it goes around:
|
|
86
|
+
|
|
87
|
+
```css
|
|
88
|
+
/* The short way round, usually what you want */
|
|
89
|
+
background: linear-gradient(in oklch shorter hue, #3b82f6, #ec4899);
|
|
90
|
+
|
|
91
|
+
/* The long way, sweeps most of the spectrum */
|
|
92
|
+
background: linear-gradient(in oklch longer hue, #3b82f6, #ec4899);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Banding shows up on large areas.** A gradient spanning a hero with little contrast between its stops steps visibly on 8-bit displays. Widen the contrast, shrink the area, or overlay a subtle noise texture.
|
|
96
|
+
|
|
97
|
+
**Keep text off gradients where you can.** Contrast varies continuously across one, so a single measurement does not describe it. Where text must sit on a gradient, measure the worst region rather than the average, or put a scrim behind it.
|
|
98
|
+
|
|
99
|
+
## Color across cultures
|
|
100
|
+
|
|
101
|
+
Color meaning is not universal. Where a color is load-bearing in finance, status, or alerts, verify the meaning holds in every locale you ship to.
|
|
102
|
+
|
|
103
|
+
| Color | Common Western reading | Elsewhere |
|
|
104
|
+
| ----- | ---------------------- | ---------------------------------------------------- |
|
|
105
|
+
| Red | Danger, loss, errors | Luck, prosperity; **gains** in Chinese financial UIs |
|
|
106
|
+
| Green | Success, gains, go | Losses in Chinese financial UIs |
|
|
107
|
+
| White | Purity, cleanliness | Mourning in parts of East Asia |
|
|
108
|
+
| Gold | Premium, luxury | Religious significance in some regions |
|
|
109
|
+
|
|
110
|
+
The classic case is stock tickers, which show gains in green for English locales and red for Chinese ones. Where the product ships to such markets, make gain and loss per-locale tokens rather than hardcoded values.
|
|
111
|
+
|
|
112
|
+
## Light, dark and increased contrast
|
|
113
|
+
|
|
114
|
+
Every custom color needs a light and a dark variant, derived per [palette-generation.md](palette-generation.md). Beyond that, users who enable increased contrast expect visibly stronger differentiation:
|
|
115
|
+
|
|
116
|
+
```css
|
|
117
|
+
:root {
|
|
118
|
+
--color-accent-solid: #3b82f6;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@media (prefers-color-scheme: dark) {
|
|
122
|
+
:root {
|
|
123
|
+
--color-accent-solid: #60a5fa;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@media (prefers-contrast: more) {
|
|
128
|
+
:root {
|
|
129
|
+
--color-accent-solid: #1d4ed8;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@media (prefers-color-scheme: dark) and (prefers-contrast: more) {
|
|
134
|
+
:root {
|
|
135
|
+
--color-accent-solid: #bfdbfe;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Check light, dark and each increased-contrast combination against its actual surfaces. The example values illustrate the cascade, not guaranteed contrast for an unspecified background. The increased-contrast variant widens the foreground/background gap by at least 15 points of perceived lightness over the default. Re-verify against APCA's preferred thresholds, Lc 90 body and Lc 75 non-body. Widening the gap without remeasuring is not fixing it.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Contrast
|
|
2
|
+
|
|
3
|
+
Contrast is measured between a **foreground color**, meaning text, an icon, or a UI element, and the **background color** it actually renders against, usually the nearest ancestor that paints one. Identify that background first. Measuring against the page background when the element sits on a card gives the wrong answer.
|
|
4
|
+
|
|
5
|
+
**Report, don't repaint.** When a check fails, report the pair, its measured value and the threshold it misses, and leave the colors unchanged. They are a design decision. Apply the fix below only when asked.
|
|
6
|
+
|
|
7
|
+
`hr-better-accessibility` decides when contrast is required and whether a given pair must pass. This file covers measuring the pair and, on request, changing it.
|
|
8
|
+
|
|
9
|
+
## APCA thresholds (recommended)
|
|
10
|
+
|
|
11
|
+
APCA (Accessible Perceptual Contrast Algorithm) models perceived contrast more accurately than WCAG 2 and is the better default for design decisions. Lc (Lightness Contrast) measures perceived contrast between foreground and background. These levels simplify APCA's full font-size and weight lookup table:
|
|
12
|
+
|
|
13
|
+
| Content type | Minimum | Preferred |
|
|
14
|
+
| ---------------------------------------------------------------------- | ------- | --------- |
|
|
15
|
+
| Body text (columns or blocks of text) | Lc 75 | Lc 90 |
|
|
16
|
+
| Non-body text (labels, headlines) | Lc 60 | Lc 75 |
|
|
17
|
+
| Large text meeting APCA size/weight guidance (e.g. 36px at weight 400) | Lc 45 | Lc 60 |
|
|
18
|
+
| Large solid semantic shapes | Lc 30 | n/a |
|
|
19
|
+
| Fine-detail pictograms and small outline icons | Lc 45 | n/a |
|
|
20
|
+
|
|
21
|
+
Use [APCA's size and weight guidance](https://github.com/Myndex/SAPC-APCA/blob/master/documentation/APCA_in_a_Nutshell.md) for the actual typeface; 36px text at weight 300 needs Lc 60. Lc 30 is also APCA's minimum for disabled and placeholder text. Lc 15 applies only to sufficiently large, solid non-semantic elements.
|
|
22
|
+
|
|
23
|
+
Lc is signed: positive means dark text on a light background, negative means light text on a dark background. Compare the absolute value against the threshold.
|
|
24
|
+
|
|
25
|
+
## WCAG 2 thresholds (for legal compliance)
|
|
26
|
+
|
|
27
|
+
WCAG 2 is still required for formal WCAG 2.x conformance claims. Its luminance ratio is both too strict and too lenient depending on the pair, but it has the legal standing.
|
|
28
|
+
|
|
29
|
+
| Content type | AA | AAA |
|
|
30
|
+
| ----------------------------------- | ----- | ----- |
|
|
31
|
+
| Normal text (<24px / <18.67px bold) | 4.5:1 | 7:1 |
|
|
32
|
+
| Large text (≥24px / ≥18.67px bold) | 3:1 | 4.5:1 |
|
|
33
|
+
| UI components and graphical objects | 3:1 | n/a |
|
|
34
|
+
|
|
35
|
+
WCAG defines large text in points: 18pt ≈ `24px`, 14pt bold ≈ `18.67px`.
|
|
36
|
+
|
|
37
|
+
When a project must claim WCAG conformance, WCAG is the gate and APCA is the tiebreaker for anything above it.
|
|
38
|
+
|
|
39
|
+
## Fixing a failing pair (on request)
|
|
40
|
+
|
|
41
|
+
**Change lightness first.** It is the channel contrast responds to. Hue and saturation move the measured value far less, so fixing contrast by changing hue is wasted effort.
|
|
42
|
+
|
|
43
|
+
Move the foreground away from the background in perceived lightness, holding hue and saturation, then remeasure. Keeping hue fixed is what stops a contrast fix becoming a palette change.
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
/* Failing: text too close to its background in lightness (Lc ≈ 50) */
|
|
47
|
+
color: #7d93b0;
|
|
48
|
+
background: #eef2f7;
|
|
49
|
+
|
|
50
|
+
/* Fixed: darker text, same hue (Lc ≈ 90) */
|
|
51
|
+
color: #2b3a4f;
|
|
52
|
+
background: #eef2f7;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Two constraints on the fix:
|
|
56
|
+
|
|
57
|
+
- **Mid-lightness backgrounds cap what is achievable.** On a background near 75% perceived lightness, even pure black text reaches only about Lc 60. Body text needs a background near one extreme, so a mid-range background is the thing that has to change.
|
|
58
|
+
- **Pushing lightness can push the color out of gamut.** Reduce saturation as needed to keep it renderable. See [color-formats.md](color-formats.md).
|
|
59
|
+
|
|
60
|
+
Always remeasure after changing a value. Do not assume a fix landed.
|
|
61
|
+
|
|
62
|
+
## Quick approximations
|
|
63
|
+
|
|
64
|
+
Useful for a first pass and approximations. Verify by measuring before reporting a result.
|
|
65
|
+
|
|
66
|
+
For body text targeting |Lc| ≥ 75:
|
|
67
|
+
|
|
68
|
+
- **Light background (above ~90% perceived lightness):** foreground below ~35%.
|
|
69
|
+
- **Dark background (below ~25% perceived lightness):** foreground above ~90%.
|
|
70
|
+
|
|
71
|
+
The gap is asymmetric because APCA is polarity-aware. Mirrored pairs do not score identically, which is why a pair passing in light mode can fail in dark.
|
|
72
|
+
|
|
73
|
+
**Light or dark background?** Under APCA the crossover is around 73% perceived lightness: above it dark text scores higher, at or below it light text does. Under WCAG 2 the crossover for neutrals sits near 56% OKLCH lightness, so between roughly 56% and 73% white text can fail AA where black passes. Measure both candidates against the standard the project gates on.
|
|
74
|
+
|
|
75
|
+
## What to check
|
|
76
|
+
|
|
77
|
+
- **Every pair, in every appearance.** A pair passing in light mode can fail in dark. The palettes are not mirror images.
|
|
78
|
+
- **Translucent surfaces.** A color on a `backdrop-filter` header or an overlay shifts with whatever scrolls behind it. Test against the lightest and darkest content it can sit over, or make the surface opaque enough that the shift cannot break the pair.
|
|
79
|
+
- **Computed colors.** `color-mix()`, relative color syntax and opacity modifiers resolve at render time; measure the rendered result, not the declaration.
|
|
80
|
+
- **Text over images.** There is no single background color. Measure the worst region, or guarantee one with a scrim.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Palette generation
|
|
2
|
+
|
|
3
|
+
Producing the values once the structure is decided. For which ramps to build and what each step is for, see [palette-structure.md](palette-structure.md).
|
|
4
|
+
|
|
5
|
+
## Start from the brand color
|
|
6
|
+
|
|
7
|
+
A brand color arrives as one value, usually a hex. Two decisions come before any ramp exists:
|
|
8
|
+
|
|
9
|
+
**Which step does it occupy?** A brand color meant for buttons and links belongs on the solid-fill step: `500` in Tailwind, `9` in Radix. Then `bg-brand-500` renders the actual brand color, not an approximation.
|
|
10
|
+
|
|
11
|
+
**Is it pinned or snapped?** Pin a contractually fixed brand color. It stays exact, the ramp builds outward from it and that one step spaces slightly unevenly. Otherwise snap it onto the ramp so it follows the chosen lightness spacing. That looks better almost always, and nobody notices without a swatch held to the screen.
|
|
12
|
+
|
|
13
|
+
A brand color that fails contrast behind white text is still the brand color, just not the solid-fill step. Put it where it lands and use a darker step for interactive fills. Never quietly darken the brand.
|
|
14
|
+
|
|
15
|
+
## What a correct ramp looks like
|
|
16
|
+
|
|
17
|
+
Properties of the finished ramp, checkable against any output in any notation:
|
|
18
|
+
|
|
19
|
+
- **Steps follow a deliberate perceived-lightness progression.** Use a perceptual color space rather than evenly spaced HSL values. The denser light-end spacing below takes priority over equal intervals across the whole ramp.
|
|
20
|
+
- **Hue is constant end to end.** Every step is recognisably the same color. A wandering hue reads as two colors blended and will not sit correctly against a neutral built on a different hue.
|
|
21
|
+
- **Vividness peaks in the middle and falls off at both ends.** The lightest and darkest steps are nearly neutral; the middle carries the color. Holding full vividness into the extremes gives a `50` that glows and a `950` like ink spilled on the brand.
|
|
22
|
+
- **Steps are denser at the light end.** Light backgrounds need finer distinctions than dark ones. Keep `50` to `200` close together and `800` to `950` further apart. Even spacing across the whole range makes the pale end unusable, because `50` and `100` stop reading as two surfaces.
|
|
23
|
+
- **No two adjacent steps are indistinguishable.** If `200` and `300` look identical on a calibrated screen, the ramp has more steps than decisions. Drop one.
|
|
24
|
+
- **Both ends stop short of pure black and white.** A ramp that reaches them loses its identity exactly where the page background lives.
|
|
25
|
+
|
|
26
|
+
## Use a color library
|
|
27
|
+
|
|
28
|
+
Never compute these by hand or by eye. `culori`, `colorjs.io` and `chroma.js` all convert between notations, measure perceived lightness and interpolate perceptually. Read the brand color in whatever format it arrives, do the math in a perceptual space and emit the project's notation:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { formatHex, interpolate, samples } from "culori";
|
|
32
|
+
|
|
33
|
+
// Interpolation preview only: uniform samples are not a finished role-based ramp.
|
|
34
|
+
const ramp = interpolate(["#eff6ff", "#3b82f6", "#172554"], "lab");
|
|
35
|
+
const steps = samples(11).map((t) => formatHex(ramp(t)));
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The example previews interpolation with uniform samples; it does not generate a finished role-based ramp. The output format is the project's choice. For a ramp the interpolation space is not, because lightness spacing must be deliberate and sRGB interpolation produces muddy mid-steps. Choose sample positions to achieve the denser light-end spacing, then inspect the rendered steps. Decorative gradients are the opposite case, where the space is a deliberate look ([color-usage.md](color-usage.md)).
|
|
39
|
+
|
|
40
|
+
```css
|
|
41
|
+
:root {
|
|
42
|
+
--brand-50: #eff6ff;
|
|
43
|
+
--brand-100: #dbeafe;
|
|
44
|
+
--brand-200: #bfdbfe;
|
|
45
|
+
--brand-300: #93c5fd;
|
|
46
|
+
--brand-400: #60a5fa;
|
|
47
|
+
--brand-500: #3b82f6;
|
|
48
|
+
--brand-600: #2563eb;
|
|
49
|
+
--brand-700: #1d4ed8;
|
|
50
|
+
--brand-800: #1e40af;
|
|
51
|
+
--brand-900: #1e3a8a;
|
|
52
|
+
--brand-950: #172554;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Several hues at once
|
|
57
|
+
|
|
58
|
+
With an accent plus status ramps, the ramps must agree step for step. `danger-500` and `brand-500` should read as equally bright and vivid, or a red button looks heavier than a blue one at the same step.
|
|
59
|
+
|
|
60
|
+
- **Match perceived lightness exactly.** Same step, same brightness, across every hue.
|
|
61
|
+
- **Match vividness relatively, not absolutely.** Hues do not share a maximum vividness. A saturated yellow and a saturated blue are not equally far from gray, and no format makes them so. Set each ramp to the same _proportion_ of what its own hue reaches. Copying a saturation number across hues leaves one washed out.
|
|
62
|
+
|
|
63
|
+
Yellows and cyans are the usual casualties, peaking much lower than reds and blues. Copy the numbers across and the warning color looks weak beside the danger one.
|
|
64
|
+
|
|
65
|
+
## Dark mode
|
|
66
|
+
|
|
67
|
+
A dark palette is not the light one reversed. Reversal is the starting point, not the output.
|
|
68
|
+
|
|
69
|
+
Swap the semantic roles first, then tune the values:
|
|
70
|
+
|
|
71
|
+
```css
|
|
72
|
+
:root {
|
|
73
|
+
--color-bg: var(--brand-50);
|
|
74
|
+
--color-text: var(--brand-950);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.dark {
|
|
78
|
+
--color-bg: var(--brand-950);
|
|
79
|
+
--color-text: var(--brand-50);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Three things almost always need hand-tuning after the swap:
|
|
84
|
+
|
|
85
|
+
- **Vividness comes down.** A color that reads as confident on white reads as neon on near-black. Dark appearances need the accent a step or two less vivid.
|
|
86
|
+
- **The dark end needs more separation.** Steps distinguishable as pale backgrounds collapse into each other as dark surfaces.
|
|
87
|
+
- **Contrast does not survive the mirror.** A pair passing in light mode can fail reversed, because contrast is not symmetric. Recheck every foreground against its real background in both appearances ([contrast.md](contrast.md)).
|
|
88
|
+
|
|
89
|
+
### Choosing the switching mechanism
|
|
90
|
+
|
|
91
|
+
Pick one and use it throughout:
|
|
92
|
+
|
|
93
|
+
- **`prefers-color-scheme` alone** is correct with no theme toggle. Nothing to persist, nothing to hydrate.
|
|
94
|
+
- **A `.dark` class** is required as soon as users can override the system setting. The media query then sets only the initial value.
|
|
95
|
+
- **`light-dark()`** collapses both values into one declaration, the least code when the project also sets `color-scheme`. It reads that property rather than a class, so a class-based toggle must set `color-scheme` too.
|
|
96
|
+
|
|
97
|
+
```css
|
|
98
|
+
:root {
|
|
99
|
+
color-scheme: light dark;
|
|
100
|
+
--color-bg: light-dark(#ffffff, #172554);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Mixing mechanisms is the common failure. A media query setting some tokens and a class setting others gives a half-themed interface the moment a user overrides their system preference.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Palette structure
|
|
2
|
+
|
|
3
|
+
What a color system is made of, before any values exist. For computing the values see [palette-generation.md](palette-generation.md); for what to call them see [token-naming.md](token-naming.md).
|
|
4
|
+
|
|
5
|
+
## What a system needs
|
|
6
|
+
|
|
7
|
+
Most products need three kinds of ramp and nothing else:
|
|
8
|
+
|
|
9
|
+
| Ramp | How many | Notes |
|
|
10
|
+
| ------- | -------- | -------------------------------------------------------------------------------------- |
|
|
11
|
+
| Neutral | 1 | Carries 80–90% of the interface: backgrounds, borders, body text |
|
|
12
|
+
| Accent | 1 | The brand hue. Interactive and selected states |
|
|
13
|
+
| Status | 0–4 | `danger`, `warning`, `success`, `info`. Add one only when the product shows that state |
|
|
14
|
+
|
|
15
|
+
Add a second accent only when two things must be distinguishable, as required by `SKILL.md`. Check the rendered colors side by side in each appearance; if they are indistinguishable, use the existing accent ramp's steps or choose a distinct second hue.
|
|
16
|
+
|
|
17
|
+
## Every step has a job
|
|
18
|
+
|
|
19
|
+
Each step maps to a role. Generate the steps the roles below call for and skip the rest.
|
|
20
|
+
|
|
21
|
+
| Role | Tailwind | Radix |
|
|
22
|
+
| --------------------------- | -------- | ----- |
|
|
23
|
+
| Page background | `50` | `1` |
|
|
24
|
+
| Subtle background | `50` | `2` |
|
|
25
|
+
| Component background | `100` | `3` |
|
|
26
|
+
| Component hover | `200` | `4` |
|
|
27
|
+
| Component active / selected | `200` | `5` |
|
|
28
|
+
| Subtle border | `200` | `6` |
|
|
29
|
+
| Border, separator | `300` | `7` |
|
|
30
|
+
| Strong border, focus ring | `400` | `8` |
|
|
31
|
+
| Solid fill | `500` | `9` |
|
|
32
|
+
| Solid fill hover | `600` | `10` |
|
|
33
|
+
| Low-contrast text | `700` | `11` |
|
|
34
|
+
| High-contrast text | `900` | `12` |
|
|
35
|
+
|
|
36
|
+
The two conventions differ in kind, not only in numbering:
|
|
37
|
+
|
|
38
|
+
- **Radix defines its 12 steps by role.** Step 9 is "the solid fill" in every ramp and appearance. The dark scale is a separate ramp reusing the same numbers, so `--accent-9` is the fill in both and component CSS never changes.
|
|
39
|
+
- **Tailwind defines its 11 steps by lightness.** `50` is light, `950` is dark. The mapping above therefore holds in light mode and inverts in dark, with the page background at `950` and high-contrast text at `50`. Components either swap step numbers per appearance or read a semantic token that swaps once.
|
|
40
|
+
|
|
41
|
+
Match whichever the project uses. For a new system prefer Radix's model, because a role-defined step survives a theme change that a lightness-defined step does not. On Tailwind, keep `50`–`950` and put the role mapping in the semantic tier.
|
|
42
|
+
|
|
43
|
+
Tailwind's 11 steps cover 12 roles, so some do double duty. Where the table repeats a step, the two roles are adjacent in practice and the collision is real. A design needing a subtle border and a component hover to be distinguishable needs a 12-step ramp.
|
|
44
|
+
|
|
45
|
+
## Neutrals
|
|
46
|
+
|
|
47
|
+
A pure gray ramp is a perfectly good default. It sits under any accent hue without competing and never needs revisiting when the brand color changes.
|
|
48
|
+
|
|
49
|
+
Tinting the neutral toward the accent hue is a stylistic option, not a correction. A trace of the accent, a few percent of its vividness, puts the greys in the same family as the accent rather than merely coexisting with it. Enough to measure, not enough to name. Neither choice reads as a mistake.
|
|
50
|
+
|
|
51
|
+
Warm neutrals, hue toward orange, read approachable and editorial; cool ones, toward blue, read technical and precise. Whichever you pick, including none, hold it across the whole ramp. A warm gray border on a cool gray background is visible even when neither color is nameable alone.
|
|
52
|
+
|
|
53
|
+
Neutrals carry the most roles, so they need the most steps. Never generate fewer neutral steps than accent steps.
|
|
54
|
+
|
|
55
|
+
## Status colors
|
|
56
|
+
|
|
57
|
+
Convention constrains status hues before taste does. Red reads as danger, amber as warning, green as success. See the cultural exceptions in [color-usage.md](color-usage.md).
|
|
58
|
+
|
|
59
|
+
Two rules govern them:
|
|
60
|
+
|
|
61
|
+
- **Keep every status hue distinct from the accent.** If the brand is red, the danger ramp cannot also be red. Move danger toward a deeper crimson and check the two side by side, or the destructive and primary actions are the same button.
|
|
62
|
+
- **Status ramps need fewer steps than the accent.** Most render four roles: a background, a border, a solid fill and text. Generate the full ramp only where the product styles status components across the whole range.
|
|
63
|
+
|
|
64
|
+
Status color is never the only signal of a state change; pair it with an icon or text. `hr-better-accessibility` owns that requirement.
|
|
65
|
+
|
|
66
|
+
## Auditing an existing palette
|
|
67
|
+
|
|
68
|
+
Before restructuring a system, inventory it. Most codebases hold several times more colors than the design has decisions.
|
|
69
|
+
|
|
70
|
+
1. **Collect every literal.** Grep for hex, `rgb(`, `hsl(`, `oklch(` and the project's utility-class prefixes. Include SVG `fill`/`stroke`, chart configs and email templates. Colors hide outside stylesheets.
|
|
71
|
+
2. **Sort by perceived lightness within each hue family.** Duplicates surface immediately as near-identical neighbors.
|
|
72
|
+
3. **Collapse near-duplicates.** Two colors closer than about one ramp step are one color that drifted. Keep the one used most and retire the others. Never average them.
|
|
73
|
+
4. **Assign each survivor a role** from the table above. A color matching no role is a missing token or a mistake. Decide which, and say so in the finding.
|
|
74
|
+
5. **Count what is left.** More than one ramp per role above means the palette outgrew its structure, not that the product needs more color.
|
|
75
|
+
|
|
76
|
+
Report the inventory before changing anything. Consolidating a palette changes rendered output on screens nobody asked you to touch, so it stays a proposal until the user accepts it.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Token naming
|
|
2
|
+
|
|
3
|
+
Naming is what makes a palette usable by anyone who did not build it. For which ramps exist and what each step does, see [palette-structure.md](palette-structure.md).
|
|
4
|
+
|
|
5
|
+
## Two tiers
|
|
6
|
+
|
|
7
|
+
**Primitives** name a value. They are the ramp, named by hue and step: `--blue-500`, `--neutral-200`. A primitive describes what the color _is_, so it never changes meaning between themes and is never applied directly in a component.
|
|
8
|
+
|
|
9
|
+
**Semantics** name a job. They point at a primitive and take the name of the role they fill: `--color-text-secondary`, `--color-border-subtle`. Components only ever reference this tier.
|
|
10
|
+
|
|
11
|
+
```css
|
|
12
|
+
:root {
|
|
13
|
+
/* Tier 1: primitives, named by appearance. Never used directly. */
|
|
14
|
+
--blue-500: #3b82f6;
|
|
15
|
+
--neutral-200: #e5e7eb;
|
|
16
|
+
--neutral-700: #374151;
|
|
17
|
+
|
|
18
|
+
/* Tier 2: semantics, named by role. This is what components use. */
|
|
19
|
+
--color-accent-solid: var(--blue-500);
|
|
20
|
+
--color-border: var(--neutral-200);
|
|
21
|
+
--color-text-secondary: var(--neutral-700);
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The tiering is what makes theming possible. Dark mode, a white-label theme and an increased-contrast variant all repoint the semantic tier, leaving the primitives and every component untouched. A codebase applying `--blue-500` directly in components has no theming seam. Adding one later means auditing every usage to work out which meant "the accent" and which just wanted blue.
|
|
26
|
+
|
|
27
|
+
Add a third, component-level tier (`--color-button-danger-bg`) only where a component genuinely and intentionally diverges from the system. One component token is a documented exception; twenty mean the semantic tier is missing roles.
|
|
28
|
+
|
|
29
|
+
## The role inventory
|
|
30
|
+
|
|
31
|
+
A system is complete when every role below has a token. Build against this list rather than adding tokens as components demand them, or the palette ends up shaped like whichever screen came first.
|
|
32
|
+
|
|
33
|
+
| Group | Roles |
|
|
34
|
+
| -------- | ----------------------------------------------------------------------------------------- |
|
|
35
|
+
| Surfaces | page background, surface, raised (menus, popovers), sunken (inputs, wells), overlay scrim |
|
|
36
|
+
| Text | primary, secondary, disabled, inverse, on-accent |
|
|
37
|
+
| Borders | subtle, default, strong, focus ring, separator |
|
|
38
|
+
| Accent | subtle background, border, solid, solid hover, text |
|
|
39
|
+
| Status | per status shipped: subtle background, border, solid, text |
|
|
40
|
+
|
|
41
|
+
Separator and border are separate roles even when they share a value today. A separator divides content; a border encloses a control. They diverge the first time someone restyles inputs, and a system that conflated them gets untangled at that moment.
|
|
42
|
+
|
|
43
|
+
## Naming grammar
|
|
44
|
+
|
|
45
|
+
Use one shape and never deviate: `--color-{role}-{variant}-{state}`.
|
|
46
|
+
|
|
47
|
+
```css
|
|
48
|
+
--color-bg-surface
|
|
49
|
+
--color-text-secondary
|
|
50
|
+
--color-border-strong
|
|
51
|
+
--color-accent-solid-hover
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Pick one word per concept and use only that word. Consistency matters more than the vocabulary. A reader who has seen `--color-text-primary` must be able to guess `--color-text-disabled` without looking:
|
|
55
|
+
|
|
56
|
+
| Concept | Pick one | Never mix in |
|
|
57
|
+
| ----------- | -------- | ------------------------------------------------ |
|
|
58
|
+
| Foreground | `text` | `fg`, `foreground`, `content`, `ink` |
|
|
59
|
+
| Background | `bg` | `background`, `surface` as a synonym, `fill` |
|
|
60
|
+
| Edge | `border` | `stroke`, `outline`, `line` |
|
|
61
|
+
| Brand color | `accent` | `primary`, `brand`, `theme` used interchangeably |
|
|
62
|
+
|
|
63
|
+
Reserve `primary` for exactly one meaning. `--color-text-primary` for body text beside `--color-primary` for the brand is the most common naming collision there is, and it makes every `primary` token ambiguous until you open its definition. Use `accent` for the brand and let `primary` mean "the most prominent of its group".
|
|
64
|
+
|
|
65
|
+
## Anti-patterns
|
|
66
|
+
|
|
67
|
+
| Name | Problem | Instead |
|
|
68
|
+
| -------------------------------- | ----------------------------------------------------------------------- | ------------------------------ |
|
|
69
|
+
| `--color-blue-button` | Appearance at the semantic tier; lies the moment the brand changes | `--color-accent-solid` |
|
|
70
|
+
| `--color-sidebar-gray` | Named for where it was used first; the second usage makes it nonsense | `--color-bg-surface` |
|
|
71
|
+
| `--color-light-gray` | Lies in dark mode, where it is the dark one | `--neutral-200` as a primitive |
|
|
72
|
+
| `--color-text-2` | Numbered semantics carry no meaning; nobody can guess what `3` would be | `--color-text-secondary` |
|
|
73
|
+
| `--color-gray-hover` | Mixes a hue with a state and belongs to no tier | `--color-bg-surface-hover` |
|
|
74
|
+
| `--blue-500` used in a component | Skips the semantic tier and removes the theming seam | Point a semantic token at it |
|
|
75
|
+
|
|
76
|
+
Every one of them is a case of **Use a token only in its role**. See [color-usage.md](color-usage.md).
|
|
77
|
+
|
|
78
|
+
## In Tailwind projects
|
|
79
|
+
|
|
80
|
+
Tailwind v4 generates utilities from `@theme`, so names declared there become the API. Declare primitives and semantics in the same block; the `--color-*` namespace is what produces `bg-*`, `text-*` and `border-*`:
|
|
81
|
+
|
|
82
|
+
```css
|
|
83
|
+
@theme {
|
|
84
|
+
/* Primitives */
|
|
85
|
+
--color-brand-50: #eff6ff;
|
|
86
|
+
--color-brand-500: #3b82f6;
|
|
87
|
+
--color-brand-900: #1e3a8a;
|
|
88
|
+
|
|
89
|
+
/* Semantics: what templates should use */
|
|
90
|
+
--color-accent-solid: var(--color-brand-500);
|
|
91
|
+
--color-text-secondary: var(--color-neutral-700);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
That yields `bg-accent-solid` and `text-text-secondary` alongside `bg-brand-500`, because Tailwind v4 appends everything after `--color-` to the utility prefix. Both are reachable, so the discipline is a convention rather than a constraint. Templates use the semantic utilities, and a raw `bg-brand-500` in a component is the thing to flag.
|
|
96
|
+
|
|
97
|
+
Opacity modifiers work on either tier, as in `bg-accent-solid/50`. But a color carrying alpha cannot be contrast-checked against a static background, because what it renders depends on what sits behind it. Use solid tokens for anything with text on it.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jakub Krehel
|
|
4
|
+
Copyright (c) 2026 Emil Kowalski
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Source notices
|
|
2
|
+
|
|
3
|
+
This notice accompanies the hr-better-layout bundle when copied on its own.
|
|
4
|
+
|
|
5
|
+
## jakubkrehel/skills
|
|
6
|
+
|
|
7
|
+
Source: https://github.com/jakubkrehel/skills (compared at revision `267330e1adfc66a718fb65fa6918c1f06d0a689e`)
|
|
8
|
+
|
|
9
|
+
- `skills/hr-better-accessibility/`, `skills/hr-better-colors/`, `skills/hr-better-layout/`,
|
|
10
|
+
`skills/hr-better-typography/` and `skills/hr-better-writing/`: adapted skill bundles,
|
|
11
|
+
including their supporting references and discovery metadata.
|
|
12
|
+
|
|
13
|
+
Copyright (c) 2026 Jakub Krehel
|
|
14
|
+
|
|
15
|
+
## emilkowalski/skills
|
|
16
|
+
|
|
17
|
+
Source: https://github.com/emilkowalski/skills (compared at revision `e8a175de22ae1e49370fc144c1f3bb9aeedf988d`)
|
|
18
|
+
|
|
19
|
+
- `skills/hr-better-accessibility/` and `skills/hr-better-layout/`: mobile behavior and
|
|
20
|
+
viewport guidance adapted from `skills/mobile-native/SKILL.md`.
|
|
21
|
+
- `skills/hr-better-layout/`, `skills/hr-better-typography/` and `skills/hr-better-writing/`:
|
|
22
|
+
clipping, truncation and plural-form guidance adapted from `skills/break-ui/SKILL.md`.
|
|
23
|
+
|
|
24
|
+
Copyright (c) 2026 Emil Kowalski
|
|
25
|
+
|
|
26
|
+
## house-rules and MIT permission notice
|
|
27
|
+
|
|
28
|
+
MIT License
|
|
29
|
+
|
|
30
|
+
Copyright (c) 2026 Aaron Queen
|
|
31
|
+
|
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
33
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
34
|
+
in the Software without restriction, including without limitation the rights
|
|
35
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
36
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
37
|
+
furnished to do so, subject to the following conditions:
|
|
38
|
+
|
|
39
|
+
The above copyright notice and this permission notice shall be included in all
|
|
40
|
+
copies or substantial portions of the Software.
|
|
41
|
+
|
|
42
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
43
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
44
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
45
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
46
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
47
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
48
|
+
SOFTWARE.
|