@its-thepoe/better-typography 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @its-thepoe/better-typography
2
+
3
+ **Agent Skill** for better-typography.
4
+
5
+ Install:
6
+
7
+ ```bash
8
+ npx @its-thepoe/skills@latest install better-typography
9
+ ```
package/SKILL.md ADDED
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: better-typography
3
+ description: Web typography from choosing fonts to spacing, wrapping and accessibility. Use when picking or pairing typefaces, configuring variable fonts or OpenType features, setting up a type scale, checking heading hierarchy, styling text in components, truncating text, styling underlines, selection, placeholders or carets, or reviewing frontend code for typography. Triggers on typography, fonts, font formats, woff2, variable fonts, font-weight, opentype, font-feature-settings, letter-spacing, line-height, type scale, heading hierarchy, heading levels, tabular numbers, text-wrap, truncation, line clamp, underlines, text-decoration, text selection, iOS input zoom, font smoothing, text contrast, measure, line length, text-box, smart punctuation, drop cap.
4
+ ---
5
+
6
+ # Great typography
7
+
8
+ Good typography is mostly restraint. A sensible scale, comfortable spacing and enough contrast beat any clever effect. A label, a table cell, a marketing headline and an article paragraph should not share one set of rules. Apply these principles when building or reviewing anything with text in it.
9
+
10
+ **Match the project's styling system.** Before suggesting or writing any fix, check how the codebase styles things and express every change in that system: Tailwind utilities in a Tailwind project, plain declarations in CSS, CSS Modules, styled-components or StyleX. The [cheat sheet](css-cheat-sheet.md) maps each declaration to its Tailwind equivalent. Never introduce a second styling approach just to apply a typography fix.
11
+
12
+ ## Quick Reference
13
+
14
+ | Category | When to use | Reference |
15
+ | --- | --- | --- |
16
+ | Choosing fonts | Font categories, pairing, formats, typeface anatomy | [choosing-fonts.md](choosing-fonts.md) |
17
+ | Variable fonts & OpenType | Axes, weights, tabular numbers, stylistic sets | [variable-fonts-and-opentype.md](variable-fonts-and-opentype.md) |
18
+ | Spacing & sizing | Type scale, heading hierarchy, line-height, letter-spacing, text trimming | [spacing-and-sizing.md](spacing-and-sizing.md) |
19
+ | Wrapping & punctuation | Measure, wrapping, truncation, smart punctuation, RTL | [wrapping-and-punctuation.md](wrapping-and-punctuation.md) |
20
+ | Details & accessibility | Underlines, selection, forms, decorative text, contrast | [details-and-accessibility.md](details-and-accessibility.md) |
21
+ | CSS cheat sheet | Quick lookup of every property covered, with Tailwind equivalents | [css-cheat-sheet.md](css-cheat-sheet.md) |
22
+
23
+ ## Core Principles
24
+
25
+ ### 1. Serve the Right Format
26
+
27
+ Use `.woff2` (Brotli compression, broadly supported) on the web. `.woff` is a fallback only for very old browsers; `.ttf` and `.otf` are raw desktop formats with no web compression. How the files are loaded is the project's own concern, this skill does not prescribe it.
28
+
29
+ ### 2. Properties Over Raw Tags
30
+
31
+ 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`. Properties keep working when a non-variable fallback renders. Reserve the raw-tag properties for custom axes (`"GRAD" 80`) and niche features (`"ss01" 1`) that have no property of their own.
32
+
33
+ ### 3. No Fake Weights
34
+
35
+ When a weight or style is not loaded, the browser synthesizes it. That is a safety mechanism, not a feature. Set `font-synthesis: none` so missing files fail visibly instead of rendering a faked bold or italic.
36
+
37
+ ### 4. Fewer Fonts, Sizes and Weights
38
+
39
+ Rarely use more than three fonts. Weight and size define hierarchy, but overusing them hurts readability quickly. Pair for contrast, not similarity: a serif headline with a sans body reads as deliberate, two near-identical sans-serifs read as a mistake.
40
+
41
+ ### 5. Use a Type Scale with Semantic Names
42
+
43
+ Define a small set of sizes and deviate from it as little as possible. Hard-coded sizes without a system break down at scale. For solo projects, default names like `text-sm` work fine as long as the usage rules are clear. On a team, name sizes by use (`text-body-sm`), not by size, so the rules stay consistent.
44
+
45
+ ### 6. Heading Sizes Descend with Level
46
+
47
+ Map each heading level used on a page to a descending step of the type scale: a lower level must never render larger than a higher one on the same page. Adjacent levels may share a size toward the small end of the scale as long as weight or spacing keeps them distinct. Pick the tag from the document outline and control the size with CSS; never skip levels or reach for an `h4` because it "looks right".
48
+
49
+ ### 7. Line-Height by Role
50
+
51
+ Headings tighter, around `1.1`. Body copy `1.5` to `1.6`. Prefer unitless values so line-height scales with the font size; fixed values like `24px` do not.
52
+
53
+ ### 8. Letter-Spacing by Size
54
+
55
+ Large headings often look better with slightly negative letter-spacing. Small uppercase labels need a little positive letter-spacing so letters do not feel crowded. Body copy at reading sizes needs neither.
56
+
57
+ ### 9. Cap the Measure
58
+
59
+ Long lines make it hard for the eye to find the next line. Cap long-form text around 60–75 characters per line. Any unit works: `65ch` measures characters directly, and a pixel or rem cap is just as good: at a `16px` body size the range lands roughly between `560px` and `680px` depending on the font, so Tailwind's `max-w-xl` or `max-w-2xl` fit. What matters is that a cap exists and the resulting line length sits in range.
60
+
61
+ ### 10. Wrap Deliberately
62
+
63
+ `text-wrap: balance` distributes text evenly across lines: use it on headings. `text-wrap: pretty` avoids leaving a single short word on the final line: use it on descriptions. Skip both in long-form text: browsers ignore `balance` past a few lines anyway, and evening out a whole paragraph wastes space and makes it harder to read. `overflow-wrap: break-word` where long words, links or IDs could escape the container. `white-space: nowrap` on labels and badges where a line break looks broken.
64
+
65
+ ### 11. Tabular Numbers on Changing Values
66
+
67
+ Digits have different widths by default, so timers, counters and prices shift layout as they update. Apply `font-variant-numeric: tabular-nums` to any value that changes.
68
+
69
+ ### 12. Truncate Without Losing Content
70
+
71
+ Single line: `text-overflow: ellipsis` with `overflow: hidden` and `white-space: nowrap`. Multiple lines: `line-clamp`. Truncation hides content, so if the missing text matters, keep the full value reachable in a tooltip or expanded view.
72
+
73
+ ### 13. Write Copy Naturally, Style with CSS
74
+
75
+ Store text in natural case and control presentation with `text-transform`, so redesigns never require rewriting copy. Use smart punctuation: curly quotes in prose (straight quotes in code), an en dash for ranges like `2010–2020`, an em dash to set off a thought, the single ellipsis character, ` ` to keep values like `16 px` together and `­` to control where long words may break.
76
+
77
+ ### 14. Underlines from the Font
78
+
79
+ 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`, or tune manually with `text-decoration-thickness`, `text-underline-offset` and `text-decoration-skip-ink`. `text-decoration-style` draws the line dotted, dashed or wavy; a dotted underline is a common hint that a word carries extra information, like an abbreviation or a defined term. Unless the only thing animating is a color change, build the underline as a separate element instead of using `text-decoration`: color is the only part of a real underline that animates reliably.
80
+
81
+ ### 15. Inputs at 16px on Mobile
82
+
83
+ iOS Safari zooms the whole page when an input's text is smaller than `16px`. Keep input text at `16px` on mobile viewports (`text-base sm:text-sm`). Avoid the `maximum-scale=1` viewport meta: Safari ignores it for pinch zoom, but every other browser honors it and blocks zooming, which fails WCAG.
84
+
85
+ ### 16. Size and Contrast Floors
86
+
87
+ Body text `16px` (the web default and the right reading size). UI text can go smaller: `14px` for inputs and menus (inputs still need `16px` on mobile, see principle 15), `13px` for captions, rarely below `12px`. WCAG AA: `4.5:1` contrast for regular text, `3:1` for large text (roughly `24px` and up).
88
+
89
+ ### 17. Font Smoothing on the Root
90
+
91
+ On macOS text renders heavier than intended. Apply `-webkit-font-smoothing: antialiased` and `-moz-osx-font-smoothing: grayscale` (both covered by Tailwind's `antialiased`) once on the root layout so they cover all text.
92
+
93
+ ### 18. Logical Properties for Direction
94
+
95
+ To support right-to-left content, use direction-agnostic properties: `margin-inline-start` instead of `margin-left`, `text-align: start` instead of `left`. Set `lang` so browsers pick the right quotes and hyphenation, and `dir="rtl"` where needed.
96
+
97
+ ### 19. Style the Selection, Disable It Where It Distracts
98
+
99
+ `::selection` is a subtle way to embed brand in the reading experience; keep the combination legible. Use `user-select: none` on button labels where copying is unlikely and selection feels distracting, and make sure `cmd+A` only grabs text the user expects to copy. In cross-platform apps that feel closer to native, disable selection for the interface and keep it only on content worth copying.
100
+
101
+ ## Review Output Format
102
+
103
+ Always present changes as a markdown table with **Before** and **After** columns. Include every change you made, not just a subset. Never list findings as separate "Before:" / "After:" lines outside of a table. Group changes by principle using a heading above each table, and keep each row focused on a single diff. Write every **After** snippet in the styling system the project already uses.
104
+
105
+ ### Example
106
+
107
+ #### Tabular numbers
108
+ | Before | After |
109
+ | --- | --- |
110
+ | `<span>{price}</span>` on live price | `<span className="tabular-nums">{price}</span>` |
111
+ | `font-feature-settings: "tnum" 1` | `font-variant-numeric: tabular-nums` |
112
+
113
+ #### Line-height and measure
114
+ | Before | After |
115
+ | --- | --- |
116
+ | `leading-none` on body paragraph | `leading-normal` (body needs `1.5`–`1.6`) |
117
+ | Full-width article column | `max-w-2xl` (~65 characters per line at `16px`) |
118
+
119
+ Rows should cite the specific file and property when it is not obvious from the snippet. If a principle was reviewed but nothing needed to change, omit that table entirely.
120
+
121
+ ## Common Mistakes
122
+
123
+ | Mistake | Fix |
124
+ | --- | --- |
125
+ | `.ttf`/`.otf` served on the web | Convert to `.woff2` |
126
+ | `font-variation-settings: "wght"` for weight | `font-weight` (works with non-variable fallbacks) |
127
+ | `font-feature-settings: "tnum" 1` | `font-variant-numeric: tabular-nums` |
128
+ | Browser-faked bold or italic | Load the file, set `font-synthesis: none` |
129
+ | Hard-coded one-off font sizes | Use the type scale |
130
+ | `h3` rendered larger than `h2` on the same page | Map heading levels to descending scale steps |
131
+ | Heading tag picked for its size, skipping levels | Level from the document outline, size via CSS |
132
+ | `line-height: 24px` on scalable text | Unitless value (`1.5`) |
133
+ | Full-width paragraphs | Cap around 60–75 characters per line |
134
+ | Orphan on the last line of a paragraph | `text-wrap: pretty` |
135
+ | Lopsided two-line heading | `text-wrap: balance` |
136
+ | Numbers cause layout shift | `tabular-nums` |
137
+ | Truncated text with no way to read it | Tooltip or expanded view for the full value |
138
+ | `UPPERCASE` typed into copy | Natural case + `text-transform` |
139
+ | Justified text in an interface | `text-align: start`; reserve justify for specific editorial layouts |
140
+ | Underline cuts through descenders | `text-decoration-skip-ink: auto`, `from-font` metrics |
141
+ | Inputs below `16px` zoom on iOS | `text-base sm:text-sm` |
142
+ | `margin-left` in RTL-capable UI | `margin-inline-start` |
143
+ | Selectable button labels in native-feel UI | `user-select: none`, keep selection on real content |
144
+ | Extra-info hint with no visual cue | Dotted underline via `text-decoration-style: dotted` |
145
+ | Tailwind classes dropped into a CSS-in-JS codebase (or the reverse) | Express the fix in the styling system the project already uses |
146
+
147
+ ## Review Checklist
148
+
149
+ - [ ] Web fonts are `.woff2`
150
+ - [ ] `font-weight` / `font-variant-*` used instead of raw axis and feature tags
151
+ - [ ] `font-synthesis: none` set; no faked weights or styles
152
+ - [ ] Sizes come from the type scale, no one-off values
153
+ - [ ] Heading sizes descend with level (`h1` ≥ `h2` ≥ `h3`…), levels stay visually distinct, none skipped
154
+ - [ ] Headings ~`1.1` line-height, body `1.5`–`1.6`, unitless
155
+ - [ ] Large headings have slightly negative tracking, small uppercase labels positive
156
+ - [ ] Long-form text capped around 60–75 characters per line
157
+ - [ ] Headings use `text-wrap: balance`, body uses `text-wrap: pretty`
158
+ - [ ] Changing numbers use `tabular-nums`
159
+ - [ ] Truncated content is reachable in full somewhere
160
+ - [ ] Copy stored in natural case, presentation via `text-transform`
161
+ - [ ] Underlines use `from-font` or tuned thickness, offset and skip-ink
162
+ - [ ] Inputs are `16px`+ on mobile viewports
163
+ - [ ] Text sizes and contrast meet the floors (`16px` body, `4.5:1` / `3:1`)
164
+ - [ ] `antialiased` applied once on the root layout
165
+ - [ ] Directional properties are logical (`inline-start`, `start`)
166
+ - [ ] Any styled `::selection` stays legible
@@ -0,0 +1,63 @@
1
+ # Choosing fonts
2
+
3
+ Choosing a typeface, the right file format and understanding 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. Fonts like SF Pro and Heldane ship a `Display` variant for large sizes and a `Text` variant for smaller sizes. Use the variant that matches 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, but overusing them hurts readability quickly.
25
+ - Pair for contrast, not similarity. A serif headline with a sans body looks like a deliberate display/reading split. Two near-identical sans-serifs look like a mistake.
26
+
27
+ ## Font family scope
28
+
29
+ Applying or reviewing typography never requires a new typeface. Use the product's existing type system unless the task explicitly asks for a type change, and do not introduce a paid or proprietary face just to satisfy a review checklist. Rendering details like font smoothing, text wrapping and tabular numbers do not override the project's chosen font family.
30
+
31
+ When a type change is asked for: a system-native macOS/iOS feel comes from the system stack; a commercial face such as Helvetica Now is a brand decision and keeps a practical fallback stack.
32
+
33
+ ```css
34
+ /* System-native macOS/iOS feel */
35
+ html {
36
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
37
+ }
38
+
39
+ /* Commercial brand face with safe fallbacks */
40
+ html {
41
+ font-family: "Helvetica Now", "Helvetica Neue", Arial, sans-serif;
42
+ }
43
+ ```
44
+
45
+ ## Formats
46
+
47
+ | Format | Notes |
48
+ | --- | --- |
49
+ | `.woff2` | Brotli compression, broadly supported. Use this on the web. |
50
+ | `.woff` | Older compression. Fallback only for very old browsers. |
51
+ | `.ttf` / `.otf` | Raw formats, no web compression, larger files. Desktop only unless there is no other option. |
52
+
53
+ ## Anatomy of a typeface
54
+
55
+ | Term | Meaning |
56
+ | --- | --- |
57
+ | x-height | Height of a lowercase `x` |
58
+ | Cap height | Height of uppercase letters |
59
+ | Baseline | The invisible line letters sit on |
60
+ | Ascender | Part of a letter rising above the x-height |
61
+ | Descender | Part dropping below the baseline |
62
+
63
+ These measurements are why two fonts at the same `font-size` can look like different sizes. A font with 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 | `antialiased` |
16
+ | `font-synthesis: none` | No faux bold or italic | `[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` | Stop text selection | `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,117 @@
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, cutting through descenders or 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
+ The line does not have to be solid. `text-decoration-style` can draw it dotted, dashed or wavy. A dotted underline is a common hint that a word carries extra information, like an abbreviation or a defined term:
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
+ Unless the only thing animating is a color change, build the underline as a custom element instead of using `text-decoration`: color is the only part of a real underline that animates reliably. Animate the custom element however the effect requires.
41
+
42
+ ## Selection
43
+
44
+ - `::selection` changes the background and color of selected text; a subtle way to embed brand. Keep the combination legible.
45
+ - `user-select: none` on button labels: copying a label is unlikely and selection there feels distracting. Also make sure `cmd+A` only grabs text the user expects to copy.
46
+ - In cross-platform apps that feel closer to native, disable text selection for the interface and keep it enabled only for content the user may actually want to copy.
47
+ - `::target-text` styles the phrase a shared link scrolls to.
48
+ - The Custom Highlight API styles ranges you pick yourself, like search matches, without extra markup.
49
+
50
+ ## Forms and editable text
51
+
52
+ - `::placeholder` styles the hint in an empty field.
53
+ - `caret-color` colors the blinking insertion bar. Color is about as far as caret styling goes: a fully custom caret is very difficult to build and usually not worth it unless a very specific effect calls for it.
54
+
55
+ ### iOS input zoom
56
+
57
+ Focusing an input with text smaller than `16px` zooms the whole page (an accessibility feature: `16px` is the web default and Safari treats smaller as too hard to read while typing).
58
+
59
+ ```tsx
60
+ // Good: 16px on mobile, smaller from the sm breakpoint up
61
+ <input className="text-base sm:text-sm" type="email" />
62
+ ```
63
+
64
+ Avoid the `maximum-scale=1` viewport meta as a fix: Safari ignores the cap for pinch zoom, but every other browser honors it and restricts pinch zoom, which fails WCAG 1.4.4. The responsive input size solves the zoom with no accessibility cost.
65
+
66
+ ## Decorative text
67
+
68
+ | Property | Effect |
69
+ | --- | --- |
70
+ | `::first-letter` | Drop cap, widely supported |
71
+ | `::first-line` | Styles only the first line |
72
+ | `initial-letter` | Sizes the drop cap; limited support, no Firefox yet |
73
+ | `background-clip: text` | Clips a background or gradient to the letter shapes |
74
+ | `-webkit-text-stroke` | Outlines the letters; works across modern browsers despite the prefix |
75
+ | `text-shadow` | Like `box-shadow` but follows the character shapes |
76
+
77
+ If a text stroke draws lines inside the letters, that is the font: the stroke traces every contour and variable fonts usually keep overlapping shapes unmerged. Static fonts do not have this issue.
78
+
79
+ ## Sizes and contrast
80
+
81
+ Typography must survive the reader changing it: zoom, a larger browser font size, overridden line height or letter spacing.
82
+
83
+ | Text | Size |
84
+ | --- | --- |
85
+ | Body | `16px` (the web default, the right reading size) |
86
+ | Inputs and menus | `14px` |
87
+ | Captions | `13px` |
88
+ | Floor | Rarely below `12px` |
89
+
90
+ WCAG AA contrast:
91
+
92
+ | Text | Minimum |
93
+ | --- | --- |
94
+ | Regular text | `4.5:1` |
95
+ | Large text (~`24px`+) | `3:1` |
96
+
97
+ Bigger, heavier letters stay legible at lower contrast.
98
+
99
+ ## Font smoothing
100
+
101
+ On macOS text renders heavier than intended. Apply font smoothing once on the root layout so it covers all text. Tailwind's `antialiased` sets both properties:
102
+
103
+ ```css
104
+ html {
105
+ -webkit-font-smoothing: antialiased;
106
+ -moz-osx-font-smoothing: grayscale;
107
+ }
108
+ ```
109
+
110
+ ```tsx
111
+ <html lang="en">
112
+ <body class="font-sans antialiased">
113
+ <main>{children}</main>
114
+ </body>
115
+ </html>
116
+ ```
117
+
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@its-thepoe/better-typography",
3
+ "version": "1.0.0",
4
+ "description": "Agent Skill: better-typography.",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "agent-skills",
8
+ "better-typography",
9
+ "design",
10
+ "ui"
11
+ ],
12
+ "files": [
13
+ "README.md",
14
+ "SKILL.md",
15
+ "*.md",
16
+ "package.json"
17
+ ],
18
+ "exports": {
19
+ ".": "./SKILL.md",
20
+ "./SKILL.md": "./SKILL.md"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/its-thepoe/skills.git",
25
+ "directory": "design/better-typography"
26
+ }
27
+ }
@@ -0,0 +1,104 @@
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 predefined sizes used across a product, deviated from as little as possible. Hard-coding sizes without a system 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
+ There are many existing scales to pick from, or define a custom one. The Tailwind type scale (`text-xs` through `text-9xl`, each class pairing a size with a matching line height) is a solid ready-made choice.
29
+
30
+ For solo projects the default names work fine as long as there are clear rules for where each size is used. On a team, give sizes semantic names: `text-sm` tells you the size but not the use; `text-body-sm` keeps sizes consistent with clear usage rules.
31
+
32
+ ## Heading hierarchy
33
+
34
+ Assign each heading level to a descending step of the scale, so hierarchy comes from the scale instead of one-off sizes:
35
+
36
+ ```css
37
+ h1 { font-size: var(--text-2xl); }
38
+ h2 { font-size: var(--text-xl); }
39
+ h3 { font-size: var(--text-lg); }
40
+ ```
41
+
42
+ In Tailwind the same mapping is utility classes per level (`text-2xl`, `text-xl`, `text-lg`), typically centralized in a component or `@layer base` rather than repeated inline.
43
+
44
+ When reviewing a page, compare the computed size of each heading level across the whole page: a level that renders larger than the one above it (an `h3` bigger than an `h2`) breaks the hierarchy and reads as a mistake. Deep levels (`h4`, `h5`) may share a size when the scale runs out of comfortable steps, as long as weight or letter-spacing keeps them distinct. A heading should not be smaller than body text unless it is deliberately a label-style overline.
45
+
46
+ Heading levels are also a navigation structure: screen readers jump through a page by level. Keep the outline sequential (one `h1`, no skipped levels) and pick the tag from the document structure, never because a lower level "looks right" at its default size. The size belongs in CSS.
47
+
48
+ ## Kerning and letter-spacing
49
+
50
+ - **Kerning** adjusts specific pairs like `AV` or `Ye`. It is built into the font and browsers apply it automatically. Only switch it off deliberately with `font-kerning: none`.
51
+ - **`letter-spacing`** adds the same space between every character:
52
+ - Large headings often look better slightly negative.
53
+ - Small uppercase labels need a little positive spacing so letters do not feel crowded.
54
+ - Body copy needs neither.
55
+
56
+ ```css
57
+ /* Good */
58
+ .display-heading {
59
+ letter-spacing: -0.02em;
60
+ }
61
+
62
+ .uppercase-label {
63
+ text-transform: uppercase;
64
+ letter-spacing: 0.05em;
65
+ }
66
+ ```
67
+
68
+ ## Line-height
69
+
70
+ | Text | Value |
71
+ | --- | --- |
72
+ | Headings | ~`1.1` |
73
+ | Body copy | `1.5`–`1.6` |
74
+
75
+ Prefer unitless values: they scale with the font size, fixed values like `line-height: 24px` do not. Tailwind's `leading-snug`, `leading-normal` and `leading-relaxed` are sensible defaults that rarely need overriding.
76
+
77
+ ## Text trimming with text-box
78
+
79
+ 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:
80
+
81
+ | Keyword | Trims at |
82
+ | --- | --- |
83
+ | `cap` | The cap height (top) |
84
+ | `alphabetic` | The baseline (bottom) |
85
+ | `text` | The font's own text edge, keeping room for descenders |
86
+
87
+ ```css
88
+ /* trim top and bottom */
89
+ .badge {
90
+ text-box: trim-both cap alphabetic;
91
+ }
92
+
93
+ /* trim only the top */
94
+ .heading {
95
+ text-box: trim-start cap;
96
+ }
97
+
98
+ /* trim only the bottom */
99
+ .label {
100
+ text-box: trim-end alphabetic;
101
+ }
102
+ ```
103
+
104
+ Browser support is still limited; treat it as progressive enhancement.
@@ -0,0 +1,102 @@
1
+ # Variable fonts and OpenType
2
+
3
+ What a font file can do beyond drawing letters, and how to reach those abilities from CSS.
4
+
5
+ ## Static vs variable
6
+
7
+ - **Static font:** one weight and one style per file. Regular + medium + bold = three files.
8
+ - **Variable font:** an entire range in one file. Any value in the range works, e.g. `font-weight: 589`.
9
+
10
+ A variable font is not automatically better. One or two weights: static files can be smaller. Several weights, optical sizes or custom axes: a variable font usually makes more sense.
11
+
12
+ ## No fake weights
13
+
14
+ When you use a weight or style you have not loaded, the browser synthesizes it. It works, but it is a safety mechanism, not a feature. Disable it so missing files fail visibly:
15
+
16
+ ```css
17
+ :root {
18
+ font-synthesis: none;
19
+ }
20
+ ```
21
+
22
+ ## Axes
23
+
24
+ Variable-font controls, each with a four-letter tag. A font only supports the axes its designer included.
25
+
26
+ | Axis | Tag | Controls |
27
+ | --- | --- | --- |
28
+ | Weight | `wght` | Stroke thickness (like `font-weight`) |
29
+ | Optical size | `opsz` | Details and spacing tuned for the display size |
30
+ | Width | `wdth` | Glyph width |
31
+ | Slant | `slnt` | Slant angle |
32
+ | Custom | e.g. `GRAD` (Roboto Flex) | Whatever the designer built |
33
+
34
+ Inter's variable file exposes only `wght` and `opsz`.
35
+
36
+ Optical sizes predate variable fonts and many fonts still ship them as separate files: Heldane Text is sturdier and more spaced for reading sizes, Heldane Display has finer details for large sizes.
37
+
38
+ ## Properties over axis tags
39
+
40
+ When a property exists, use it. `font-weight` keeps working when a non-variable fallback renders; `font-variation-settings` silently does nothing. Save the raw tags for custom axes with no property of their own:
41
+
42
+ ```css
43
+ /* Good: common axes use the properties */
44
+ .heading {
45
+ font-weight: 650;
46
+ font-optical-sizing: auto;
47
+ }
48
+
49
+ /* Good: custom axis with no property of its own */
50
+ .heading-grade {
51
+ font-variation-settings: "GRAD" 80;
52
+ }
53
+
54
+ /* Bad: weight via raw tag breaks on fallback fonts */
55
+ .heading {
56
+ font-variation-settings: "wght" 650;
57
+ }
58
+ ```
59
+
60
+ ## OpenType features
61
+
62
+ OpenType is the standard behind almost every modern font. Features are extra built-in options and, unlike axes, they work the same on static and variable fonts. A font only ships the features its designer included.
63
+
64
+ | Tag | Feature |
65
+ | --- | --- |
66
+ | `tnum` | Tabular numbers: every digit the same width |
67
+ | `zero` | Slashed zero: `0` distinct from `O` |
68
+ | `liga` | Ligatures: joins pairs like "fi" into one shape |
69
+ | `ss01`–`ss20` | Stylistic sets (numbered slots) |
70
+ | `cv01`–`cv99` | Character variants (numbered slots) |
71
+
72
+ Same rule as axes: prefer the `font-variant-*` properties, reserve `font-feature-settings` for tags with no property:
73
+
74
+ ```css
75
+ /* Good: common features use the properties */
76
+ .price {
77
+ font-variant-numeric: tabular-nums;
78
+ }
79
+
80
+ /* Good: slashed zero via the property too */
81
+ .id {
82
+ font-variant-numeric: slashed-zero;
83
+ }
84
+
85
+ /* Good: niche feature with no property of its own */
86
+ .logo {
87
+ font-feature-settings: "ss01" 1;
88
+ }
89
+ ```
90
+
91
+ Tabular numbers matter for changing values: without them each digit has a different width and the layout shifts as values update.
92
+
93
+ ## Small caps, superscripts, subscripts
94
+
95
+ - **Small capitals:** uppercase letters drawn at a smaller size. Enable real ones with `font-variant-caps`.
96
+ - **Superscripts** sit above the normal line (the 2 in x²), **subscripts** below it (H₂O). Enable proper glyphs with `font-variant-position`.
97
+
98
+ Both require the font to include the glyphs.
99
+
100
+ ## Stylistic sets and character variants
101
+
102
+ `ss01` = stylistic set, slot 01. `cv11` = character variant, slot 11. What each slot does differs font to font, which is why they are numbered, not named. Check the font's docs. In Inter, `ss01` switches to open digits and `cv11` swaps in a single-story `a`.
@@ -0,0 +1,70 @@
1
+ # Wrapping and punctuation
2
+
3
+ Where lines start, where they end, where they break and which characters they use.
4
+
5
+ ## Measure (line length)
6
+
7
+ Long lines make it harder for the eye to find the start of the next line. For long-form text, aim for 60–75 characters per line.
8
+
9
+ Any unit works. `65ch` measures characters directly (one `ch` is the width of the `0` in the current font), but a pixel or rem cap is just as good: at a `16px` body size the 60–75 character range lands roughly between `560px` and `680px` depending on the font, so Tailwind's `max-w-xl` (`576px`) or `max-w-2xl` (`672px`) fit. What matters is that a cap exists and the resulting line length sits in range; recheck it if the body font size changes.
10
+
11
+ ## Alignment
12
+
13
+ `text-align` controls where each line starts and ends. `text-align: justify` stretches spaces until both edges line up; it can work in specific editorial layouts but avoid it in most interfaces.
14
+
15
+ ## Wrapping
16
+
17
+ | Property | Use |
18
+ | --- | --- |
19
+ | `text-wrap: balance` | Distributes text evenly across multiple lines |
20
+ | `text-wrap: pretty` | Avoids leaving a single short word on the final line |
21
+ | `overflow-wrap: break-word` | Lets long words, links and IDs break before escaping the container |
22
+ | `white-space: nowrap` | Keeps labels and badges on one line where a break looks broken |
23
+
24
+ Use `balance` on headings and `pretty` on descriptions; combined they give the best outcome. Skip both in long-form text: browsers ignore `balance` past a few lines anyway, and evening out a whole paragraph wastes space and makes it harder to read.
25
+
26
+ ## Truncation
27
+
28
+ - Single line: `text-overflow: ellipsis`, which needs `overflow: hidden` and `white-space: nowrap`.
29
+ - Multiple lines: `line-clamp` allows any number of lines before the ellipsis.
30
+
31
+ Truncation hides content. If the missing text matters, make the full value available elsewhere (tooltip or expanded view).
32
+
33
+ ## Case
34
+
35
+ `text-transform` changes how case appears without changing the underlying text. Write copy naturally and control presentation with CSS, so changing presentation never requires rewriting copy.
36
+
37
+ ## Smart punctuation
38
+
39
+ Keyboard characters are not always the best characters:
40
+
41
+ | Instead of | Use |
42
+ | --- | --- |
43
+ | Straight quotes `"..."` | Curly quotes that curve around the text (keep straight quotes in code) |
44
+ | Hyphen in ranges | En dash: `2010–2020` |
45
+ | Two hyphens for an aside | Em dash character |
46
+ | Three periods `...` | The single ellipsis character `…` |
47
+ | Regular space in `16 px` | `&nbsp;` so the value never breaks apart |
48
+ | Uncontrolled word breaks | `&shy;` to mark where a word may break |
49
+
50
+ ## Internationalization
51
+
52
+ - Set the `lang` attribute so browsers choose the right quotes, hyphenation and pronunciation.
53
+ - Use `dir="rtl"` for right-to-left content.
54
+ - To support both directions, use logical properties everywhere a directional one exists:
55
+
56
+ ```css
57
+ /* Good */
58
+ p {
59
+ margin-inline-start: 8px;
60
+ text-align: start;
61
+ }
62
+
63
+ /* Bad: breaks in RTL */
64
+ p {
65
+ margin-left: 8px;
66
+ text-align: left;
67
+ }
68
+ ```
69
+
70
+ The same applies to `padding` and every other directional property.