@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.
Files changed (236) hide show
  1. package/CODE_OF_CONDUCT.md +83 -0
  2. package/CONTRIBUTING.md +54 -0
  3. package/LICENSE +21 -0
  4. package/README.md +378 -2
  5. package/THIRD_PARTY_NOTICES.md +188 -0
  6. package/compose.mjs +125 -0
  7. package/composition.mjs +361 -0
  8. package/config-view.mjs +209 -0
  9. package/config.mjs +381 -0
  10. package/docs/assets/banner-dark.png +0 -0
  11. package/docs/assets/banner-light.png +0 -0
  12. package/docs/assets/overview.png +0 -0
  13. package/docs/configuration.md +269 -0
  14. package/docs/skill-names.md +50 -0
  15. package/examples/person/house-rules.json +15 -0
  16. package/examples/person/rules/10-tooling.md +8 -0
  17. package/examples/person/rules/20-reporting.md +9 -0
  18. package/examples/project/AGENTS.md +18 -0
  19. package/package.json +42 -4
  20. package/rule-references.mjs +91 -0
  21. package/rules/core.md +340 -0
  22. package/rules/modifiers/backlog-outline.md +25 -0
  23. package/rules/modifiers/backlog-visual.md +16 -0
  24. package/rules/modifiers/coded-offers.md +70 -0
  25. package/rules/modifiers/cross-session-relay.md +23 -0
  26. package/rules/modifiers/effort-estimates.md +41 -0
  27. package/rules/modifiers/land-when-done.md +12 -0
  28. package/rules/modifiers/low-quota-handoff.md +20 -0
  29. package/rules/modifiers/model-default-questions.md +33 -0
  30. package/rules/modifiers/multi-agent.md +38 -0
  31. package/rules/modifiers/no-attribution.md +16 -0
  32. package/rules/modifiers/periodic-updates.md +25 -0
  33. package/rules/modifiers/plan-files.md +82 -0
  34. package/rules/modifiers/question-cards.md +48 -0
  35. package/rules/modifiers/release-batching.md +15 -0
  36. package/rules/modifiers/scratch-on-disk.md +15 -0
  37. package/rules/modifiers/shared-host-load.md +19 -0
  38. package/rules/modifiers/solo-operator.md +13 -0
  39. package/rules/modifiers/squash-landing.md +22 -0
  40. package/rules/modifiers/upstream-contributions.md +45 -0
  41. package/rules/modifiers/web-image-formats.md +35 -0
  42. package/rules/references/backlog-view.md +154 -0
  43. package/rules/references/github-actions.md +45 -0
  44. package/rules/references/landing.md +39 -0
  45. package/rules/references/multi-agent.md +51 -0
  46. package/rules/references/release-batching.md +66 -0
  47. package/rules/references/squash-landing.md +50 -0
  48. package/rules/references/thread-visuals.md +45 -0
  49. package/setup.mjs +445 -0
  50. package/skills/hr-agent-guidance-audit/LICENSE +21 -0
  51. package/skills/hr-agent-guidance-audit/SKILL.md +91 -0
  52. package/skills/hr-agent-guidance-audit/agents/openai.yaml +2 -0
  53. package/skills/hr-agent-guidance-audit/references/prune-workflow.md +72 -0
  54. package/skills/hr-agent-guidance-audit/references/records-and-lessons.md +60 -0
  55. package/skills/hr-agent-guidance-audit/references/retirement-verdicts.md +49 -0
  56. package/skills/hr-agent-guidance-refresh/LICENSE +21 -0
  57. package/skills/hr-agent-guidance-refresh/SKILL.md +36 -0
  58. package/skills/hr-agent-guidance-refresh/agents/openai.yaml +2 -0
  59. package/skills/hr-agent-guidance-refresh/references/verification.md +47 -0
  60. package/skills/hr-api-exposure-check/SKILL.md +159 -0
  61. package/skills/hr-api-exposure-check/agents/openai.yaml +3 -0
  62. package/skills/hr-audit-choices/SKILL.md +67 -0
  63. package/skills/hr-audit-choices/agents/openai.yaml +2 -0
  64. package/skills/hr-benchmarking/NOTICE.md +36 -0
  65. package/skills/hr-benchmarking/SKILL.md +279 -0
  66. package/skills/hr-benchmarking/agents/openai.yaml +2 -0
  67. package/skills/hr-benchmarking/references/javascript.md +130 -0
  68. package/skills/hr-benchmarking/references/tools.md +165 -0
  69. package/skills/hr-benchmarking/scripts/process-attribution.ps1 +20 -0
  70. package/skills/hr-benchmarking/scripts/process-attribution.py +107 -0
  71. package/skills/hr-better-accessibility/LICENSE +22 -0
  72. package/skills/hr-better-accessibility/NOTICE.md +46 -0
  73. package/skills/hr-better-accessibility/SKILL.md +119 -0
  74. package/skills/hr-better-accessibility/agents/openai.yaml +3 -0
  75. package/skills/hr-better-accessibility/focus-and-keyboard.md +130 -0
  76. package/skills/hr-better-accessibility/forms.md +82 -0
  77. package/skills/hr-better-accessibility/hit-areas.md +98 -0
  78. package/skills/hr-better-accessibility/motion-and-zoom.md +81 -0
  79. package/skills/hr-better-accessibility/screen-readers.md +102 -0
  80. package/skills/hr-better-accessibility/semantics-and-aria.md +84 -0
  81. package/skills/hr-better-colors/LICENSE +21 -0
  82. package/skills/hr-better-colors/NOTICE.md +37 -0
  83. package/skills/hr-better-colors/SKILL.md +114 -0
  84. package/skills/hr-better-colors/agents/openai.yaml +3 -0
  85. package/skills/hr-better-colors/color-formats.md +90 -0
  86. package/skills/hr-better-colors/color-usage.md +140 -0
  87. package/skills/hr-better-colors/contrast.md +80 -0
  88. package/skills/hr-better-colors/palette-generation.md +104 -0
  89. package/skills/hr-better-colors/palette-structure.md +76 -0
  90. package/skills/hr-better-colors/token-naming.md +97 -0
  91. package/skills/hr-better-layout/LICENSE +22 -0
  92. package/skills/hr-better-layout/NOTICE.md +48 -0
  93. package/skills/hr-better-layout/SKILL.md +92 -0
  94. package/skills/hr-better-layout/agents/openai.yaml +3 -0
  95. package/skills/hr-better-layout/grouping-and-alignment.md +144 -0
  96. package/skills/hr-better-layout/spacing-and-adaptivity.md +215 -0
  97. package/skills/hr-better-typography/LICENSE +22 -0
  98. package/skills/hr-better-typography/NOTICE.md +46 -0
  99. package/skills/hr-better-typography/SKILL.md +172 -0
  100. package/skills/hr-better-typography/agents/openai.yaml +3 -0
  101. package/skills/hr-better-typography/choosing-fonts.md +69 -0
  102. package/skills/hr-better-typography/css-cheat-sheet.md +65 -0
  103. package/skills/hr-better-typography/details-and-accessibility.md +119 -0
  104. package/skills/hr-better-typography/spacing-and-sizing.md +131 -0
  105. package/skills/hr-better-typography/variable-fonts-and-opentype.md +105 -0
  106. package/skills/hr-better-typography/wrapping-and-punctuation.md +57 -0
  107. package/skills/hr-better-writing/LICENSE +22 -0
  108. package/skills/hr-better-writing/NOTICE.md +46 -0
  109. package/skills/hr-better-writing/SKILL.md +122 -0
  110. package/skills/hr-better-writing/agents/openai.yaml +3 -0
  111. package/skills/hr-code-review/LICENSE +21 -0
  112. package/skills/hr-code-review/NOTICE.md +26 -0
  113. package/skills/hr-code-review/SKILL.md +163 -0
  114. package/skills/hr-code-review/agents/openai.yaml +3 -0
  115. package/skills/hr-code-review/references/acp-client.md +43 -0
  116. package/skills/hr-code-review/references/evidence.md +57 -0
  117. package/skills/hr-code-review/references/impact.md +103 -0
  118. package/skills/hr-code-review/references/panel-adjudication.md +58 -0
  119. package/skills/hr-code-review/references/panel-plan.md +61 -0
  120. package/skills/hr-code-review/references/panel-recommendations.json +105 -0
  121. package/skills/hr-code-review/references/panel-recommendations.md +46 -0
  122. package/skills/hr-code-review/references/panel-setup.md +54 -0
  123. package/skills/hr-code-review/references/reviewer-role.md +31 -0
  124. package/skills/hr-code-review/scripts/acp-panel-client.mjs +299 -0
  125. package/skills/hr-code-review/scripts/check-receipts.mjs +263 -0
  126. package/skills/hr-code-review/scripts/panel-plan.mjs +69 -0
  127. package/skills/hr-code-review/scripts/panel-preferences.mjs +220 -0
  128. package/skills/hr-design-exploration/SKILL.md +105 -0
  129. package/skills/hr-design-exploration/agents/openai.yaml +4 -0
  130. package/skills/hr-diagnosing-bugs/NOTICE.md +45 -0
  131. package/skills/hr-diagnosing-bugs/SKILL.md +108 -0
  132. package/skills/hr-diagnosing-bugs/agents/openai.yaml +3 -0
  133. package/skills/hr-diagnosing-bugs/references/reporting.md +97 -0
  134. package/skills/hr-diagnosing-bugs/scripts/check-pr-offer.mjs +47 -0
  135. package/skills/hr-explain-code/NOTICE.md +37 -0
  136. package/skills/hr-explain-code/SKILL.md +110 -0
  137. package/skills/hr-explain-code/agents/openai.yaml +5 -0
  138. package/skills/hr-explain-code/references/helper-brief.md +39 -0
  139. package/skills/hr-extract-shared-steps/SKILL.md +134 -0
  140. package/skills/hr-extract-shared-steps/agents/openai.yaml +3 -0
  141. package/skills/hr-forgotten-work-audit/SKILL.md +130 -0
  142. package/skills/hr-forgotten-work-audit/references/brief-template.md +56 -0
  143. package/skills/hr-forgotten-work-audit/references/result-format.md +46 -0
  144. package/skills/hr-handoff/LICENSE +21 -0
  145. package/skills/hr-handoff/NOTICE.md +29 -0
  146. package/skills/hr-handoff/SKILL.md +114 -0
  147. package/skills/hr-handoff/agents/openai.yaml +5 -0
  148. package/skills/hr-handoff/assets/ledger.example.json +10 -0
  149. package/skills/hr-handoff/assets/receipt.example.json +76 -0
  150. package/skills/hr-handoff/assets/source.example.json +20 -0
  151. package/skills/hr-handoff/references/reconciliation.md +124 -0
  152. package/skills/hr-handoff/references/successor-thread.md +76 -0
  153. package/skills/hr-handoff/scripts/check-reconciliation.mjs +324 -0
  154. package/skills/hr-house-rules-setup/SKILL.md +114 -0
  155. package/skills/hr-house-rules-setup/agents/openai.yaml +5 -0
  156. package/skills/hr-issue-tracker-setup/SKILL.md +49 -0
  157. package/skills/hr-issue-tracker-setup/agents/openai.yaml +5 -0
  158. package/skills/hr-lean-plan/SKILL.md +146 -0
  159. package/skills/hr-lean-plan/agents/openai.yaml +5 -0
  160. package/skills/hr-maintainability-review/SKILL.md +186 -0
  161. package/skills/hr-maintainability-review/agents/openai.yaml +5 -0
  162. package/skills/hr-manual-qa/NOTICE.md +36 -0
  163. package/skills/hr-manual-qa/SKILL.md +37 -0
  164. package/skills/hr-manual-qa/agents/openai.yaml +3 -0
  165. package/skills/hr-navigation-retro/LICENSE +21 -0
  166. package/skills/hr-navigation-retro/NOTICE.md +5 -0
  167. package/skills/hr-navigation-retro/SKILL.md +86 -0
  168. package/skills/hr-navigation-retro/agents/openai.yaml +4 -0
  169. package/skills/hr-navigation-retro/references/inputs.md +67 -0
  170. package/skills/hr-navigation-retro/scripts/retro.py +552 -0
  171. package/skills/hr-navigation-retro/scripts/transcripts.py +205 -0
  172. package/skills/hr-ordering-tests/SKILL.md +51 -0
  173. package/skills/hr-ordering-tests/agents/openai.yaml +2 -0
  174. package/skills/hr-plain-prose/SKILL.md +264 -0
  175. package/skills/hr-plain-prose/agents/openai.yaml +3 -0
  176. package/skills/hr-plain-prose/scripts/check.mjs +331 -0
  177. package/skills/hr-pr-followup/LICENSE +21 -0
  178. package/skills/hr-pr-followup/NOTICE.md +9 -0
  179. package/skills/hr-pr-followup/SKILL.md +74 -0
  180. package/skills/hr-pr-followup/agents/openai.yaml +2 -0
  181. package/skills/hr-pr-followup/references/review-helpers.md +111 -0
  182. package/skills/hr-pr-followup/scripts/check-landing.py +204 -0
  183. package/skills/hr-pr-followup/scripts/check-pr-review.ts +510 -0
  184. package/skills/hr-pr-followup/scripts/merge-pr.sh +100 -0
  185. package/skills/hr-pr-followup/scripts/resolve-pr-thread.ts +219 -0
  186. package/skills/hr-pr-followup/scripts/verify-landed.sh +184 -0
  187. package/skills/hr-progress-report/SKILL.md +49 -0
  188. package/skills/hr-progress-report/agents/openai.yaml +5 -0
  189. package/skills/hr-read-reddit/SKILL.md +44 -0
  190. package/skills/hr-read-reddit/agents/openai.yaml +2 -0
  191. package/skills/hr-read-reddit/scripts/reddit.ts +252 -0
  192. package/skills/hr-read-x-links/SKILL.md +42 -0
  193. package/skills/hr-read-x-links/agents/openai.yaml +2 -0
  194. package/skills/hr-read-x-links/scripts/x-post.ts +264 -0
  195. package/skills/hr-research-shared-links/SKILL.md +103 -0
  196. package/skills/hr-split-to-prs/LICENSE +21 -0
  197. package/skills/hr-split-to-prs/NOTICE.md +9 -0
  198. package/skills/hr-split-to-prs/SKILL.md +61 -0
  199. package/skills/hr-split-to-prs/agents/openai.yaml +2 -0
  200. package/skills/hr-sqlite/SKILL.md +174 -0
  201. package/skills/hr-sqlite/agents/openai.yaml +2 -0
  202. package/skills/hr-sqlite/references/evidence.md +94 -0
  203. package/skills/hr-sqlite/references/tool-decisions.md +39 -0
  204. package/skills/hr-sqlite/scripts/inspect-sqlite.py +245 -0
  205. package/skills/hr-stock-ui-audit/SKILL.md +132 -0
  206. package/skills/hr-stock-ui-audit/agents/openai.yaml +3 -0
  207. package/skills/hr-stock-ui-audit/scripts/scan.mjs +619 -0
  208. package/skills/hr-tdd/LICENSE +21 -0
  209. package/skills/hr-tdd/NOTICE.md +10 -0
  210. package/skills/hr-tdd/SKILL.md +40 -0
  211. package/skills/hr-tdd/agents/openai.yaml +3 -0
  212. package/skills/hr-tdd/mocking.md +39 -0
  213. package/skills/hr-tdd/tests.md +77 -0
  214. package/skills/hr-test-audit/LICENSE +21 -0
  215. package/skills/hr-test-audit/NOTICE.md +67 -0
  216. package/skills/hr-test-audit/SKILL.md +146 -0
  217. package/skills/hr-test-audit/agents/openai.yaml +3 -0
  218. package/skills/hr-test-audit/scripts/floor-guard.ts +137 -0
  219. package/skills/hr-usage-report/SKILL.md +90 -0
  220. package/skills/hr-usage-report/agents/openai.yaml +2 -0
  221. package/skills/hr-usage-report/assets/example-report.json +125 -0
  222. package/skills/hr-usage-report/assets/report-template.html +377 -0
  223. package/skills/hr-usage-report/scripts/render-report.ts +86 -0
  224. package/skills/hr-usage-report/scripts/usage-by-model.ts +499 -0
  225. package/skills/hr-what-next/SKILL.md +151 -0
  226. package/skills/hr-what-next/agents/openai.yaml +4 -0
  227. package/skills/hr-what-next/references/reporting.md +59 -0
  228. package/skills/hr-what-next/scripts/check-open-work.mjs +711 -0
  229. package/skills/hr-writing-for-agents/NOTICE.md +46 -0
  230. package/skills/hr-writing-for-agents/SKILL-MECHANICS.md +83 -0
  231. package/skills/hr-writing-for-agents/SKILL.md +108 -0
  232. package/skills/hr-writing-for-agents/agents/openai.yaml +3 -0
  233. package/skills/hr-writing-pr/NOTICE.md +48 -0
  234. package/skills/hr-writing-pr/SKILL.md +119 -0
  235. package/skills/hr-writing-pr/agents/openai.yaml +2 -0
  236. package/skills/hr-writing-pr/scripts/benefit-chart.py +200 -0
@@ -0,0 +1,172 @@
1
+ ---
2
+ name: hr-better-typography
3
+ description: "Typography rules for web UI: type scale, line-height, wrapping, truncation, tabular numbers, font loading and mobile input text size. Use when styling or reviewing how text renders in web UI CSS."
4
+ license: MIT
5
+ metadata:
6
+ upstream: https://github.com/jakubkrehel/skills
7
+ upstream_ref: 267330e1adfc66a718fb65fa6918c1f06d0a689e
8
+ upstream_path: skills/better-typography/
9
+ local_patches: "Use-when description; scaled-input recipe keeps a focus ring and cannot shrink; enforce the scale with a lint or guard; per-field wrap/truncate choice and middle truncation (from emilkowalski/skills break-ui)"
10
+ house_rules_patches: "Portable provenance and review verdict"
11
+ break_ui_source: https://github.com/emilkowalski/skills/blob/e8a175de22ae1e49370fc144c1f3bb9aeedf988d/skills/break-ui/SKILL.md
12
+ ---
13
+
14
+ # Typography
15
+
16
+ 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.
17
+
18
+ When reviewing, read the rendered page instead of scanning the code. Bad wrapping, widows and truncation only show up at real content lengths.
19
+
20
+ Write every fix in the project's styling system, and use the exact values below rather than familiar-looking equivalents. The [cheat sheet](css-cheat-sheet.md) maps each declaration to its Tailwind equivalent.
21
+
22
+ The words themselves belong to `hr-better-writing`. Semantic heading structure belongs to `hr-better-accessibility`. Spatial RTL layout and logical properties belong to `hr-better-layout`. Contrast measurement belongs to `hr-better-colors`. This skill owns how text renders, wraps and behaves in mixed-direction content.
23
+
24
+ ## Serve the right format
25
+
26
+ 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.
27
+
28
+ ## Properties over raw tags
29
+
30
+ 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`.
31
+
32
+ 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](variable-fonts-and-opentype.md).
33
+
34
+ ## Load intended weights and styles
35
+
36
+ Browsers synthesize a weight or style the active family doesn't provide, distorting the real face. Load the faces the design uses.
37
+
38
+ `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.
39
+
40
+ ## Fewer fonts, sizes and weights
41
+
42
+ 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.
43
+
44
+ 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](choosing-fonts.md).
45
+
46
+ ## Use a type scale with semantic names
47
+
48
+ 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. Enforce the scale with a lint rule or guard test that fails on an arbitrary size outside it.
49
+
50
+ 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](spacing-and-sizing.md).
51
+
52
+ ## Heading sizes descend with level
53
+
54
+ 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 `hr-better-accessibility`'s; this skill sets only the visual treatment.
55
+
56
+ ## Line-height by role
57
+
58
+ 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.
59
+
60
+ 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.
61
+
62
+ ## Letter-spacing by size
63
+
64
+ 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.
65
+
66
+ ## Cap the measure
67
+
68
+ 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](wrapping-and-punctuation.md#measure-line-length).
69
+
70
+ ## Wrap deliberately
71
+
72
+ Four declarations, four jobs:
73
+
74
+ - `text-wrap: balance` distributes text evenly across lines. Use it on headings.
75
+ - `text-wrap: pretty` stops a single short word landing on the final line. Use it on descriptions.
76
+ - `overflow-wrap: break-word` where a long word, link, or ID could escape the container.
77
+ - `white-space: nowrap` on labels and badges where a line break looks broken.
78
+
79
+ Skip `balance` and `pretty` in long-form text.
80
+
81
+ ## Tabular numbers on changing values
82
+
83
+ 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.
84
+
85
+ ## Truncate without losing content
86
+
87
+ 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.
88
+
89
+ Choose per field. Wrap names and titles the user needs in full to identify an item. Truncate in the middle when items differ at the end, such as file names, paths and emails sharing a domain, because an end ellipsis makes them look identical. Never truncate numbers, amounts or dates the user compares. Details are in [wrapping-and-punctuation.md](wrapping-and-punctuation.md).
90
+
91
+ ## Write copy naturally, style with CSS
92
+
93
+ Store text in natural case and control presentation with `text-transform`, so a redesign never means rewriting copy.
94
+
95
+ Use smart punctuation in rendered text:
96
+
97
+ - Curly quotes in prose, straight quotes in code.
98
+ - An en dash for ranges: `2010–2020`.
99
+ - The single ellipsis character, not three periods.
100
+ - ` ` to hold `16 px` together across a line break.
101
+ - `­` to say where a long word may break.
102
+
103
+ ## Underlines from the font
104
+
105
+ 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`.
106
+
107
+ `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.
108
+
109
+ 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`.
110
+
111
+ ## Inputs at 16px on mobile
112
+
113
+ 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:
114
+
115
+ - Size the input up on mobile (`text-base sm:text-sm`). Changes how it looks on small screens.
116
+ - 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.
117
+
118
+ Both recipes are in [details-and-accessibility.md](details-and-accessibility.md).
119
+
120
+ ## Size and contrast floors
121
+
122
+ 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.
123
+
124
+ 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.
125
+
126
+ When text looks low-contrast, use `hr-better-colors` to measure the rendered pair and `hr-better-accessibility` to classify the requirement. Leave the colors alone unless asked.
127
+
128
+ ## Font smoothing on the root
129
+
130
+ 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.
131
+
132
+ ## Language and bidi behavior
133
+
134
+ 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 `hr-better-layout`.
135
+
136
+ ## Keep useful text selectable
137
+
138
+ Keep text selectable by default. `::selection` can carry brand into the reading experience, as long as the selected combination stays legible.
139
+
140
+ `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.
141
+
142
+ ## Before you finish
143
+
144
+ | Mistake | Fix |
145
+ | ------------------------------------------------ | ------------------------------------------------------------------- |
146
+ | Synthesized face differs from the design | Load the real face; disable only the verified synthesis mode |
147
+ | Child heading visually overpowers its parent | Map that section's hierarchy to descending scale steps |
148
+ | Heading element picked for its default size | Choose semantics first, then set the size in CSS |
149
+ | Orphan on the last line of a paragraph | `text-wrap: pretty` |
150
+ | Lopsided two-line heading | `text-wrap: balance` |
151
+ | Justified text in an interface | `text-align: start`; reserve justify for specific editorial layouts |
152
+ | Underline cuts through descenders | `text-decoration-skip-ink: auto`, `from-font` metrics |
153
+ | Mixed-direction value renders in the wrong order | Correct `lang`/`dir`; isolate the value with `<bdi>` |
154
+ | Selection disabled across application chrome | Restore it; suppress only where it conflicts with a drag or gesture |
155
+ | Extra-info hint with no visual cue | Dotted underline via `text-decoration-style: dotted` |
156
+ | Thin/Light weight on `14px` UI text | Weight `400`+ below `18px`; thin weights are display-only |
157
+ | `leading-none` on a three-line card description | At least `1.4` on any text that wraps to 3+ lines |
158
+
159
+ ## Reporting
160
+
161
+ **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.
162
+
163
+ **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`.
164
+
165
+ **Format.** Group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in:
166
+
167
+ | Severity | Location | Before | After | Why |
168
+ | -------- | -------- | ------ | ----- | --- |
169
+
170
+ `Location` is `path/to/file:line`. `Why` names the principle and the user impact.
171
+
172
+ Report `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,3 @@
1
+ interface:
2
+ display_name: "HR: Better Typography"
3
+ short_description: "Fonts, type scales, spacing and wrapping"
@@ -0,0 +1,69 @@
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:
38
+ system-ui,
39
+ -apple-system,
40
+ BlinkMacSystemFont,
41
+ "Segoe UI",
42
+ sans-serif;
43
+ }
44
+
45
+ /* Commercial brand face with safe fallbacks */
46
+ html {
47
+ font-family: "Helvetica Now", "Helvetica Neue", Arial, sans-serif;
48
+ }
49
+ ```
50
+
51
+ ## Formats
52
+
53
+ | Format | Notes |
54
+ | --------------- | -------------------------------------------------------------------------------------------- |
55
+ | `.woff2` | Brotli compression, broadly supported. Use this on the web. |
56
+ | `.woff` | Older compression. Fallback only for very old browsers. |
57
+ | `.ttf` / `.otf` | Raw formats, no web compression, larger files. Desktop only unless there is no other option. |
58
+
59
+ ## Anatomy of a typeface
60
+
61
+ | Term | Meaning |
62
+ | ---------- | ------------------------------------------ |
63
+ | x-height | Height of a lowercase `x` |
64
+ | Cap height | Height of uppercase letters |
65
+ | Baseline | The invisible line letters sit on |
66
+ | Ascender | Part of a letter rising above the x-height |
67
+ | Descender | Part dropping below the baseline |
68
+
69
+ These measurements are why two fonts at the same `font-size` look like different sizes. A large x-height looks bigger.
@@ -0,0 +1,65 @@
1
+ # CSS cheat sheet
2
+
3
+ One-line lookup for every typography CSS declaration covered by this skill, with the Tailwind 4 equivalent. Where no utility exists, the arbitrary-value form is shown. Pick the column that matches the project: the declaration in plain CSS, CSS Modules, styled-components or StyleX codebases, the utility in Tailwind codebases.
4
+
5
+ ## Font
6
+
7
+ | Declaration | What it does | Tailwind |
8
+ | ---------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------- |
9
+ | `font-family: sans-serif` | The sans family | `font-sans` |
10
+ | `font-family: serif` | The serif family | `font-serif` |
11
+ | `font-family: monospace` | The monospace family | `font-mono` |
12
+ | `font-size` | Size from the type scale | `text-*` |
13
+ | `font-weight` | Any value from 1 to 1000 | `font-*` |
14
+ | `font-style: italic` | Switch to italic style | `italic` |
15
+ | `-webkit-font-smoothing` + `-moz-osx-font-smoothing` | Smooth macOS font rendering; apply once at the root | `antialiased` |
16
+ | `font-synthesis: none` | Disable all synthesized forms after verifying fallbacks and emphasis | `[font-synthesis:none]` |
17
+ | `font-feature-settings` | Toggle OpenType features | `[font-feature-settings:"ss01"]` |
18
+ | `font-variation-settings` | Tune variable font axes | `[font-variation-settings:"GRAD"_80]` |
19
+ | `font-optical-sizing` | Adjust details per size | `[font-optical-sizing:auto]` |
20
+ | `font-variant-caps` | Real small capitals | `[font-variant-caps:small-caps]` |
21
+ | `font-variant-position` | Real super and subscripts | `[font-variant-position:super]` |
22
+ | `font-variant-numeric: tabular-nums` | Equal-width digits | `tabular-nums` |
23
+ | `font-variant-numeric: slashed-zero` | Tell 0 from O | `slashed-zero` |
24
+
25
+ ## Spacing and layout
26
+
27
+ | Declaration | What it does | Tailwind |
28
+ | --------------------------- | --------------------------------- | ----------------------------------------- |
29
+ | `letter-spacing` | Space between letters | `tracking-*` |
30
+ | `line-height` | Space between lines | `leading-*` |
31
+ | `font-kerning` | Kerning on or off | `[font-kerning:none]` |
32
+ | `text-box: trim-both` | Trim space above and below | `[text-box:trim-both_cap_alphabetic]` |
33
+ | `max-width` on text columns | Cap at ~60–75 characters per line | `max-w-xl` / `max-w-2xl` / `max-w-[65ch]` |
34
+ | `text-align` | Where lines start and end | `text-start` / `text-center` |
35
+
36
+ ## Wrapping and overflow
37
+
38
+ | Declaration | What it does | Tailwind |
39
+ | --------------------------- | ------------------------- | -------------------------- |
40
+ | `text-wrap: balance` | Even out heading lines | `text-balance` |
41
+ | `text-wrap: pretty` | Avoid orphaned words | `text-pretty` |
42
+ | `text-overflow: ellipsis` | Ellipsis for clipped text | `truncate` |
43
+ | `line-clamp` | Cut off after N lines | `line-clamp-*` |
44
+ | `overflow-wrap: break-word` | Break long strings | `break-words` |
45
+ | `white-space: nowrap` | Stop wrapping | `whitespace-nowrap` |
46
+ | `text-transform` | Change the casing | `uppercase` / `capitalize` |
47
+
48
+ ## Decoration and interaction
49
+
50
+ | Declaration | What it does | Tailwind |
51
+ | -------------------------------------- | ----------------------------------------------------------- | --------------------------------------- |
52
+ | `text-decoration-line: underline` | Draw an underline | `underline` |
53
+ | `text-decoration-color` | Underline color | `decoration-*` |
54
+ | `text-decoration-thickness` | Underline thickness | `decoration-1` / `decoration-2` |
55
+ | `text-underline-offset` | Push the line down | `underline-offset-*` |
56
+ | `text-underline-position: from-font` | Underline position from the font | `[text-underline-position:from-font]` |
57
+ | `text-decoration-style` | Dotted, dashed or wavy | `decoration-dotted` / `decoration-wavy` |
58
+ | `text-decoration-thickness: from-font` | Underline set by the font | `decoration-from-font` |
59
+ | `text-decoration-skip-ink` | Gaps around descenders | `[text-decoration-skip-ink:auto]` |
60
+ | `caret-color` | Tint the text cursor | `caret-*` |
61
+ | `user-select: none` | Suppress selection on a verified drag/gesture conflict only | `select-none` |
62
+ | `text-shadow` | Shadow behind the letters | `text-shadow-*` |
63
+ | `-webkit-text-stroke` | Outline the letters | `[-webkit-text-stroke:1px_black]` |
64
+ | `background-clip: text` | Clip a background to the letters | `bg-clip-text` |
65
+ | `initial-letter` | Size a drop cap | `[initial-letter:3]` |
@@ -0,0 +1,119 @@
1
+ # Details and accessibility
2
+
3
+ Underlines, selection, forms, decorative text and the floors that keep everything readable.
4
+
5
+ ## Underlines
6
+
7
+ Default underline position is browser-determined, sometimes too close, sometimes cutting through descenders, sometimes too thin. Pull position and thickness from the font's own metrics:
8
+
9
+ ```css
10
+ a {
11
+ text-underline-position: from-font;
12
+ text-decoration-thickness: from-font;
13
+ }
14
+ ```
15
+
16
+ A dotted underline on an abbreviation:
17
+
18
+ ```css
19
+ abbr {
20
+ text-decoration: underline dotted;
21
+ }
22
+ ```
23
+
24
+ Or tune manually:
25
+
26
+ ```css
27
+ a {
28
+ text-decoration-thickness: 1px;
29
+ text-underline-offset: 3px;
30
+ text-decoration-skip-ink: auto;
31
+ text-decoration-color: var(--color-gray-1000);
32
+ transition: text-decoration-color 200ms ease-out;
33
+ }
34
+
35
+ a:hover {
36
+ text-decoration-color: var(--color-gray-1200);
37
+ }
38
+ ```
39
+
40
+ Animate the custom element however the effect requires.
41
+
42
+ ## Selection
43
+
44
+ - `::target-text` styles the phrase a shared link scrolls to.
45
+ - The Custom Highlight API styles ranges you pick yourself, like search matches, without extra markup.
46
+
47
+ ## Forms and editable text
48
+
49
+ - `::placeholder` styles the hint in an empty field.
50
+ - `caret-color` colors the blinking insertion bar. Color is about as far as caret styling goes; a fully custom caret is hard to build and rarely worth it.
51
+
52
+ ### iOS input zoom
53
+
54
+ This is an accessibility feature: `16px` is the web default, and Safari treats smaller as too hard to read while typing.
55
+
56
+ The two fixes differ in what they do to the design, not in correctness.
57
+
58
+ **Size up on mobile.** The input renders at `16px` on small screens and drops to the design size from the `sm` breakpoint up. Nothing to compensate, but the mobile input no longer matches the desktop one.
59
+
60
+ ```tsx
61
+ <input className="text-base sm:text-sm" type="email" />
62
+ ```
63
+
64
+ **Scale the text down.** Keep `font-size` at `16px` so Safari never zooms, then render at the intended size with a transform. The design survives at every viewport, at the cost of two compensating calcs. Widen the element by the inverse of the scale so it still fills its container once shrunk, and divide `line-height` by the same factor so the intended leading survives. `origin-left` pins the text to the start edge, `origin-right` under RTL. Above the breakpoint, drop the transform and set the real size. `shrink-0` stops the flex wrapper from shrinking the widened input back, `overflow-hidden` clips its unscaled layout box, and the wrapper carries the focus ring because the input drops its own.
65
+
66
+ ```tsx
67
+ // 13px rendered from a 16px font-size: 13 / 16 = 0.8125
68
+ <div className="flex h-10 items-center rounded-[10px] bg-gray-300 overflow-hidden px-2.5 focus-within:outline-2 focus-within:outline-offset-2 focus-within:[outline-style:solid]">
69
+ <input
70
+ className="h-full w-[calc(100%/0.8125)] shrink-0 origin-left rtl:origin-right scale-[0.8125] bg-transparent text-base leading-[calc(1.125/0.8125)] outline-none sm:w-full sm:scale-100 sm:text-[13px]"
71
+ type="email"
72
+ />
73
+ </div>
74
+ ```
75
+
76
+ The transform shrinks the whole box, not only the glyphs, so let a wrapper draw the field's surface and keep the input transparent. A background, border, or ring on the scaled element shrinks with the text and misses the intended hit area.
77
+
78
+ ## Decorative text
79
+
80
+ | Property | Effect |
81
+ | ----------------------- | --------------------------------------------------------------------- |
82
+ | `::first-letter` | Drop cap, widely supported |
83
+ | `::first-line` | Styles only the first line |
84
+ | `initial-letter` | Sizes the drop cap; limited support, no Firefox yet |
85
+ | `background-clip: text` | Clips a background or gradient to the letter shapes |
86
+ | `-webkit-text-stroke` | Outlines the letters; works across modern browsers despite the prefix |
87
+ | `text-shadow` | Like `box-shadow` but follows the character shapes |
88
+
89
+ A text stroke can expose overlapping font contours inside the letters. Variable fonts may retain overlaps for interpolation, and static fonts can contain them too. Inspect the specific font before treating a static cut as a fix.
90
+
91
+ ## Sizes
92
+
93
+ Typography must survive the reader changing it: zoom, a larger browser font size, an overridden line height or letter spacing.
94
+
95
+ | Text | Size |
96
+ | ------------------------------- | ---------------------------------------------------------- |
97
+ | Long-form body starting point | Around `16px`, verified in the actual typeface and measure |
98
+ | Inputs and menus starting point | Around `14px` |
99
+ | Captions | `13px` |
100
+ | Floor | Rarely below `12px` |
101
+
102
+ ## Font smoothing
103
+
104
+ Tailwind's `antialiased` sets both properties:
105
+
106
+ ```css
107
+ html {
108
+ -webkit-font-smoothing: antialiased;
109
+ -moz-osx-font-smoothing: grayscale;
110
+ }
111
+ ```
112
+
113
+ ```tsx
114
+ <html lang="en">
115
+ <body className="font-sans antialiased">
116
+ <main>{children}</main>
117
+ </body>
118
+ </html>
119
+ ```
@@ -0,0 +1,131 @@
1
+ # Spacing and sizing
2
+
3
+ A sensible scale and comfortable spacing do more for typography than any effect.
4
+
5
+ ## Units
6
+
7
+ | Unit | Behavior |
8
+ | ------------------ | ----------------------------------------------------- |
9
+ | `px` | Fixed |
10
+ | `em` | Scales with the current font size |
11
+ | `rem` | Scales with the root font size |
12
+ | `%` on `font-size` | Relative to the parent's font size, behaves like `em` |
13
+
14
+ ## Type scale
15
+
16
+ A small set of sizes used across a product, deviated from as little as possible. Hard-coding sizes with no system behind them breaks down at scale.
17
+
18
+ ```css
19
+ :root {
20
+ --text-sm: 0.875rem;
21
+ --text-base: 1rem;
22
+ --text-lg: 1.125rem;
23
+ --text-xl: 1.5rem;
24
+ --text-2xl: 2rem;
25
+ }
26
+ ```
27
+
28
+ Pick an existing scale or define one. Tailwind's (`text-xs` through `text-9xl`, each class pairing a size with a matching line height) is a solid ready-made choice.
29
+
30
+ Solo, the default names work fine given clear rules for where each size is used. On a team, name sizes semantically. `text-sm` tells you the size but not the use, where `text-body-sm` carries both.
31
+
32
+ A role-based scale pairs each size with its line-height and weight, making a role one decision instead of three. A starting point for a product interface:
33
+
34
+ | Role | Size | Line-height | Weight |
35
+ | ------- | ------------------ | ----------- | ------ |
36
+ | Display | `2.25rem` (36px) | `1.1` | `600` |
37
+ | Title | `1.5rem` (24px) | `1.2` | `600` |
38
+ | Heading | `1.125rem` (18px) | `1.3` | `600` |
39
+ | Body | `1rem` (16px) | `1.5` | `400` |
40
+ | Caption | `0.8125rem` (13px) | `1.4` | `400` |
41
+
42
+ Emphasis within a role is one weight step up (`400` → `500`), not a size change.
43
+
44
+ ## Heading hierarchy
45
+
46
+ Assign each heading level to a descending step of the scale, so hierarchy comes from the scale instead of one-off sizes:
47
+
48
+ ```css
49
+ h1 {
50
+ font-size: var(--text-2xl);
51
+ }
52
+ h2 {
53
+ font-size: var(--text-xl);
54
+ }
55
+ h3 {
56
+ font-size: var(--text-lg);
57
+ }
58
+ ```
59
+
60
+ In Tailwind the same mapping is utility classes per level (`text-2xl`, `text-xl`, `text-lg`), centralized in a component or `@layer base` rather than repeated inline.
61
+
62
+ When reviewing, compare the computed size of headings within each semantic section. A child rendering more prominently than its parent breaks the hierarchy. Deep levels may share a size where the scale runs out of comfortable steps, as long as weight or letter-spacing keeps them distinct. A heading is never smaller than body text unless it is deliberately a label-style overline.
63
+
64
+ Heading semantics and outline quality belong to `hr-better-accessibility`. Pick the element from the document structure, then use this skill to make that structure visually legible. Never pick a heading element for its browser-default size.
65
+
66
+ ## Kerning and letter-spacing
67
+
68
+ - **Kerning** adjusts specific pairs such as `AV` or `Ye`. It is built into the font and applied automatically. Switch it off only deliberately, with `font-kerning: none`.
69
+ - **`letter-spacing`** adds the same space between every character.
70
+
71
+ ```css
72
+ /* Good */
73
+ .display-heading {
74
+ letter-spacing: -0.02em;
75
+ }
76
+
77
+ .uppercase-label {
78
+ text-transform: uppercase;
79
+ letter-spacing: 0.05em;
80
+ }
81
+ ```
82
+
83
+ ## Line-height
84
+
85
+ | Text | Value |
86
+ | --------- | ----------- |
87
+ | Headings | ~`1.1` |
88
+ | Body copy | `1.5`–`1.6` |
89
+
90
+ Tailwind's `leading-snug`, `leading-normal` and `leading-relaxed` are sensible defaults that rarely need overriding. A tightly-leaded paragraph is harder to read than a taller row is to fit.
91
+
92
+ ```css
93
+ /* Bad: card description at heading leading */
94
+ .card-description {
95
+ line-height: 1.1;
96
+ }
97
+
98
+ /* Good: it wraps to 3 lines, so it reads as body text */
99
+ .card-description {
100
+ line-height: 1.4;
101
+ }
102
+ ```
103
+
104
+ ## Text trimming with text-box
105
+
106
+ Fonts reserve space above and below the letters, which is why text sits slightly too low in buttons and badges. `text-box` trims it. Two parts: which edges to trim (`trim-both`, `trim-start`, `trim-end`) and where:
107
+
108
+ | Keyword | Trims at |
109
+ | ------------ | ----------------------------------------------------- |
110
+ | `cap` | The cap height (top) |
111
+ | `alphabetic` | The baseline (bottom) |
112
+ | `text` | The font's own text edge, keeping room for descenders |
113
+
114
+ ```css
115
+ /* trim top and bottom */
116
+ .badge {
117
+ text-box: trim-both cap alphabetic;
118
+ }
119
+
120
+ /* trim only the top */
121
+ .heading {
122
+ text-box: trim-start cap;
123
+ }
124
+
125
+ /* trim only the bottom */
126
+ .label {
127
+ text-box: trim-end alphabetic;
128
+ }
129
+ ```
130
+
131
+ Supported in Chromium (133+), Safari (18.2+) and Firefox (154+). Treat it as progressive enhancement, where unsupported browsers keep the default leading.