@mohammadhprp/system-prompt 0.12.0 → 0.12.2
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/framework/agents/README.md +0 -1
- package/framework/commands/mr.md +8 -9
- package/framework/mcps/README.md +0 -3
- package/framework/skills/README.md +5 -4
- package/framework/skills/effective-html/SKILL.md +63 -0
- package/framework/skills/effective-html/examples.md +19 -0
- package/framework/skills/effective-html/references/charts-and-data.md +32 -0
- package/framework/skills/effective-html/references/creative-direction.md +48 -0
- package/framework/skills/effective-html/references/design-artifact.md +78 -0
- package/framework/skills/effective-html/references/diagrams.md +68 -0
- package/framework/skills/effective-html/references/documents-and-presentations.md +28 -0
- package/framework/skills/effective-html/references/html-diagram.md +43 -0
- package/framework/skills/effective-html/references/html-plan.md +40 -0
- package/framework/skills/effective-html/references/html-prototype.md +97 -0
- package/framework/skills/effective-html/references/html-wireframe.md +81 -0
- package/framework/skills/effective-html/references/html.md +72 -0
- package/framework/skills/effective-html/references/interfaces.md +17 -0
- package/framework/skills/glab/SKILL.md +222 -0
- package/framework/skills/glab/references/commands-detailed.md +616 -0
- package/framework/skills/glab/references/quick-reference.md +145 -0
- package/framework/skills/glab/references/troubleshooting.md +669 -0
- package/framework/skills/great-interface/SKILL.md +43 -0
- package/framework/skills/great-interface/references/animations.md +205 -0
- package/framework/skills/great-interface/references/better-accessibility.md +106 -0
- package/framework/skills/great-interface/references/better-colors.md +100 -0
- package/framework/skills/great-interface/references/better-interface.md +132 -0
- package/framework/skills/great-interface/references/better-layout.md +76 -0
- package/framework/skills/great-interface/references/better-typography.md +157 -0
- package/framework/skills/great-interface/references/better-ui.md +107 -0
- package/framework/skills/great-interface/references/better-writing.md +109 -0
- package/framework/skills/great-interface/references/choosing-fonts.md +64 -0
- package/framework/skills/great-interface/references/color-formats.md +90 -0
- package/framework/skills/great-interface/references/color-usage.md +118 -0
- package/framework/skills/great-interface/references/contrast.md +79 -0
- package/framework/skills/great-interface/references/css-cheat-sheet.md +65 -0
- package/framework/skills/great-interface/references/details-and-accessibility.md +119 -0
- package/framework/skills/great-interface/references/enter-exit.md +147 -0
- package/framework/skills/great-interface/references/explain-interface.md +126 -0
- package/framework/skills/great-interface/references/find-the-effect.md +94 -0
- package/framework/skills/great-interface/references/focus-and-keyboard.md +131 -0
- package/framework/skills/great-interface/references/forms.md +84 -0
- package/framework/skills/great-interface/references/from-an-image.md +55 -0
- package/framework/skills/great-interface/references/grouping-and-alignment.md +123 -0
- package/framework/skills/great-interface/references/hit-areas.md +94 -0
- package/framework/skills/great-interface/references/icon-transitions.md +102 -0
- package/framework/skills/great-interface/references/icons.md +110 -0
- package/framework/skills/great-interface/references/interface-review.md +148 -0
- package/framework/skills/great-interface/references/motion-and-zoom.md +79 -0
- package/framework/skills/great-interface/references/no-browser.md +73 -0
- package/framework/skills/great-interface/references/palette-generation.md +104 -0
- package/framework/skills/great-interface/references/palette-structure.md +76 -0
- package/framework/skills/great-interface/references/performance.md +88 -0
- package/framework/skills/great-interface/references/picker.md +76 -0
- package/framework/skills/great-interface/references/read-the-system.md +178 -0
- package/framework/skills/great-interface/references/removed-signals.md +38 -0
- package/framework/skills/great-interface/references/review-format.md +46 -0
- package/framework/skills/great-interface/references/scope-resolution.md +88 -0
- package/framework/skills/great-interface/references/screen-readers.md +101 -0
- package/framework/skills/great-interface/references/semantics-and-aria.md +84 -0
- package/framework/skills/great-interface/references/spacing-and-adaptivity.md +159 -0
- package/framework/skills/great-interface/references/spacing-and-sizing.md +121 -0
- package/framework/skills/great-interface/references/surfaces.md +219 -0
- package/framework/skills/great-interface/references/token-naming.md +97 -0
- package/framework/skills/great-interface/references/variable-fonts-and-opentype.md +105 -0
- package/framework/skills/great-interface/references/variant.md +104 -0
- package/framework/skills/great-interface/references/wrapping-and-punctuation.md +55 -0
- package/framework/skills/improve/SKILL.md +137 -0
- package/framework/skills/improve/examples.md +19 -0
- package/framework/skills/improve/references/audit-playbook.md +130 -0
- package/framework/skills/improve/references/closing-the-loop.md +96 -0
- package/framework/skills/improve/references/plan-template.md +197 -0
- package/framework/skills/jira-cli/SKILL.md +260 -0
- package/framework/skills/jira-cli/references/commands-detailed.md +268 -0
- package/framework/skills/jira-cli/references/quick-reference.md +111 -0
- package/framework/skills/jira-cli/references/troubleshooting.md +114 -0
- package/framework/styles/README.md +9 -4
- package/framework/styles/factory/DESIGN.md +360 -0
- package/framework/styles/factory/README.md +32 -0
- package/framework/styles/factory/assets/preview.jpg +0 -0
- package/framework/styles/huly/DESIGN.md +449 -0
- package/framework/styles/huly/README.md +32 -0
- package/framework/styles/huly/assets/preview.jpg +0 -0
- package/framework/styles/notion/DESIGN.md +423 -0
- package/framework/styles/notion/README.md +32 -0
- package/framework/styles/notion/assets/preview.jpg +0 -0
- package/package.json +1 -1
- package/src/catalog.js +8 -8
- package/framework/agents/backend-architect.md +0 -146
- package/framework/mcps/github-mcp/README.md +0 -51
- package/framework/mcps/github-mcp/capabilities.md +0 -83
- package/framework/mcps/github-mcp/configs/.env.example +0 -1
- package/framework/mcps/github-mcp/configs/opencode.json +0 -13
- package/framework/mcps/github-mcp/install.md +0 -60
- package/framework/mcps/github-mcp/troubleshooting.md +0 -79
- package/framework/mcps/gitlab-mcp/README.md +0 -53
- package/framework/mcps/gitlab-mcp/capabilities.md +0 -216
- package/framework/mcps/gitlab-mcp/configs/.env.example +0 -2
- package/framework/mcps/gitlab-mcp/configs/opencode.json +0 -13
- package/framework/mcps/gitlab-mcp/install.md +0 -99
- package/framework/mcps/gitlab-mcp/troubleshooting.md +0 -116
- package/framework/mcps/jira-mcp/README.md +0 -52
- package/framework/mcps/jira-mcp/capabilities.md +0 -79
- package/framework/mcps/jira-mcp/configs/.env.example +0 -2
- package/framework/mcps/jira-mcp/configs/opencode.json +0 -13
- package/framework/mcps/jira-mcp/install.md +0 -94
- package/framework/mcps/jira-mcp/troubleshooting.md +0 -113
- package/framework/skills/brand-guidelines/LICENSE.txt +0 -202
- package/framework/skills/brand-guidelines/SKILL.md +0 -73
- package/framework/skills/gitlab-mcp/SKILL.md +0 -83
- package/framework/skills/gitlab-mcp/examples.md +0 -31
- package/framework/skills/gitlab-mcp/references/code-review.md +0 -110
- package/framework/skills/gitlab-mcp/references/issues.md +0 -141
- package/framework/skills/gitlab-mcp/references/merge-requests.md +0 -120
- package/framework/skills/gitlab-mcp/references/pipelines.md +0 -67
- package/framework/skills/gitlab-mcp/references/search.md +0 -17
- package/framework/skills/gitlab-mcp/references/webhooks.md +0 -32
- package/framework/skills/gitlab-mcp/references/work-items.md +0 -50
- package/framework/skills/jira-mcp/SKILL.md +0 -57
- package/framework/skills/jira-mcp/examples.md +0 -31
- package/framework/skills/jira-mcp/references/comments.md +0 -27
- package/framework/skills/jira-mcp/references/issues.md +0 -97
- package/framework/skills/jira-mcp/references/projects.md +0 -39
- package/framework/skills/lavish/SKILL.md +0 -67
- package/framework/skills/lavish/examples.md +0 -31
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Typography
|
|
2
|
+
|
|
3
|
+
Typography is mostly restraint: a sensible scale, comfortable spacing, enough contrast. A label, a table cell, a marketing headline and an article paragraph do not share one set of rules.
|
|
4
|
+
|
|
5
|
+
When reviewing, read the rendered page instead of scanning the code. Bad wrapping, widows and truncation only show up at real content lengths.
|
|
6
|
+
|
|
7
|
+
Write every fix in the project's styling system, and use the exact values below rather than familiar-looking equivalents. The [cheat sheet]() maps each declaration to its Tailwind equivalent.
|
|
8
|
+
|
|
9
|
+
The words themselves belong to `better-writing`. Semantic heading structure belongs to `better-accessibility`. Spatial RTL layout and logical properties belong to `better-layout`. Contrast measurement belongs to `better-colors`. This skill owns how text renders, wraps and behaves in mixed-direction content.
|
|
10
|
+
|
|
11
|
+
## Serve the right format
|
|
12
|
+
|
|
13
|
+
Use `.woff2` on the web, for Brotli compression and broad support. `.woff` is a fallback for very old browsers. `.ttf` and `.otf` are desktop formats with no web compression. How the files load is the project's concern.
|
|
14
|
+
|
|
15
|
+
## Properties over raw tags
|
|
16
|
+
|
|
17
|
+
When a CSS property exists, use it. `font-weight: 650` instead of `font-variation-settings: "wght" 650`. `font-optical-sizing: auto` instead of `"opsz"`. `font-variant-numeric: tabular-nums` instead of `font-feature-settings: "tnum" 1`.
|
|
18
|
+
|
|
19
|
+
Properties keep working when a non-variable fallback renders. Reserve raw tags for custom axes (`"GRAD" 80`) and niche features (`"ss01" 1`) with no property of their own. Axes and feature tags are listed in [variable-fonts-and-opentype.md]().
|
|
20
|
+
|
|
21
|
+
## Load intended weights and styles
|
|
22
|
+
|
|
23
|
+
Browsers synthesize a weight or style the active family doesn't provide, distorting the real face. Load the faces the design uses.
|
|
24
|
+
|
|
25
|
+
`font-synthesis: none` turns synthesis off, but it erases emphasis rather than reporting it. Set it only after checking every required bold, italic, small-cap, superscript and subscript form stays distinct across the fallback stack.
|
|
26
|
+
|
|
27
|
+
## Fewer fonts, sizes and weights
|
|
28
|
+
|
|
29
|
+
Rarely use more than three fonts. Weight and size define hierarchy; overusing them hurts readability fast. Pair for contrast, not similarity: a serif headline over a sans body reads as deliberate, two near-identical sans-serifs read as a mistake.
|
|
30
|
+
|
|
31
|
+
Below `18px`, stay at weight `400` or heavier. Weights under `300` are display-only at `28px`+; they disappear at text sizes. Pairing guidance is in [choosing-fonts.md]().
|
|
32
|
+
|
|
33
|
+
## Use a type scale with semantic names
|
|
34
|
+
|
|
35
|
+
Define a small set of sizes and deviate from it as little as possible. Hard-coded sizes with no system behind them break down at scale.
|
|
36
|
+
|
|
37
|
+
Solo, default names like `text-sm` are fine when the usage rules are clear. On a team, name sizes by use (`text-body-sm`) so the rules survive other people. Scale construction is in [spacing-and-sizing.md]().
|
|
38
|
+
|
|
39
|
+
## Heading sizes descend with level
|
|
40
|
+
|
|
41
|
+
Map heading levels to descending steps of the type scale, so a visually subordinate heading never overpowers its parent. Adjacent levels may share a size toward the small end of the scale, as long as weight or spacing keeps them distinct. The semantic element is `better-accessibility`'s; this skill sets only the visual treatment.
|
|
42
|
+
|
|
43
|
+
## Line-height by role
|
|
44
|
+
|
|
45
|
+
Headings tighter, around `1.1`. Body copy `1.5` to `1.6`. Prefer unitless values, so line-height scales with the font size; a fixed `24px` does not.
|
|
46
|
+
|
|
47
|
+
Tight line-height is for short text. Anything that wraps to three or more lines needs at least `1.4`, even in a height-constrained row.
|
|
48
|
+
|
|
49
|
+
## Letter-spacing by size
|
|
50
|
+
|
|
51
|
+
Large headings often look better with slightly negative letter-spacing. Small uppercase labels need a little positive letter-spacing, or the letters feel crowded. Body copy at reading sizes needs neither.
|
|
52
|
+
|
|
53
|
+
## Cap the measure
|
|
54
|
+
|
|
55
|
+
Long lines make it hard for the eye to find the next one. Cap long-form text around 60–75 characters per line. Any unit works, as long as a cap exists and the line length lands in range. See [unit choices and the pixel equivalents]().
|
|
56
|
+
|
|
57
|
+
## Wrap deliberately
|
|
58
|
+
|
|
59
|
+
Four declarations, four jobs:
|
|
60
|
+
|
|
61
|
+
- `text-wrap: balance` distributes text evenly across lines. Use it on headings.
|
|
62
|
+
- `text-wrap: pretty` stops a single short word landing on the final line. Use it on descriptions.
|
|
63
|
+
- `overflow-wrap: break-word` where a long word, link, or ID could escape the container.
|
|
64
|
+
- `white-space: nowrap` on labels and badges where a line break looks broken.
|
|
65
|
+
|
|
66
|
+
Skip `balance` and `pretty` in long-form text.
|
|
67
|
+
|
|
68
|
+
## Tabular numbers on changing values
|
|
69
|
+
|
|
70
|
+
Digits have different widths by default, so timers, counters and prices shift the layout as they update. Apply `font-variant-numeric: tabular-nums` to any value that changes.
|
|
71
|
+
|
|
72
|
+
## Truncate without losing content
|
|
73
|
+
|
|
74
|
+
For a single line, `text-overflow: ellipsis` with `overflow: hidden` and `white-space: nowrap`. For several, `line-clamp`. Truncation hides content. When the missing text matters, keep the full value reachable in a tooltip or an expanded view.
|
|
75
|
+
|
|
76
|
+
## Write copy naturally, style with CSS
|
|
77
|
+
|
|
78
|
+
Store text in natural case and control presentation with `text-transform`, so a redesign never means rewriting copy.
|
|
79
|
+
|
|
80
|
+
Use smart punctuation in rendered text:
|
|
81
|
+
|
|
82
|
+
- Curly quotes in prose, straight quotes in code.
|
|
83
|
+
- An en dash for ranges: `2010–2020`.
|
|
84
|
+
- The single ellipsis character, not three periods.
|
|
85
|
+
- ` ` to hold `16 px` together across a line break.
|
|
86
|
+
- `­` to say where a long word may break.
|
|
87
|
+
|
|
88
|
+
## Underlines from the font
|
|
89
|
+
|
|
90
|
+
Default underlines sit wherever the browser decides. Pull position and thickness from the font's own metrics with `text-underline-position: from-font` and `text-decoration-thickness: from-font`. Tune by hand with `text-decoration-thickness`, `text-underline-offset` and `text-decoration-skip-ink`.
|
|
91
|
+
|
|
92
|
+
`text-decoration-style` draws the line dotted, dashed, or wavy. A dotted underline is a common hint that a word carries extra information, such as an abbreviation or a defined term.
|
|
93
|
+
|
|
94
|
+
Color is the only part of a real underline that animates reliably. So unless the only thing animating is the color, build the underline as a separate element rather than using `text-decoration`.
|
|
95
|
+
|
|
96
|
+
## Inputs at 16px on mobile
|
|
97
|
+
|
|
98
|
+
iOS Safari zooms the whole page when an input's text is smaller than `16px`. Two fixes hold the size at `16px` and look different, so ask which one the design wants:
|
|
99
|
+
|
|
100
|
+
- Size the input up on mobile (`text-base sm:text-sm`). Changes how it looks on small screens.
|
|
101
|
+
- Keep `font-size: 16px` and render the intended size with `transform: scale()`, compensating width and `line-height`. Identical at every viewport, more code to maintain.
|
|
102
|
+
|
|
103
|
+
Both recipes are in [details-and-accessibility.md]().
|
|
104
|
+
|
|
105
|
+
## Size and contrast floors
|
|
106
|
+
|
|
107
|
+
Start long-form body text at `16px`, the browser default. Move off it only for a reason you can name: the typeface runs small, the measure is narrow, or the product is a dense professional tool.
|
|
108
|
+
|
|
109
|
+
UI text can go smaller. `14px` is a useful starting point for inputs and menus, `13px` for captions and rarely below `12px`. Inputs still need `16px` on mobile.
|
|
110
|
+
|
|
111
|
+
When text looks low-contrast, use `better-colors` to measure the rendered pair and `better-accessibility` to classify the requirement. Leave the colors alone unless asked.
|
|
112
|
+
|
|
113
|
+
## Font smoothing on the root
|
|
114
|
+
|
|
115
|
+
On macOS, text renders heavier than intended. Apply `-webkit-font-smoothing: antialiased` and `-moz-osx-font-smoothing: grayscale` once on the root layout, never per component. Tailwind's `antialiased` covers both.
|
|
116
|
+
|
|
117
|
+
## Language and bidi behavior
|
|
118
|
+
|
|
119
|
+
Set `lang` so browsers and assistive technology pick the right pronunciation, quotes and hyphenation. Set `dir` at the document or at the content boundary where direction changes. Preserve digit order, and use `<bdi>` to isolate a mixed-direction value. Spatial mirroring and logical CSS properties belong to `better-layout`.
|
|
120
|
+
|
|
121
|
+
## Keep useful text selectable
|
|
122
|
+
|
|
123
|
+
Keep text selectable by default. `::selection` can carry brand into the reading experience, as long as the selected combination stays legible.
|
|
124
|
+
|
|
125
|
+
`user-select: none` belongs on a draggable or gesture-driven surface where accidental selection interferes. Never across the interface and never because a button label can be highlighted.
|
|
126
|
+
|
|
127
|
+
## Before you finish
|
|
128
|
+
|
|
129
|
+
| Mistake | Fix |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| Synthesized face differs from the design | Load the real face; disable only the verified synthesis mode |
|
|
132
|
+
| Child heading visually overpowers its parent | Map that section's hierarchy to descending scale steps |
|
|
133
|
+
| Heading element picked for its default size | Choose semantics first, then set the size in CSS |
|
|
134
|
+
| Orphan on the last line of a paragraph | `text-wrap: pretty` |
|
|
135
|
+
| Lopsided two-line heading | `text-wrap: balance` |
|
|
136
|
+
| Justified text in an interface | `text-align: start`; reserve justify for specific editorial layouts |
|
|
137
|
+
| Underline cuts through descenders | `text-decoration-skip-ink: auto`, `from-font` metrics |
|
|
138
|
+
| Mixed-direction value renders in the wrong order | Correct `lang`/`dir`; isolate the value with `<bdi>` |
|
|
139
|
+
| Selection disabled across application chrome | Restore it; suppress only where it conflicts with a drag or gesture |
|
|
140
|
+
| Extra-info hint with no visual cue | Dotted underline via `text-decoration-style: dotted` |
|
|
141
|
+
| Thin/Light weight on `14px` UI text | Weight `400`+ below `18px`; thin weights are display-only |
|
|
142
|
+
| `leading-none` on a three-line card description | At least `1.4` on any text that wraps to 3+ lines |
|
|
143
|
+
|
|
144
|
+
## Reporting
|
|
145
|
+
|
|
146
|
+
**Severity.** `HIGH` makes text unreadable or truncates content with no way to recover it. `MEDIUM` breaks the type system or the heading hierarchy. `LOW` is isolated polish.
|
|
147
|
+
|
|
148
|
+
**Verification.** Without a browser: computed size and weight for each heading level, checked descending; declared line-height and measure; truncation rules against realistic string lengths. With one: resize the viewport to catch wrapping, widows and truncation at real content lengths. Report every check you could not run as `Not verified`.
|
|
149
|
+
|
|
150
|
+
**Format.** Group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in:
|
|
151
|
+
|
|
152
|
+
| Severity | Location | Before | After | Why |
|
|
153
|
+
| --- | --- | --- | --- | --- |
|
|
154
|
+
|
|
155
|
+
`Location` is `path/to/file:line`. `Why` names the principle and the user impact.
|
|
156
|
+
|
|
157
|
+
End with `Block` when any `HIGH` remains, `Approve` otherwise, leaving the rest in the table as work to do. Never `Approve` coverage you did not inspect. With nothing to report, state "No actionable typography findings" and report verification.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# UI polish
|
|
2
|
+
|
|
3
|
+
Polish comes from a pile of small details that compound. This skill is the reference for which are worth having and what values they take.
|
|
4
|
+
|
|
5
|
+
When reviewing, slow the interface down. What feels off at 10% speed is what is subtly wrong at full speed.
|
|
6
|
+
|
|
7
|
+
Keep the project's component library, tokens and density, and match its motion language except where a rule below prescribes an exact interaction.
|
|
8
|
+
|
|
9
|
+
Every duration, curve, scale and blur below is a specific value, not a range to approximate. `cubic-bezier(0.2, 0, 0, 1)` is not `cubic-bezier(0.4, 0, 0.2, 1)`, and `0.96` is not `0.95`. Use what is written.
|
|
10
|
+
|
|
11
|
+
Text wrapping, font rendering, tabular numbers and text spacing belong to `better-typography`. Hit areas, focus, keyboard support, ARIA and reduced motion belong to `better-accessibility`. Grouping, section spacing, breakpoints and spatial RTL belong to `better-layout`.
|
|
12
|
+
|
|
13
|
+
## Concentric border radius
|
|
14
|
+
|
|
15
|
+
Outer radius = inner radius + padding. Mismatched radii on nested elements is the most common thing that makes an interface feel off. Radius, shadow and outline recipes are in [surfaces.md]().
|
|
16
|
+
|
|
17
|
+
## Optical over geometric alignment
|
|
18
|
+
|
|
19
|
+
When geometric centering looks off, align optically. Buttons with icons, play triangles and asymmetric icons all need a manual nudge.
|
|
20
|
+
|
|
21
|
+
## Shadows for elevation, borders for structure
|
|
22
|
+
|
|
23
|
+
Where a border exists only to create depth, prefer layered transparent `box-shadow` values. Keep borders that communicate structure or state: dividers, separators and selected or focus states.
|
|
24
|
+
|
|
25
|
+
## Interruptible animations
|
|
26
|
+
|
|
27
|
+
Use CSS transitions for interactive state changes, because they can be interrupted mid-animation. Reserve keyframes for staged sequences that run once.
|
|
28
|
+
|
|
29
|
+
## Split and stagger enter animations
|
|
30
|
+
|
|
31
|
+
For an infrequent staged entrance where sequence communicates hierarchy, break the content into semantic chunks and stagger them by ~100ms. Animating one container gets you less for the same cost. Leave high-frequency interactions unstaggered. See [enter-exit.md]().
|
|
32
|
+
|
|
33
|
+
## Subtle exit animations
|
|
34
|
+
|
|
35
|
+
Use a small fixed `translateY` rather than full height. Exits should be softer than enters. Use `ease-out` for both directions.
|
|
36
|
+
|
|
37
|
+
## Contextual icon animations
|
|
38
|
+
|
|
39
|
+
Animate icons with `opacity`, `scale` and `blur` rather than toggling visibility. Use exactly these values: scale `0.25` to `1`, opacity `0` to `1`, blur `4px` to `0px`.
|
|
40
|
+
|
|
41
|
+
With a motion library (`motion` or `framer-motion` in `package.json`), match that package's import path, or nearby imports where both exist. Use `transition: { type: "spring", duration: 0.3, bounce: 0 }`. Bounce is always `0`.
|
|
42
|
+
|
|
43
|
+
Without one, keep both icons in the DOM with one absolutely positioned, and cross-fade with `cubic-bezier(0.2, 0, 0, 1)`. That gives you enter and exit with no dependency. Both recipes are in [icon-transitions.md]().
|
|
44
|
+
|
|
45
|
+
## Image outlines
|
|
46
|
+
|
|
47
|
+
Give images a `1px` outline at low opacity for consistent depth. Pure black in light mode (`oklch(0 0 0 / 0.1)`), pure white in dark (`oklch(1 0 0 / 0.1)`). Never a near-black like slate or zinc and never a tinted neutral. A tinted outline picks up the surface underneath and reads as dirt on the image edge.
|
|
48
|
+
|
|
49
|
+
## Scale on press
|
|
50
|
+
|
|
51
|
+
A `scale(0.96)` on click gives a button tactile feedback. Always `0.96`; anything below `0.95` feels exaggerated. Add a `static` prop to switch it off where motion would distract. See [recipes for CSS, Tailwind and Motion]().
|
|
52
|
+
|
|
53
|
+
## Skip animation on page load
|
|
54
|
+
|
|
55
|
+
Use `initial={false}` on `AnimatePresence` to keep enter animations off the first render. Check that it leaves intentional page entrances intact.
|
|
56
|
+
|
|
57
|
+
## Suppress transitions on theme switch
|
|
58
|
+
|
|
59
|
+
A theme flip changes color, background, border and shadow on nearly every element at once. Every transition on those properties fires together and the switch smears instead of snapping. Inject `*,*::before,*::after{transition:none !important}`, force a reflow, then remove it on the next frame. See the [recipe]().
|
|
60
|
+
|
|
61
|
+
## Transition only what changes
|
|
62
|
+
|
|
63
|
+
Always name the exact properties: `transition-property: scale, opacity`. Tailwind's `transition-transform` covers `transform, translate, scale, rotate`.
|
|
64
|
+
|
|
65
|
+
## Use `will-change` sparingly
|
|
66
|
+
|
|
67
|
+
Only for `transform`, `opacity` and `filter`, which the GPU can composite. Never `will-change: all`. Add it when you see first-frame stutter, not before. See [performance.md]().
|
|
68
|
+
|
|
69
|
+
## Match icon stroke to text weight
|
|
70
|
+
|
|
71
|
+
An icon next to text carries the text's optical weight: `1.5px` stroke beside regular (400) text, `2px` beside semibold (600). One stroke weight per icon set and one icon library per surface. Sizing and RTL flipping are in [icons.md]().
|
|
72
|
+
|
|
73
|
+
## One SVG, recolored per state
|
|
74
|
+
|
|
75
|
+
Icons use `currentColor` and take hover, selected and disabled states from CSS color and opacity, never from separate assets. Outline is the default variant; fill marks the active state.
|
|
76
|
+
|
|
77
|
+
## Motion restraint
|
|
78
|
+
|
|
79
|
+
Give high-frequency interactions instant feedback, or a transition of `150ms` or less on opacity and color. A custom animation there charges its attention cost on every trigger.
|
|
80
|
+
|
|
81
|
+
Every animated state change also needs a static cue: color, an icon, or a label. Motion is never the only feedback channel.
|
|
82
|
+
|
|
83
|
+
## Before you finish
|
|
84
|
+
|
|
85
|
+
| Mistake | Fix |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| Icons look off-center | Nudge optically with padding, or fix the SVG |
|
|
88
|
+
| Jarring staged entrance or exit | Stagger infrequent entrances; keep exits subtle |
|
|
89
|
+
| Theme toggle crossfades the whole page | Disable transitions for the swap, force a reflow, restore on the next frame |
|
|
90
|
+
| `transition: all` on elements | Specify exact properties |
|
|
91
|
+
| First-frame animation stutter | Add `will-change: transform` (sparingly) |
|
|
92
|
+
| Hairline icon beside bold text | Match the stroke width to the text weight |
|
|
93
|
+
|
|
94
|
+
## Reporting
|
|
95
|
+
|
|
96
|
+
**Severity.** `HIGH` breaks an interaction, makes motion unusable, or leaves a state change visible only while the animation runs. `MEDIUM` is a visible inconsistency in surfaces, icons, or motion. `LOW` is isolated polish.
|
|
97
|
+
|
|
98
|
+
**Verification.** Without a browser: every state the component defines, meaning hover, focus, active, loading and empty, plus motion durations and easings read from the code. With one: walk each state, and replay motion at 10% speed in the browser's Animations panel. Report every check you could not run as `Not verified`.
|
|
99
|
+
|
|
100
|
+
**Format.** Group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in:
|
|
101
|
+
|
|
102
|
+
| Severity | Location | Before | After | Why |
|
|
103
|
+
| --- | --- | --- | --- | --- |
|
|
104
|
+
|
|
105
|
+
`Location` is `path/to/file:line`. `Why` names the principle and the user impact.
|
|
106
|
+
|
|
107
|
+
End with `Block` when any `HIGH` remains, `Approve` otherwise, leaving the rest in the table as work to do. Never `Approve` coverage you did not inspect. With nothing to report, state "No actionable UI-polish findings" and report verification.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Interface writing
|
|
2
|
+
|
|
3
|
+
Clear and brief beats clever; consistent beats varied. The best error message is the interaction redesigned so the error cannot happen.
|
|
4
|
+
|
|
5
|
+
How copy renders (capitalization via `text-transform`, truncation, smart punctuation) belongs to `better-typography`. Error markup and announcements (`aria-invalid`, live regions) belong to `better-accessibility`. Room for translated strings belongs to `better-layout`.
|
|
6
|
+
|
|
7
|
+
## Recon the existing voice
|
|
8
|
+
|
|
9
|
+
Before writing or reviewing, read the copy nearby. Note the product's terminology, its localization conventions and any voice or content style guide.
|
|
10
|
+
|
|
11
|
+
A deliberate brand voice is not a defect. Raise a departure from plain language only when it creates inconsistency, ambiguity, translation risk, or a tone the stakes don't support.
|
|
12
|
+
|
|
13
|
+
## One voice, flexible tone
|
|
14
|
+
|
|
15
|
+
The product has one voice and its existing copy establishes it. A local edit does not get to invent a new one. Keep terms consistent: if it's "Archive" in the menu, it isn't "Move to storage" in the toast. Tone flexes with the stakes:
|
|
16
|
+
|
|
17
|
+
| Context | Tone |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| Success, onboarding, empty states | Warm, can be light |
|
|
20
|
+
| Routine actions, settings | Neutral, minimal |
|
|
21
|
+
| Errors, destructive confirmations | Calm, plain, zero playfulness |
|
|
22
|
+
| Data loss, security | Serious, explicit |
|
|
23
|
+
|
|
24
|
+
## Address the reader directly
|
|
25
|
+
|
|
26
|
+
In instructional copy, write "you", not "the user". In errors, "we" invites ambiguity and reads as deflection, so prefer "Unable to load content" over "We're having trouble loading this content". An established first-person voice can stay in low-stakes copy where it still reads clearly.
|
|
27
|
+
|
|
28
|
+
Use possessives sparingly: "Favorites" beats "Your Favorites". Hold one perspective throughout a flow.
|
|
29
|
+
|
|
30
|
+
## Plain words over clever ones
|
|
31
|
+
|
|
32
|
+
Choose words a tired reader gets on the first pass, and delete every word that does no work. No idioms, no colloquialisms, no humor that won't translate.
|
|
33
|
+
|
|
34
|
+
Skip unnecessary gender: "Subscribers can post recipes", not "each subscriber can post his or her recipes". Match the input device: "tap" on touch, "click" with a pointer, "select" when both are possible.
|
|
35
|
+
|
|
36
|
+
Never assemble a sentence from fragments around a variable (`"You have " + n + " new messages"`), because word order changes per language. Use a full templated string with proper pluralization.
|
|
37
|
+
|
|
38
|
+
## Verb-first buttons
|
|
39
|
+
|
|
40
|
+
A button label starts with a verb naming the action: "Send", "Save draft", "Delete project". Never "OK!", "Let's go!", or a bare "Yes" and "No" on a consequential action.
|
|
41
|
+
|
|
42
|
+
A confirmation button repeats the consequence, so the dialog is answerable without reading the body. "Delete this project?" offers `Delete project` and `Cancel`.
|
|
43
|
+
|
|
44
|
+
## Consistent flow vocabulary
|
|
45
|
+
|
|
46
|
+
A multi-step flow uses one vocabulary throughout: "Get started" to enter, "Continue" or "Next" (pick one) to advance, "Done" to finish. Alternating synonyms makes users wonder whether the buttons do different things.
|
|
47
|
+
|
|
48
|
+
## Links describe their destination
|
|
49
|
+
|
|
50
|
+
Link text has to make sense out of context, because screen-reader users navigate by a list of the page's links. Write "Read the billing docs". "Click here" fails this and the device-verb rule at once.
|
|
51
|
+
|
|
52
|
+
A bare "Learn more" breaks down as soon as two appear on one page. Suffix each one: "Learn more about exports".
|
|
53
|
+
|
|
54
|
+
## One capitalization policy
|
|
55
|
+
|
|
56
|
+
Pick title case or sentence case per element type, then apply it to every instance of that type. Sentence case is the safer default. It is calmer, has no per-word rules to remember and localizes cleanly. "Save Changes" beside "Discard changes" reads as sloppiness.
|
|
57
|
+
|
|
58
|
+
## Settings describe the ON state
|
|
59
|
+
|
|
60
|
+
Label a toggle for what happens when it is on. "Send read receipts" lets users infer the off state; the negative ("Don't send read receipts") turns the toggle into a double negative.
|
|
61
|
+
|
|
62
|
+
Link straight to a referenced setting rather than describing the path to it: a "Notification settings" link, not "Go to Settings > Notifications > Email".
|
|
63
|
+
|
|
64
|
+
## Errors say how to fix, next to where it broke
|
|
65
|
+
|
|
66
|
+
An error is an instruction, and it belongs beside the field that failed:
|
|
67
|
+
|
|
68
|
+
| Bad | Good |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| That password is too short | Choose a password with at least 8 characters |
|
|
71
|
+
| Invalid name | Use only letters for your name |
|
|
72
|
+
| Oops! Something went wrong. | Unable to save. Check your connection and try again. |
|
|
73
|
+
|
|
74
|
+
No blame, no "oops", no exclamation marks. Phrase hints positively ("Use only letters", not "Don't use numbers or symbols") and show them before the mistake, not after. When the same error keeps firing, redesign the interaction instead of rewording it.
|
|
75
|
+
|
|
76
|
+
## Empty states point forward
|
|
77
|
+
|
|
78
|
+
An empty state says what this place is, how to fill it and offers one clear next action:
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<!-- Bad: a shrug -->
|
|
82
|
+
<p>No results.</p>
|
|
83
|
+
|
|
84
|
+
<!-- Good: orientation plus a next step -->
|
|
85
|
+
<p class="font-medium">No projects yet</p>
|
|
86
|
+
<p class="text-sm text-zinc-500">Projects keep your tasks and files together.</p>
|
|
87
|
+
<button class="mt-4">Create a project</button>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A search or filter empty state names the query and offers an exit: "No results for 'quarterly'. Clear filters". Never park persistent information in an empty state. It disappears the moment content exists.
|
|
91
|
+
|
|
92
|
+
## Placeholders are examples, not labels
|
|
93
|
+
|
|
94
|
+
A placeholder shows the expected format: `name@example.com`, `DD/MM/YYYY`. It vanishes on input, so it is never the only label. Every field keeps a visible one.
|
|
95
|
+
|
|
96
|
+
## Reporting
|
|
97
|
+
|
|
98
|
+
**Severity.** `HIGH` misleads the user or hides how to recover from an error. `MEDIUM` breaks voice, terminology, or capitalization consistency. `LOW` is isolated wording polish.
|
|
99
|
+
|
|
100
|
+
**Verification.** Source alone is enough here. Check every label against the action it invokes, every error for a stated fix and terminology against the copy around it. No browser check is required.
|
|
101
|
+
|
|
102
|
+
**Format.** Group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in:
|
|
103
|
+
|
|
104
|
+
| Severity | Location | Before | After | Why |
|
|
105
|
+
| --- | --- | --- | --- | --- |
|
|
106
|
+
|
|
107
|
+
`Location` is `path/to/file:line`. `Why` names the principle and the user impact.
|
|
108
|
+
|
|
109
|
+
End with `Block` when any `HIGH` remains, `Approve` otherwise, leaving the rest in the table as work to do. Never `Approve` coverage you did not inspect. With nothing to report, state "No actionable writing findings" and report verification.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Choosing fonts
|
|
2
|
+
|
|
3
|
+
Choosing a typeface, the right file format and why fonts look the way they do.
|
|
4
|
+
|
|
5
|
+
## Choosing a typeface
|
|
6
|
+
|
|
7
|
+
Font families set the tone before the specific font does.
|
|
8
|
+
|
|
9
|
+
| Category | Traits | Use for |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Serif | Small strokes at the ends of letters guide the eye along a line | Long passages, editorial reading |
|
|
12
|
+
| Sans-serif | Clean, even shapes that stay crisp at small sizes | Default for most interfaces (Helvetica, Inter, Geist) |
|
|
13
|
+
| Monospace | Every glyph the same width so columns line up | Code, tables, tabular data |
|
|
14
|
+
| Display | Drawn for large headlines | Marketing headlines, hero text |
|
|
15
|
+
| Script | Mimics handwriting | Rare, decorative moments |
|
|
16
|
+
|
|
17
|
+
CSS exposes `cursive` and `fantasy` keywords for the last two categories.
|
|
18
|
+
|
|
19
|
+
"Display" in a font's name does not make it a display font. SF Pro and Heldane ship a `Display` variant for large sizes and a `Text` variant for small ones. Use the variant matching the size you are setting.
|
|
20
|
+
|
|
21
|
+
### Rules
|
|
22
|
+
|
|
23
|
+
- Fewer fonts is usually better. Rarely use more than three. Marketing pages can be more expressive than apps.
|
|
24
|
+
- The same applies to sizes and weights. They define hierarchy, and overusing them hurts readability fast.
|
|
25
|
+
- Pair for contrast, not similarity. A serif headline over a sans body reads as a deliberate display and reading split; two near-identical sans-serifs read as a mistake.
|
|
26
|
+
- Thin weights are display-only. Below `18px` stay at weight `400`+, because Ultralight, Thin and Light (`100`–`300`) strokes disappear at text sizes and on low-DPI screens. Reserve them for `28px`+ display text, and check even there that they hold against the background.
|
|
27
|
+
|
|
28
|
+
## Font family scope
|
|
29
|
+
|
|
30
|
+
Applying or reviewing typography never requires a new typeface. Use the product's type system unless the task asks for a type change, and never introduce a paid or proprietary face to satisfy a checklist. Rendering details such as font smoothing, wrapping and tabular numbers do not override the project's font family.
|
|
31
|
+
|
|
32
|
+
When a type change is asked for, two routes. The system stack gives a native macOS and iOS feel. A commercial face such as Helvetica Now is a brand decision and still needs a fallback stack.
|
|
33
|
+
|
|
34
|
+
```css
|
|
35
|
+
/* System-native macOS/iOS feel */
|
|
36
|
+
html {
|
|
37
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Commercial brand face with safe fallbacks */
|
|
41
|
+
html {
|
|
42
|
+
font-family: "Helvetica Now", "Helvetica Neue", Arial, sans-serif;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Formats
|
|
47
|
+
|
|
48
|
+
| Format | Notes |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `.woff2` | Brotli compression, broadly supported. Use this on the web. |
|
|
51
|
+
| `.woff` | Older compression. Fallback only for very old browsers. |
|
|
52
|
+
| `.ttf` / `.otf` | Raw formats, no web compression, larger files. Desktop only unless there is no other option. |
|
|
53
|
+
|
|
54
|
+
## Anatomy of a typeface
|
|
55
|
+
|
|
56
|
+
| Term | Meaning |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| x-height | Height of a lowercase `x` |
|
|
59
|
+
| Cap height | Height of uppercase letters |
|
|
60
|
+
| Baseline | The invisible line letters sit on |
|
|
61
|
+
| Ascender | Part of a letter rising above the x-height |
|
|
62
|
+
| Descender | Part dropping below the baseline |
|
|
63
|
+
|
|
64
|
+
These measurements are why two fonts at the same `font-size` look like different sizes. A large x-height looks bigger.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Color formats
|
|
2
|
+
|
|
3
|
+
Which notation to write colors in, how to convert between them and what happens at the edges of a display's gamut. Every other rule in this skill is stated perceptually and holds whatever notation you write.
|
|
4
|
+
|
|
5
|
+
## Choosing a notation
|
|
6
|
+
|
|
7
|
+
| Notation | Good for | Weakness |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Hex | Universal support, compact, what design tools hand you | Opaque. No channel is readable or editable by hand |
|
|
10
|
+
| `rgb()` | Same reach as hex, readable alpha | Channels do not correspond to anything a designer thinks about |
|
|
11
|
+
| `hsl()` | Channels look like design controls | Its lightness is not perceptual and its hue drifts; a ramp built by varying lightness bunches at one end and shifts hue |
|
|
12
|
+
| `oklch()` | Perceptually uniform lightness, stable hue, predictable ramps | Baseline 2023, so very old browser matrices need a fallback |
|
|
13
|
+
|
|
14
|
+
**Match whatever the project already uses.** Notation is not a defect: a project on hex is not doing it wrong.
|
|
15
|
+
|
|
16
|
+
**For a genuinely new color system, `oklch()` is the best default.** Even lightness steps stay even, and a fixed hue stays fixed. See [palette-generation.md](palette-generation.md).
|
|
17
|
+
|
|
18
|
+
```css
|
|
19
|
+
oklch(L C H) /* lightness 0–1, chroma 0–~0.4, hue 0–360 */
|
|
20
|
+
oklch(L C H / alpha) /* alpha uses a slash, never a comma */
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Converting
|
|
24
|
+
|
|
25
|
+
Convert when the user asks, when an agreed migration is in scope, or when the project is standardizing on a notation and this value is the straggler. Never convert an isolated value in a project that deliberately uses something else and never because this skill happened to load.
|
|
26
|
+
|
|
27
|
+
When conversion is in scope, change the values and nothing else:
|
|
28
|
+
|
|
29
|
+
- Leave CSS keywords alone: `currentColor`, `inherit`, `transparent`, `initial`, `unset`.
|
|
30
|
+
- Leave gradient functions alone. Convert the color stops inside them; do not touch the interpolation method.
|
|
31
|
+
- Leave colors in third-party configs that expect a specific format.
|
|
32
|
+
- Preserve comments and formatting.
|
|
33
|
+
|
|
34
|
+
```css
|
|
35
|
+
/* Before */
|
|
36
|
+
color: #3b82f6;
|
|
37
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
38
|
+
|
|
39
|
+
/* After */
|
|
40
|
+
color: oklch(0.623 0.188 259.815);
|
|
41
|
+
border: 1px solid oklch(0 0 0 / 0.1);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Bulk conversion is a migration, not cleanup. It shifts every rendered color by a rounding margin and touches files nobody asked about, so it has to be the task rather than a side effect.
|
|
45
|
+
|
|
46
|
+
## Gamut
|
|
47
|
+
|
|
48
|
+
Every sRGB color exists in Display P3, but not the reverse. P3 covers roughly 50% more colors, which matters only for the most saturated values. A color at 60% of maximum vividness looks the same on both.
|
|
49
|
+
|
|
50
|
+
A color more vivid than its display can render gets clipped, and clipping is not graceful. It flattens neighbouring steps into one rendered color, so the top of a ramp can lose its distinctions on an sRGB screen. Maximum vividness varies by hue and lightness. Cyans top out far lower than reds and purples, so a clipping ramp clips at some steps and not others.
|
|
51
|
+
|
|
52
|
+
The fix is to reduce vividness while holding hue and lightness. Generate ramps against sRGB unless the product is display-restricted, and add P3 as an enhancement:
|
|
53
|
+
|
|
54
|
+
```css
|
|
55
|
+
.accent {
|
|
56
|
+
background: #3b82f6;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (color-gamut: p3) {
|
|
60
|
+
.accent {
|
|
61
|
+
background: oklch(0.62 0.24 259);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Order matters. The sRGB value comes first so every display gets something, and the P3 rule overrides only where it will render. A P3 color with no fallback is a `HIGH` finding; it does not degrade, it fails.
|
|
67
|
+
|
|
68
|
+
For browser matrices predating `oklch()` support, the same layering works with `@supports`:
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
.accent {
|
|
72
|
+
background: #3b82f6;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@supports (color: oklch(0 0 0)) {
|
|
76
|
+
.accent {
|
|
77
|
+
background: oklch(0.62 0.19 259);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Check the project's actual browser matrix before adding this. On a modern baseline it is dead weight.
|
|
83
|
+
|
|
84
|
+
## Modern CSS worth knowing
|
|
85
|
+
|
|
86
|
+
- **`color-mix()`** derives one color from another, as in `color-mix(in oklab, var(--color-accent-solid) 15%, white)` for a tinted background. Useful for states, but keep generated values out of the token layer, since a mixed color cannot be inspected in a design tool.
|
|
87
|
+
- **Relative color syntax** adjusts one channel of an existing color. `oklch(from var(--color-accent-solid) calc(l - 0.1) c h)` darkens the accent by hand. Powerful and easy to overuse, since a token defined by three chained derivations is unreadable.
|
|
88
|
+
- **`light-dark()`** puts both appearances in one declaration. See [palette-generation.md](palette-generation.md).
|
|
89
|
+
|
|
90
|
+
All three compute at render time, so their output cannot be contrast-checked statically. Measure the rendered result.
|
|
@@ -0,0 +1,118 @@
|
|
|
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 { color: #3b82f6; }
|
|
12
|
+
.section-title { color: #4f8ef7; }
|
|
13
|
+
|
|
14
|
+
/* Good: interactive elements own the accent; headings stay neutral */
|
|
15
|
+
a { color: var(--color-accent-text); }
|
|
16
|
+
.section-title { color: var(--color-text-primary); }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
|
|
21
|
+
Color is never the only carrier of meaning. Pair it with an icon, a label, or a shape. `better-accessibility` owns that requirement.
|
|
22
|
+
|
|
23
|
+
## Use tokens in their role
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
```css
|
|
28
|
+
/* Bad: separator token repurposed as a text color because it looked right */
|
|
29
|
+
.caption { color: var(--color-border); }
|
|
30
|
+
|
|
31
|
+
/* Bad: text token repurposed as a background */
|
|
32
|
+
.tag { background: var(--color-text-secondary); }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The role inventory in [token-naming.md](token-naming.md) is the list of roles a system needs.
|
|
36
|
+
|
|
37
|
+
## One colored action per view
|
|
38
|
+
|
|
39
|
+
Preserve an established component hierarchy that communicates emphasis another way. Do not recolor controls merely to impose this recipe.
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<!-- Good: one filled primary action, neutral secondaries -->
|
|
43
|
+
<button class="bg-accent-solid text-white">Save</button>
|
|
44
|
+
<button class="text-neutral-700">Cancel</button>
|
|
45
|
+
|
|
46
|
+
<!-- Bad: every action colored, so nothing is primary -->
|
|
47
|
+
<button class="bg-accent-solid text-white">Save</button>
|
|
48
|
+
<button class="bg-accent-solid text-white">Duplicate</button>
|
|
49
|
+
<button class="bg-accent-solid text-white">Export</button>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Selected states may use the accent on the glyph and label. An active tab or a checked segment is state, not emphasis.
|
|
53
|
+
|
|
54
|
+
## Gradients
|
|
55
|
+
|
|
56
|
+
**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:
|
|
57
|
+
|
|
58
|
+
```css
|
|
59
|
+
/* sRGB: the default and the classic. Midpoint darkens and mutes. */
|
|
60
|
+
background: linear-gradient(#3b82f6, #ec4899);
|
|
61
|
+
|
|
62
|
+
/* oklab: even brightness across the transition. The best default. */
|
|
63
|
+
background: linear-gradient(in oklab, #3b82f6, #ec4899);
|
|
64
|
+
|
|
65
|
+
/* oklch: travels around the hue wheel, staying vivid throughout. */
|
|
66
|
+
background: linear-gradient(in oklch, #3b82f6, #ec4899);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`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.
|
|
70
|
+
|
|
71
|
+
**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.
|
|
72
|
+
|
|
73
|
+
With a polar space you also control which way it goes around:
|
|
74
|
+
|
|
75
|
+
```css
|
|
76
|
+
/* The short way round, usually what you want */
|
|
77
|
+
background: linear-gradient(in oklch shorter hue, #3b82f6, #ec4899);
|
|
78
|
+
|
|
79
|
+
/* The long way, sweeps most of the spectrum */
|
|
80
|
+
background: linear-gradient(in oklch longer hue, #3b82f6, #ec4899);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**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.
|
|
84
|
+
|
|
85
|
+
**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.
|
|
86
|
+
|
|
87
|
+
## Color across cultures
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
91
|
+
| Color | Common Western reading | Elsewhere |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| Red | Danger, loss, errors | Luck, prosperity; **gains** in Chinese financial UIs |
|
|
94
|
+
| Green | Success, gains, go | Losses in Chinese financial UIs |
|
|
95
|
+
| White | Purity, cleanliness | Mourning in parts of East Asia |
|
|
96
|
+
| Gold | Premium, luxury | Religious significance in some regions |
|
|
97
|
+
|
|
98
|
+
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.
|
|
99
|
+
|
|
100
|
+
## Light, dark and increased contrast
|
|
101
|
+
|
|
102
|
+
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:
|
|
103
|
+
|
|
104
|
+
```css
|
|
105
|
+
:root {
|
|
106
|
+
--color-accent-solid: #3b82f6;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@media (prefers-color-scheme: dark) {
|
|
110
|
+
:root { --color-accent-solid: #60a5fa; }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@media (prefers-contrast: more) {
|
|
114
|
+
:root { --color-accent-solid: #1d4ed8; }
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
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.
|