@duro-app/cli 5.16.0 → 5.18.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 (64) hide show
  1. package/README.md +12 -5
  2. package/dist/bin.js +3 -2
  3. package/dist/bin.js.map +1 -1
  4. package/dist/commands/hook.d.ts.map +1 -1
  5. package/dist/commands/hook.js +11 -2
  6. package/dist/commands/hook.js.map +1 -1
  7. package/dist/commands/list.d.ts.map +1 -1
  8. package/dist/commands/list.js +23 -2
  9. package/dist/commands/list.js.map +1 -1
  10. package/dist/commands/lookup.d.ts.map +1 -1
  11. package/dist/commands/lookup.js +15 -1
  12. package/dist/commands/lookup.js.map +1 -1
  13. package/dist/commands/mcp.d.ts +12 -0
  14. package/dist/commands/mcp.d.ts.map +1 -1
  15. package/dist/commands/mcp.js +51 -36
  16. package/dist/commands/mcp.js.map +1 -1
  17. package/dist/commands/skill.d.ts +6 -0
  18. package/dist/commands/skill.d.ts.map +1 -1
  19. package/dist/commands/skill.js +168 -24
  20. package/dist/commands/skill.js.map +1 -1
  21. package/dist/format.d.ts.map +1 -1
  22. package/dist/format.js +7 -0
  23. package/dist/format.js.map +1 -1
  24. package/dist/hook-script.d.ts +2 -2
  25. package/dist/hook-script.d.ts.map +1 -1
  26. package/dist/hook-script.js +1 -1
  27. package/dist/manifest.d.ts.map +1 -1
  28. package/dist/manifest.js +10 -4
  29. package/dist/manifest.js.map +1 -1
  30. package/dist/registry-types.d.ts +5 -0
  31. package/dist/registry-types.d.ts.map +1 -1
  32. package/dist/registry.d.ts +20 -0
  33. package/dist/registry.d.ts.map +1 -1
  34. package/dist/registry.js +38 -0
  35. package/dist/registry.js.map +1 -1
  36. package/package.json +6 -5
  37. package/registry.json +35 -0
  38. package/{LICENSE → skills/colors/LICENSE} +1 -1
  39. package/skills/colors/NOTICE.md +36 -0
  40. package/skills/colors/SKILL.md +99 -0
  41. package/skills/colors/color-formats.md +90 -0
  42. package/skills/colors/color-usage.md +109 -0
  43. package/skills/colors/contrast.md +70 -0
  44. package/skills/colors/palette-generation.md +108 -0
  45. package/skills/colors/palette-structure.md +72 -0
  46. package/skills/colors/token-naming.md +99 -0
  47. package/skills/typography/LICENSE +21 -0
  48. package/skills/typography/NOTICE.md +36 -0
  49. package/skills/typography/SKILL.md +169 -0
  50. package/skills/typography/choosing-fonts.md +53 -0
  51. package/skills/typography/css-cheat-sheet.md +67 -0
  52. package/skills/typography/details-and-accessibility.md +84 -0
  53. package/skills/typography/spacing-and-sizing.md +87 -0
  54. package/skills/typography/variable-fonts-and-opentype.md +100 -0
  55. package/skills/typography/wrapping-and-punctuation.md +23 -0
  56. package/skills/ui/LICENSE +21 -0
  57. package/skills/ui/NOTICE.md +37 -0
  58. package/skills/ui/SKILL.md +139 -0
  59. package/skills/ui/animations.md +170 -0
  60. package/skills/ui/enter-exit.md +157 -0
  61. package/skills/ui/icon-transitions.md +72 -0
  62. package/skills/ui/icons.md +105 -0
  63. package/skills/ui/performance.md +63 -0
  64. package/skills/ui/surfaces.md +166 -0
@@ -0,0 +1,169 @@
1
+ ---
2
+ name: typography
3
+ description: Sets and reviews how text renders in your product, from the type scale and spacing to font features, wrapping, truncation and punctuation.
4
+ ---
5
+
6
+ # Typography
7
+
8
+ This skill sets and reviews how text renders, from the type scale and spacing to font loading, wrapping and punctuation. It writes every fix in the project's styling system, and the [cheat sheet](css-cheat-sheet.md) maps each declaration to its Tailwind utility.
9
+
10
+ A decision of this repository (resolved at its applicable scope with `aval resolve`) outranks a value in this skill. Apply token requirements and gap routing only where the repository's decisions require them, keeping their named exemptions. On `duro-stack`, a non-exempt measure without a token is a design-system gap under ADR-0006/0027, never a local literal.
11
+
12
+ Where Duro defines the value, these win: the CLAUDE.md "Typography Presets" table, `typography.css.ts` and `type-presets.css.ts`.
13
+
14
+ The words belong to `writing`, semantic heading structure to `accessibility` and spatial RTL layout to `layout`. Measure contrast with `colors`; whether it passes is `accessibility`'s.
15
+
16
+ ## Measured, not preferred
17
+
18
+ Some values here are exact. Unitless line-height, weight `400` or heavier below `18px`, a 60–75 character measure, `16px` inputs on iOS and `tabular-nums` on changing values are findings when missed.
19
+
20
+ Letter-spacing, pairing and scale ratios are heuristics. Report them only where they break the project's own scale. Never propose a new typeface, paid or free, unless the task asks for a type change.
21
+
22
+ ## Fewer fonts, sizes and weights
23
+
24
+ Rarely use more than three fonts; marketing pages can carry more than apps. Weight and size carry hierarchy, so each extra one dilutes it. Pair for contrast. A serif headline over a sans body reads as deliberate, two near-identical sans-serifs read as a mistake.
25
+
26
+ Below `18px`, use weight `400` or heavier. Weights `100`–`300` belong at `28px` and up, checked against the background even there. Categories, `Display` and `Text` cuts, formats and fallback stacks are in [choosing-fonts.md](choosing-fonts.md).
27
+
28
+ ## Load the faces the design uses
29
+
30
+ Browsers synthesize a weight or style the family doesn't ship, distorting the face. Load every face the design uses. Disable synthesis only after verifying the fallback stack, and only the unwanted mode. The [synthesis longhands](variable-fonts-and-opentype.md#disable-synthesis-narrowly) show how.
31
+
32
+ ## Properties over raw tags
33
+
34
+ When a CSS property exists, use it. `font-weight: 650`, not `font-variation-settings: "wght" 650`. `font-variant-numeric: tabular-nums`, not `font-feature-settings: "tnum" 1`. Properties keep working when a non-variable fallback renders.
35
+
36
+ Leave `font-optical-sizing` at its default `auto`. Hard-code `"opsz"` only when the design deliberately decouples optical size from font size. Reserve raw tags for custom axes like `"GRAD" 80` and features with no property, like `"ss01" 1`. Axes, features and variable versus static files are in [variable-fonts-and-opentype.md](variable-fonts-and-opentype.md).
37
+
38
+ ## Use a type scale with semantic names
39
+
40
+ Define a small set of sizes and deviate from it as little as possible. Pair each size with its line-height and weight, so a role is one decision instead of three.
41
+
42
+ 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. A [role-based starting scale](spacing-and-sizing.md#type-scale) is in the reference.
43
+
44
+ ## Heading sizes descend with level
45
+
46
+ Map heading levels to descending steps of the scale, so a subordinate heading never overpowers its parent. Deep levels may share a size where the scale runs out of steps, as long as weight or letter-spacing keeps them distinct. A heading is never smaller than body text unless it is a deliberate overline. See the [level mapping](spacing-and-sizing.md#heading-hierarchy).
47
+
48
+ ## Line-height by role
49
+
50
+ Display text `1.1`, headings `1.2`–`1.3`, body copy `1.5`–`1.6`. Use unitless values so line-height scales with the font size; a fixed `24px` does not.
51
+
52
+ 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. See the [card description example](spacing-and-sizing.md#line-height-for-wrapping-text).
53
+
54
+ ## Letter-spacing by size
55
+
56
+ Headings at `24px` and up take `-0.01em` to `-0.02em`. Uppercase labels at `14px` or smaller take `0.05em`. Body copy stays at `0`. Use `em` so tracking scales with the size.
57
+
58
+ Kerning is built into the font and on by default. `font-kerning: none` is never a fix.
59
+
60
+ ## Cap the measure
61
+
62
+ Cap long-form text at 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).
63
+
64
+ ## Wrap deliberately
65
+
66
+ - `text-wrap: balance` evens out headings and short descriptions. Never on paragraphs, and Chromium ignores it past six lines.
67
+ - `text-wrap: pretty` keeps a lone word off the last line. Safe on any paragraph, long-form included.
68
+ - `overflow-wrap: break-word` where a long word, link or ID could escape the container.
69
+ - `white-space: nowrap` on labels and badges where a line break looks broken.
70
+
71
+ Keep interface text at `text-align: start`. `justify` belongs only in specific editorial layouts.
72
+
73
+ ## Tabular numbers on changing values
74
+
75
+ Apply `font-variant-numeric: tabular-nums` to timers, counters, prices and numeric table columns, so every digit keeps one width and nothing shifts. Leave figures in prose proportional.
76
+
77
+ ## Truncate with a way back
78
+
79
+ For one line, `overflow: hidden`, `text-overflow: ellipsis` and `white-space: nowrap`, which Tailwind's `truncate` sets together. For several, `display: -webkit-box`, `-webkit-box-orient: vertical`, `-webkit-line-clamp: 3` and `overflow: hidden`, which Tailwind's `line-clamp-3` emits.
80
+
81
+ A truncated value needs a way back to the full text, which `layout` owns.
82
+
83
+ ## Natural case, typographic punctuation
84
+
85
+ Store text in natural case and set case with `text-transform`, so a redesign never means rewriting copy.
86
+
87
+ Rendered text uses typographic characters, never their keyboard stand-ins. Curly quotes, en dashes, the ellipsis character and non-breaking spaces are in the [substitution table](wrapping-and-punctuation.md#smart-punctuation). Code keeps straight quotes.
88
+
89
+ ## Underlines from the font
90
+
91
+ Set `text-underline-position: from-font` and `text-decoration-thickness: from-font`, or tune `text-underline-offset` and thickness by hand. A dotted `text-decoration-style` hints that a word carries extra information, such as an abbreviation or a defined term. See the [underline recipes](details-and-accessibility.md#underlines).
92
+
93
+ Color is the only part of a real underline that animates reliably. Any other underline animation needs a separate element, and its motion is `ui`'s.
94
+
95
+ ## Inputs at 16px on mobile
96
+
97
+ iOS Safari zooms the whole page when an input's text is smaller than `16px`. Two fixes hold `16px` and look different, so ask which one the design wants:
98
+
99
+ - Size the input up on mobile (`text-base sm:text-sm`). Changes how it looks on small screens.
100
+ - Keep `font-size: 16px` and render the intended size with `transform: scale()`. Identical at every viewport, more code to maintain.
101
+
102
+ Both recipes, plus placeholder and caret styling, are in [details-and-accessibility.md](details-and-accessibility.md#forms-and-editable-text).
103
+
104
+ ## Size floors
105
+
106
+ Start long-form body text at `16px`, the browser default. Move off it only for a reason you can name, such as a typeface that runs small, a narrow measure or a dense professional tool.
107
+
108
+ UI text can go smaller. Start inputs and menus at `14px` and captions at `13px`, and rarely go below `12px`. Set `font-size` in [`rem`](spacing-and-sizing.md#units) so the reader's browser font size still applies.
109
+
110
+ ## Font smoothing on the root
111
+
112
+ Apply `-webkit-font-smoothing: antialiased` and `-moz-osx-font-smoothing: grayscale` once on the root, never per component. Tailwind's `antialiased` sets both.
113
+
114
+ ## Language and bidi behavior
115
+
116
+ Set `lang` so browsers and assistive technology pick the right quotes, hyphenation and pronunciation. Set `dir` on the document and wherever direction changes. Wrap a mixed-direction value in `<bdi>` and never reorder digits by hand. How long paragraphs and numbers behave is in [wrapping-and-punctuation.md](wrapping-and-punctuation.md#internationalization).
117
+
118
+ ## Trim text boxes in tight containers
119
+
120
+ In buttons and badges, the space a font reserves above and below its letters makes text sit low. `text-box: trim-both cap alphabetic` trims it as a progressive enhancement. See the [edges, keywords and support](spacing-and-sizing.md#text-trimming-with-text-box).
121
+
122
+ ## Decorative text in CSS, not images
123
+
124
+ Build drop caps, gradient text, outlines and text shadows in CSS so the text stays selectable and searchable. The [properties and their support](details-and-accessibility.md#decorative-text) are in the reference.
125
+
126
+ ## Keep useful text selectable
127
+
128
+ Keep text selectable by default. `::selection` can carry brand into the reading experience, as long as the selected combination stays legible. [Styling other ranges](details-and-accessibility.md#selection) such as search matches is in the reference.
129
+
130
+ `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.
131
+
132
+ ## Before you finish
133
+
134
+ | Pattern | Fix |
135
+ | --- | --- |
136
+ | `font-weight` value with no matching weight in the loaded `@font-face` files | Load that face or use a weight the family ships |
137
+ | `font-synthesis: none` on body or interface text | Remove it; set only the unwanted longhand after checking the fallback stack |
138
+ | `font-variation-settings: "wght"` or `"opsz"` | `font-weight`; drop `"opsz"` so `font-optical-sizing: auto` applies |
139
+ | `font-feature-settings: "tnum"` or `"zero"` | `font-variant-numeric: tabular-nums` or `slashed-zero` |
140
+ | `line-height` in `px` or `rem` | A unitless value |
141
+ | `leading-none` or `leading-tight` on a description, card body or anything that can wrap | `1.4` or more |
142
+ | `letter-spacing` in `px` | The same value in `em` |
143
+ | Body `font-size` in `px` | `rem` |
144
+ | Weight `100`–`300` or `font-thin`, `font-light` on text under `18px` | `400` or heavier |
145
+ | `text-wrap: balance` on a paragraph | `text-wrap: pretty` |
146
+ | `text-align: justify` in application UI | `text-align: start` |
147
+ | `-webkit-line-clamp` without `display: -webkit-box` | Add `display: -webkit-box` and `-webkit-box-orient: vertical` |
148
+ | A timer, counter, price or numeric column without `tabular-nums` | `font-variant-numeric: tabular-nums` |
149
+ | `...`, `--` or a hyphenated range in rendered strings | `…`, an em dash or an en dash |
150
+ | `text-decoration-skip-ink: none` | Remove it; set `text-underline-offset` or `from-font` instead |
151
+ | `<input>` with `text-sm` or `14px` and no mobile override | One of the **Inputs at 16px on mobile** fixes |
152
+ | `-webkit-font-smoothing` or `antialiased` inside a component | Move it to the root |
153
+ | Mixed-direction value such as a name, number or URL without `<bdi>` or `dir` | Wrap it in `<bdi>` |
154
+ | `user-select: none` or `select-none` on a layout root or text container | Remove it; keep it only on a drag or gesture surface |
155
+
156
+ ## Reporting
157
+
158
+ **Severity.** `HIGH` makes text unreadable, truncates content with no way back to it or clips text at 320px width or 200% zoom. The last two are `interface` escalation triggers, so they stay `HIGH` however minor the surface. `MEDIUM` breaks the type system or the visual heading hierarchy. `LOW` is isolated polish.
159
+
160
+ **Verification.** Without a browser, check declared size and weight per heading level, descending within each semantic section. Check declared line-height, units and measure. Check truncation rules against realistic string lengths. With one, compare computed values, then resize the viewport with real content to catch wrapping, lone last-line words and truncation. Report every check you could not run as `Not verified`.
161
+
162
+ **Format.** Group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in:
163
+
164
+ | Severity | Location | Before | After | Why |
165
+ | --- | --- | --- | --- | --- |
166
+
167
+ `Location` is `path/to/file:line`. `Why` names the principle and the user impact.
168
+
169
+ 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,53 @@
1
+ # Choosing fonts
2
+
3
+ Font categories, fallback stacks for a type change, file formats and why fonts look the way they do.
4
+
5
+ ## Choosing a typeface
6
+
7
+ | Category | Traits | Use for |
8
+ | --- | --- | --- |
9
+ | Serif | Small strokes at the ends of letters guide the eye along a line | Long passages, editorial reading |
10
+ | Sans-serif | Clean, even shapes that stay crisp at small sizes | Default for most interfaces (Helvetica, Inter, Geist) |
11
+ | Monospace | Every glyph the same width | Code and fixed-width identifiers; numeric columns use `tabular-nums` instead |
12
+ | Display | Drawn for large headlines | Marketing headlines, hero text |
13
+ | Script | Mimics handwriting | Rare, decorative moments |
14
+
15
+ "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.
16
+
17
+ ## Fallback stacks for a type change
18
+
19
+ When a type change is asked for, two routes. `system-ui` gives each operating system's own interface face. A commercial face such as Helvetica Now is a brand decision and still needs a fallback stack.
20
+
21
+ ```css
22
+ /* The platform's native interface face */
23
+ html {
24
+ font-family: system-ui, sans-serif;
25
+ }
26
+
27
+ /* Commercial brand face with safe fallbacks */
28
+ html {
29
+ font-family: "Helvetica Now", "Helvetica Neue", Arial, sans-serif;
30
+ }
31
+ ```
32
+
33
+ ## Formats
34
+
35
+ | Format | Notes |
36
+ | --- | --- |
37
+ | `.woff2` | Brotli compression, broadly supported. Use this on the web. |
38
+ | `.woff` | Older compression. Fallback only for very old browsers. |
39
+ | `.ttf` / `.otf` | Desktop formats with no built-in compression, so larger files. Only when there is no other option. |
40
+
41
+ How the files load is the project's concern.
42
+
43
+ ## Anatomy of a typeface
44
+
45
+ | Term | Meaning |
46
+ | --- | --- |
47
+ | x-height | Height of a lowercase `x` |
48
+ | Cap height | Height of uppercase letters |
49
+ | Baseline | The invisible line letters sit on |
50
+ | Ascender | Part of a letter rising above the x-height |
51
+ | Descender | Part dropping below the baseline |
52
+
53
+ These measurements are why two fonts at the same `font-size` look like different sizes. A large x-height looks bigger.
@@ -0,0 +1,67 @@
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: auto` | Optical size follows font size; the default, so leave it | none needed |
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-prose` |
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` | Keep a lone word off the last line | `text-pretty` |
42
+ | `text-overflow: ellipsis` | Ellipsis for clipped text | `text-ellipsis` |
43
+ | `overflow: hidden` + `text-overflow: ellipsis` + `white-space: nowrap` | Single-line truncation | `truncate` |
44
+ | `-webkit-line-clamp` + `display: -webkit-box` + `-webkit-box-orient: vertical` + `overflow: hidden` | Cut off after N lines | `line-clamp-*` |
45
+ | `overflow-wrap: break-word` | Break long strings | `break-words` |
46
+ | `white-space: nowrap` | Stop wrapping | `whitespace-nowrap` |
47
+ | `text-transform` | Change the casing | `uppercase` / `capitalize` |
48
+ | `hyphens: auto` | Hyphenate by the element's `lang` | `hyphens-auto` |
49
+
50
+ ## Decoration and interaction
51
+
52
+ | Declaration | What it does | Tailwind |
53
+ | --- | --- | --- |
54
+ | `text-decoration-line: underline` | Draw an underline | `underline` |
55
+ | `text-decoration-color` | Underline color | `decoration-*` |
56
+ | `text-decoration-thickness` | Underline thickness | `decoration-1` / `decoration-2` |
57
+ | `text-underline-offset` | Push the line down | `underline-offset-*` |
58
+ | `text-underline-position: from-font` | Underline position from the font | `[text-underline-position:from-font]` |
59
+ | `text-decoration-style` | Dotted, dashed or wavy | `decoration-dotted` / `decoration-wavy` |
60
+ | `text-decoration-thickness: from-font` | Underline set by the font | `decoration-from-font` |
61
+ | `text-decoration-skip-ink` | Gaps around descenders; `auto` is the default | `[text-decoration-skip-ink:auto]` |
62
+ | `caret-color` | Tint the text cursor | `caret-*` |
63
+ | `user-select: none` | Suppress selection on a verified drag/gesture conflict only | `select-none` |
64
+ | `text-shadow` | Shadow behind the letters | `text-shadow-*` |
65
+ | `-webkit-text-stroke` | Outline the letters | `[-webkit-text-stroke:1px_black]` |
66
+ | `background-clip: text` | Clip a background to the letters | `bg-clip-text` |
67
+ | `initial-letter` | Size a drop cap | `[initial-letter:3]` |
@@ -0,0 +1,84 @@
1
+ # Details and accessibility
2
+
3
+ Underlines, selection, form text and decorative text.
4
+
5
+ ## Underlines
6
+
7
+ 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-color: var(--color-gray-1000);
31
+ transition: text-decoration-color 200ms ease-out;
32
+ }
33
+
34
+ a:hover {
35
+ text-decoration-color: var(--color-gray-1200);
36
+ }
37
+ ```
38
+
39
+ ## Selection
40
+
41
+ - `::target-text` styles the phrase a shared link scrolls to.
42
+ - The Custom Highlight API styles ranges you pick yourself, like search matches, without extra markup.
43
+
44
+ ## Forms and editable text
45
+
46
+ - `::placeholder` styles the hint in an empty field.
47
+ - `caret-color` colors the blinking insertion bar. A fully custom caret is hard to build and rarely worth it.
48
+
49
+ ### iOS input zoom
50
+
51
+ **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.
52
+
53
+ ```tsx
54
+ <input className="text-base sm:text-sm" type="email" />
55
+ ```
56
+
57
+ **Scale the text down.** Keep `font-size` at `16px` so Safari never zooms, then render at the intended size with a transform. Widen the element by the inverse of the scale so it still fills its container once shrunk. 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.
58
+
59
+ ```tsx
60
+ // 13px rendered from a 16px font-size: 13 / 16 = 0.8125
61
+ <div className="flex h-10 items-center overflow-hidden rounded-[10px] bg-gray-300 px-2.5 focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-blue-500">
62
+ <input
63
+ 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]"
64
+ type="email"
65
+ />
66
+ </div>
67
+ ```
68
+
69
+ `shrink-0` preserves the compensated width inside the flex wrapper. The wrapper clips the oversized layout box and draws an unscaled focus outline. Use the project's focus color and verify its contrast against the surrounding surface.
70
+
71
+ 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.
72
+
73
+ ## Decorative text
74
+
75
+ | Property | Effect |
76
+ | --- | --- |
77
+ | `::first-letter` | Drop cap, widely supported |
78
+ | `::first-line` | Styles only the first line |
79
+ | `initial-letter` | Sizes the drop cap; Safari needs `-webkit-initial-letter`, no Firefox yet |
80
+ | `background-clip: text` | Clips a background or gradient to the letter shapes |
81
+ | `-webkit-text-stroke` | Outlines the letters; works across modern browsers despite the prefix |
82
+ | `text-shadow` | Like `box-shadow` but follows the character shapes |
83
+
84
+ If `-webkit-text-stroke` draws lines inside the letters, the font has overlapping contours. Variable fonts often keep them unmerged, so use the static cut for stroked text.
@@ -0,0 +1,87 @@
1
+ # Spacing and sizing
2
+
3
+ Units, a starting type scale, the heading mapping, line-height for wrapping text and text trimming.
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
+ Pick an existing scale or define one. Tailwind's `text-xs` through `text-9xl`, each pairing a size with a line height, is a solid ready-made choice.
17
+
18
+ A starting point for a product interface, named by role:
19
+
20
+ ```css
21
+ :root {
22
+ --text-caption: 0.8125rem;
23
+ --text-body: 1rem;
24
+ --text-heading: 1.125rem;
25
+ --text-title: 1.5rem;
26
+ --text-display: 2.25rem;
27
+ }
28
+ ```
29
+
30
+ | Role | Size | Line-height | Weight |
31
+ | --- | --- | --- | --- |
32
+ | Display | `2.25rem` (36px) | `1.1` | `600` |
33
+ | Title | `1.5rem` (24px) | `1.2` | `600` |
34
+ | Heading | `1.125rem` (18px) | `1.3` | `600` |
35
+ | Body | `1rem` (16px) | `1.5` | `400` |
36
+ | Caption | `0.8125rem` (13px) | `1.4` | `400` |
37
+
38
+ Emphasis within a role is one weight step up (`400` → `500`), not a size change.
39
+
40
+ ## Heading hierarchy
41
+
42
+ ```css
43
+ h1 { font-size: var(--text-display); }
44
+ h2 { font-size: var(--text-title); }
45
+ h3 { font-size: var(--text-heading); }
46
+ ```
47
+
48
+ In Tailwind, centralize the per-level classes in a component or `@layer base` rather than repeating them inline.
49
+
50
+ ## Line-height for wrapping text
51
+
52
+ ```css
53
+ /* Bad: card description at heading leading */
54
+ .card-description { line-height: 1.1; }
55
+
56
+ /* Good: it wraps to 3 lines, so it reads as body text */
57
+ .card-description { line-height: 1.4; }
58
+ ```
59
+
60
+ ## Text trimming with text-box
61
+
62
+ `text-box` takes which edges to trim (`trim-both`, `trim-start`, `trim-end`) and where:
63
+
64
+ | Keyword | Trims at |
65
+ | --- | --- |
66
+ | `cap` | The cap height (top) |
67
+ | `alphabetic` | The baseline (bottom) |
68
+ | `text` | The font's own text edge, keeping room for descenders |
69
+
70
+ ```css
71
+ /* trim top and bottom */
72
+ .badge {
73
+ text-box: trim-both cap alphabetic;
74
+ }
75
+
76
+ /* trim only the top */
77
+ .heading {
78
+ text-box: trim-start cap;
79
+ }
80
+
81
+ /* trim only the bottom */
82
+ .label {
83
+ text-box: trim-end alphabetic;
84
+ }
85
+ ```
86
+
87
+ Supported in Chromium 133+ and Safari 18.2+, not yet Firefox. Unsupported browsers keep the default leading.
@@ -0,0 +1,100 @@
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 and bold is three files.
8
+ - **Variable font:** a whole range in one file. Any value in it works, such as `font-weight: 589`.
9
+
10
+ A variable font is not automatically better. At one or two weights, static files can be smaller. At several weights, optical sizes or custom axes, a variable font usually wins.
11
+
12
+ ## Disable synthesis narrowly
13
+
14
+ `font-synthesis: none` disables weight, style, small-cap and position synthesis together. Scope it to an isolated treatment you have verified across the fallback stack:
15
+
16
+ ```css
17
+ .brand-wordmark {
18
+ font-synthesis: none;
19
+ }
20
+ ```
21
+
22
+ To disable one mode, use its longhand: `font-synthesis-weight`, `font-synthesis-style`, `font-synthesis-small-caps` or `font-synthesis-position`.
23
+
24
+ ## Axes
25
+
26
+ Variable-font controls, each with a four-letter tag. A font supports only the axes its designer included.
27
+
28
+ | Axis | Tag | Controls |
29
+ | --- | --- | --- |
30
+ | Weight | `wght` | Stroke thickness (like `font-weight`) |
31
+ | Optical size | `opsz` | Details and spacing tuned for the display size |
32
+ | Width | `wdth` | Glyph width |
33
+ | Slant | `slnt` | Slant angle |
34
+ | Custom | e.g. `GRAD` (Roboto Flex) | Whatever the designer built |
35
+
36
+ Inter's variable file exposes only `wght` and `opsz`.
37
+
38
+ Optical sizes predate variable fonts. Many families still ship them as separate `Text` and `Display` files instead of an `opsz` axis.
39
+
40
+ ## Raw tags only for custom axes
41
+
42
+ `font-variation-settings` silently does nothing on a non-variable fallback, where `font-weight` still picks the nearest face:
43
+
44
+ ```css
45
+ /* Good: common axes use the properties */
46
+ .heading {
47
+ font-weight: 650;
48
+ }
49
+
50
+ /* Good: custom axis with no property of its own */
51
+ .heading-grade {
52
+ font-variation-settings: "GRAD" 80;
53
+ }
54
+
55
+ /* Bad: weight via raw tag breaks on fallback fonts */
56
+ .heading {
57
+ font-variation-settings: "wght" 650;
58
+ }
59
+ ```
60
+
61
+ ## OpenType features
62
+
63
+ Unlike axes, features work the same on static and variable fonts. A font ships only the features its designer included.
64
+
65
+ | Tag | Feature |
66
+ | --- | --- |
67
+ | `tnum` | Tabular numbers: every digit the same width |
68
+ | `zero` | Slashed zero: `0` distinct from `O` |
69
+ | `liga` | Ligatures: joins pairs like "fi" into one shape |
70
+ | `ss01`–`ss20` | Stylistic sets (numbered slots) |
71
+ | `cv01`–`cv99` | Character variants (numbered slots) |
72
+
73
+ Common features have a `font-variant-*` property, and `font-feature-settings` is for the rest:
74
+
75
+ ```css
76
+ /* Good: common features use the properties */
77
+ .price {
78
+ font-variant-numeric: tabular-nums;
79
+ }
80
+
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
+ ## Small caps, superscripts, subscripts
92
+
93
+ - **Small capitals:** uppercase letters drawn at a smaller size. Enable real ones with `font-variant-caps`.
94
+ - **Superscripts** sit above the normal line, the 2 in x², and **subscripts** below it, as in H₂O. Enable proper glyphs with `font-variant-position`.
95
+
96
+ Both require the font to include the glyphs.
97
+
98
+ ## Stylistic sets and character variants
99
+
100
+ `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,23 @@
1
+ # Wrapping and punctuation
2
+
3
+ Line length, typographic characters and mixed-direction text.
4
+
5
+ ## Measure (line length)
6
+
7
+ Any unit works. `65ch` measures characters directly, one `ch` being the width of the `0` in the current font, and 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. Tailwind's `max-w-xl` (`576px`), `max-w-2xl` (`672px`) and `max-w-prose` (`65ch`) all fit. Recheck the cap if the body font size changes.
8
+
9
+ ## Smart punctuation
10
+
11
+ | Instead of | Use |
12
+ | --- | --- |
13
+ | Straight quotes `"..."` | Curly quotes that curve around the text (keep straight quotes in code) |
14
+ | Hyphen in ranges | En dash: `2010–2020` |
15
+ | Two hyphens for an aside | Em dash character |
16
+ | Three periods `...` | The single ellipsis character `…` |
17
+ | Regular space in `10 km` | `&nbsp;` so the value never breaks apart |
18
+ | Uncontrolled word breaks | `&shy;` to mark where a word may break, or `hyphens: auto` with the correct `lang` |
19
+
20
+ ## Internationalization
21
+
22
+ - **Long paragraphs align by their own language.** A one- or two-line snippet follows the surrounding UI's direction. A paragraph of three or more lines aligns to its own script instead, so an English paragraph stays start-aligned LTR even inside an RTL interface. `text-align: start` with the correct `lang` and `dir` on the paragraph element handles this.
23
+ - **Digits keep their order.** A phone number or "541" reads identically in RTL, because the Unicode bidi algorithm keeps digit runs left to right. `<bdi>` repairs a value whose order adjacent RTL text disturbs.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jakub Krehel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,37 @@
1
+ # Notice
2
+
3
+ This skill is a fork of `better-ui/` from the repository `jakubkrehel/skills`, at commit `4c66b1fa86e9ab70187b43d7da9b6fdb29f2ef63`, fetched 2026-10-16.
4
+
5
+ Local edits:
6
+
7
+ - Renamed by `scripts/fork-interface-skills.sh` at dotfiles commit `5fbdc4b` (`better-ui` to `ui`).
8
+ - Dropped `agents/openai.yaml`.
9
+ - Added the precedence block after the introduction of `SKILL.md`.
10
+ - Reconciled the clause in `ui/SKILL.md` that limited project tokens, so a project token always wins.
11
+ - Extended the `description:` with animation triggers (menu or dialog enter and exit, staggered items, hover and press feedback, icon swaps), so `ui` wins those prompts over `motion-design`: 18/18 should, 0/12 near-miss on 2026-10-17, against 12/18 before.
12
+
13
+ Updating: diff upstream from the pinned commit, have a frontier model port the changes over the local edits, then bump the pin.
14
+
15
+ The upstream license (MIT) applies; the text is in `LICENSE`:
16
+
17
+ > MIT License
18
+ >
19
+ > Copyright (c) 2026 Jakub Krehel
20
+ >
21
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
22
+ > of this software and associated documentation files (the "Software"), to deal
23
+ > in the Software without restriction, including without limitation the rights
24
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25
+ > copies of the Software, and to permit persons to whom the Software is
26
+ > furnished to do so, subject to the following conditions:
27
+ >
28
+ > The above copyright notice and this permission notice shall be included in all
29
+ > copies or substantial portions of the Software.
30
+ >
31
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37
+ > SOFTWARE.