@mohammadhprp/system-prompt 0.11.2 → 0.12.1

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 (92) hide show
  1. package/framework/agents/researcher.md +1 -1
  2. package/framework/mcps/github-mcp/README.md +1 -1
  3. package/framework/mcps/github-mcp/configs/.env.example +1 -1
  4. package/framework/plugins/ponytail/README.md +1 -1
  5. package/framework/plugins/ponytail/capabilities.md +1 -1
  6. package/framework/references/standards/pull-requests.md +1 -1
  7. package/framework/skills/README.md +3 -3
  8. package/framework/skills/diagram-design/SKILL.md +1 -1
  9. package/framework/skills/effective-html/SKILL.md +63 -0
  10. package/framework/skills/effective-html/examples.md +19 -0
  11. package/framework/skills/effective-html/references/charts-and-data.md +32 -0
  12. package/framework/skills/effective-html/references/creative-direction.md +48 -0
  13. package/framework/skills/effective-html/references/design-artifact.md +78 -0
  14. package/framework/skills/effective-html/references/diagrams.md +68 -0
  15. package/framework/skills/effective-html/references/documents-and-presentations.md +28 -0
  16. package/framework/skills/effective-html/references/html-diagram.md +43 -0
  17. package/framework/skills/effective-html/references/html-plan.md +40 -0
  18. package/framework/skills/effective-html/references/html-prototype.md +97 -0
  19. package/framework/skills/effective-html/references/html-wireframe.md +81 -0
  20. package/framework/skills/effective-html/references/html.md +72 -0
  21. package/framework/skills/effective-html/references/interfaces.md +17 -0
  22. package/framework/skills/great-interface/SKILL.md +43 -0
  23. package/framework/skills/great-interface/references/animations.md +205 -0
  24. package/framework/skills/great-interface/references/better-accessibility.md +106 -0
  25. package/framework/skills/great-interface/references/better-colors.md +100 -0
  26. package/framework/skills/great-interface/references/better-interface.md +132 -0
  27. package/framework/skills/great-interface/references/better-layout.md +76 -0
  28. package/framework/skills/great-interface/references/better-typography.md +157 -0
  29. package/framework/skills/great-interface/references/better-ui.md +107 -0
  30. package/framework/skills/great-interface/references/better-writing.md +109 -0
  31. package/framework/skills/great-interface/references/choosing-fonts.md +64 -0
  32. package/framework/skills/great-interface/references/color-formats.md +90 -0
  33. package/framework/skills/great-interface/references/color-usage.md +118 -0
  34. package/framework/skills/great-interface/references/contrast.md +79 -0
  35. package/framework/skills/great-interface/references/css-cheat-sheet.md +65 -0
  36. package/framework/skills/great-interface/references/details-and-accessibility.md +119 -0
  37. package/framework/skills/great-interface/references/enter-exit.md +147 -0
  38. package/framework/skills/great-interface/references/explain-interface.md +126 -0
  39. package/framework/skills/great-interface/references/find-the-effect.md +94 -0
  40. package/framework/skills/great-interface/references/focus-and-keyboard.md +131 -0
  41. package/framework/skills/great-interface/references/forms.md +84 -0
  42. package/framework/skills/great-interface/references/from-an-image.md +55 -0
  43. package/framework/skills/great-interface/references/grouping-and-alignment.md +123 -0
  44. package/framework/skills/great-interface/references/hit-areas.md +94 -0
  45. package/framework/skills/great-interface/references/icon-transitions.md +102 -0
  46. package/framework/skills/great-interface/references/icons.md +110 -0
  47. package/framework/skills/great-interface/references/interface-review.md +148 -0
  48. package/framework/skills/great-interface/references/motion-and-zoom.md +79 -0
  49. package/framework/skills/great-interface/references/no-browser.md +73 -0
  50. package/framework/skills/great-interface/references/palette-generation.md +104 -0
  51. package/framework/skills/great-interface/references/palette-structure.md +76 -0
  52. package/framework/skills/great-interface/references/performance.md +88 -0
  53. package/framework/skills/great-interface/references/picker.md +76 -0
  54. package/framework/skills/great-interface/references/read-the-system.md +178 -0
  55. package/framework/skills/great-interface/references/removed-signals.md +38 -0
  56. package/framework/skills/great-interface/references/review-format.md +46 -0
  57. package/framework/skills/great-interface/references/scope-resolution.md +88 -0
  58. package/framework/skills/great-interface/references/screen-readers.md +101 -0
  59. package/framework/skills/great-interface/references/semantics-and-aria.md +84 -0
  60. package/framework/skills/great-interface/references/spacing-and-adaptivity.md +159 -0
  61. package/framework/skills/great-interface/references/spacing-and-sizing.md +121 -0
  62. package/framework/skills/great-interface/references/surfaces.md +219 -0
  63. package/framework/skills/great-interface/references/token-naming.md +97 -0
  64. package/framework/skills/great-interface/references/variable-fonts-and-opentype.md +105 -0
  65. package/framework/skills/great-interface/references/variant.md +104 -0
  66. package/framework/skills/great-interface/references/wrapping-and-punctuation.md +55 -0
  67. package/framework/skills/taste/SKILL.md +47 -0
  68. package/framework/skills/taste/examples.md +73 -0
  69. package/framework/skills/taste/references/brandkit.md +793 -0
  70. package/framework/skills/taste/references/design-taste-frontend-v1.md +221 -0
  71. package/framework/skills/taste/references/design-taste-frontend.md +1201 -0
  72. package/framework/skills/taste/references/full-output-enforcement.md +44 -0
  73. package/framework/skills/taste/references/gpt-taste.md +69 -0
  74. package/framework/skills/taste/references/high-end-visual-design.md +93 -0
  75. package/framework/skills/taste/references/image-to-code.md +1223 -0
  76. package/framework/skills/taste/references/imagegen-frontend-mobile.md +1460 -0
  77. package/framework/skills/taste/references/imagegen-frontend-web.md +982 -0
  78. package/framework/skills/taste/references/industrial-brutalist-ui.md +87 -0
  79. package/framework/skills/taste/references/minimalist-ui.md +80 -0
  80. package/framework/skills/taste/references/redesign-existing-projects.md +173 -0
  81. package/framework/skills/taste/references/stitch-design-taste-design.md +121 -0
  82. package/framework/skills/taste/references/stitch-design-taste.md +179 -0
  83. package/package.json +1 -1
  84. package/src/agent-configs.js +6 -2
  85. package/src/catalog.js +3 -2
  86. package/src/cli.js +82 -2
  87. package/src/doctor.js +84 -0
  88. package/src/installer.js +337 -154
  89. package/framework/skills/brand-guidelines/LICENSE.txt +0 -202
  90. package/framework/skills/brand-guidelines/SKILL.md +0 -73
  91. package/framework/skills/lavish/SKILL.md +0 -67
  92. package/framework/skills/lavish/examples.md +0 -31
@@ -0,0 +1,46 @@
1
+ # Review output format
2
+
3
+ This is the format for a review `better-interface` orchestrates. A domain skill reporting on its own carries its own smaller format, in its `## Reporting` section.
4
+
5
+ ## Scope and coverage
6
+
7
+ State the exact scope, stack and styling conventions, the project convention documents found in recon and any review boundary. Then show coverage:
8
+
9
+ | Domain | Evidence inspected | Result |
10
+ | --- | --- | --- |
11
+ | Accessibility | Files, components, states, or checks | Findings count or `Clear` |
12
+
13
+ Include every domain listed under `better-interface`'s **Use domain skills as the sources of truth**. `Clear` means inspected with no actionable finding; `Not reviewed` must explain why.
14
+
15
+ ## Findings
16
+
17
+ One table, ordered by severity, then by reach:
18
+
19
+ | Severity | Domain | Location | Before | After | Why |
20
+ | --- | --- | --- | --- | --- | --- |
21
+ | HIGH | Accessibility | `src/Dialog.tsx:42` | `<button><XIcon /></button>` | Add `aria-label="Close"` and hide the icon from the accessibility tree | The icon-only control has no accessible name |
22
+
23
+ - **Severity** comes from `better-interface`'s **Rank by user impact**.
24
+ - **Location** cites `path/to/file:line`. Cite the exact screen and component when the artifact has no source files.
25
+ - **Before / After** show the current implementation and an actionable replacement. Never split them into separate "Before:" and "After:" lines.
26
+ - **Why** names the violated principle and its user impact.
27
+ - **Domain** is the owning skill without the `better-` prefix.
28
+
29
+ Each row is one root cause. Consolidate a repeated systemic issue into one row and list every affected location. Respect the finding cap. With no findings, omit the table and state "No actionable interface findings."
30
+
31
+ ## Verification
32
+
33
+ List each check or interaction, the exact command or steps and the observed result. Separate checks that passed from checks marked **Not verified**.
34
+
35
+ ## Verdict
36
+
37
+ End with one of two:
38
+
39
+ - `Block`: one or more `HIGH` findings remain. Do not ship until they are fixed.
40
+ - `Approve`: no `HIGH` findings remain. Any `MEDIUM` and `LOW` findings stay in the table as work to do.
41
+
42
+ `Approve` claims the coverage you reported, so never issue it for a domain you did not inspect.
43
+
44
+ ## Change-scoped reviews
45
+
46
+ When `interface-review` resolved the scope from version control, it supplies the scope block, a status on every finding and the change-scoped format, which its `## Review output format` holds. Severity, ranking, the cap and the verdict are the ones above, and all four cover `Introduced` and `Regression` only.
@@ -0,0 +1,88 @@
1
+ # Scope resolution
2
+
3
+ Turning a review target into a file list. The commands themselves are ordinary git. What follows is the part that is not obvious, plus the traps that fail quietly and leave the scope block claiming a count it never delivered.
4
+
5
+ ## Default branch
6
+
7
+ Try `refs/remotes/origin/HEAD`, then `gh repo view --json defaultBranchRef`, then `init.defaultBranch`. If the ref is missing, ask the remote with `git remote set-head origin --auto` rather than guessing. It needs the network and writes a ref under `.git`, leaving the working tree untouched, so it is permitted; note it in Verification. With no remote at all, fall back to a local `main` or `master` and state which base you assumed.
8
+
9
+ ## Targets
10
+
11
+ Accepted targets are `working`, `staged`, `branch`, `pr <n>`, a bare `<ref>` and an explicit `<a>..<b>` or `<a>...<b>` range. Anything else in the invocation is a `<ref>`.
12
+
13
+ Diff a branch against the **merge base**, three dots. Two dots reports every upstream commit that landed on the base branch as part of the change.
14
+
15
+ But use the dots the user wrote when they wrote a range. `<a>..<b>` compares the endpoints; `<a>...<b>` compares `merge-base(<a>, <b>)` with `<b>`. Rewriting `release..feature` to three dots drops everything between `release` and the merge base, which is often exactly what was asked for. State the resolved range in the scope block.
16
+
17
+ `git diff HEAD` reports tracked changes only. Any target including uncommitted work must pair it with `git ls-files --others --exclude-standard`. Otherwise a newly added component is silently dropped from a scope the report claims to cover in full. For `branch` with uncommitted work, report the two counts separately.
18
+
19
+ ## Pull requests
20
+
21
+ Fetch the head into a remote-tracking ref, `git fetch origin "pull/<n>/head:refs/remotes/pr/<n>"`, and review it in place. This works for forks, which `origin/<branch>` does not.
22
+
23
+ Read files at that ref with `git show refs/remotes/pr/<n>:path/to/file`. Never open the working-tree copy; on a fork PR it is a different file.
24
+
25
+ `gh pr diff <n>` is a fine shortcut for the patch text. It gives no way to read unchanged context or expand to consumers, so fetch the ref as well.
26
+
27
+ **Citations.** `better-interface` requires `path/to/file:line`, and line numbers from a fetched ref need not match the working tree. Cite against the head ref, and declare that ref and its SHA in the scope block so the numbers resolve.
28
+
29
+ **Intent.** The `title` and `body` from `gh pr view` are the stated intent for **Hold the change to its stated intent**. Add the commit subjects when the body is empty.
30
+
31
+ ## Awkward repository states
32
+
33
+ Three worth handling. Everything else fails loudly at `merge-base`: no remote, unrelated histories, a repo with no commits, a moved submodule pointer. Say the base is unresolvable and stop. Never review a range you cannot name.
34
+
35
+ **Detached HEAD.** Use the merge base against the default branch and name the SHA, not a branch, in the scope block.
36
+
37
+ **Shallow clone**, the CI default, where `merge-base` returns nothing. Fetch `--deepen=50`, retry, then `--deepen=200`, then report the scope as unresolvable. Deepening writes to `.git` and not to the working tree, so it is permitted; note it in Verification.
38
+
39
+ **Mid-rebase or mid-merge**, the one that does not fail loudly. `git diff` succeeds and returns something that is not the change, so the review looks fine and is wrong. Detect it with `git rev-parse --git-path` against `rebase-merge`, `rebase-apply`, `MERGE_HEAD` and `CHERRY_PICK_HEAD`. Do not test `.git/` paths directly, because they are not directories inside a linked worktree. Stop and say the tree is mid-operation.
40
+
41
+ ## Nothing to review
42
+
43
+ The tree is clean and `HEAD` is not ahead of the merge base. Gather the facts before asking, so the offer is accurate rather than a guess: the current branch, whether the tree is clean, the count ahead of the base, the last commit's SHA and subject and any open pull request from `gh pr status`.
44
+
45
+ `gh pr status` succeeds when no pull request is open. It omits `currentBranch`, so an empty result is an answer, not an error. It fails outright without `gh`, without authentication and on a repository with no GitHub remote. Treat any failure as "no pull request found", say so and offer the remaining routes rather than stopping.
46
+
47
+ Report those facts, then offer the three routes in **With no change, ask rather than invent one**. State the last commit's SHA and subject inside the offer. The user recognises "a1b2c3d Merge pull request #482" as not what they wanted and cannot recognise "the last commit".
48
+
49
+ A whole-repository audit is a different review, not this one with a wider net. Hand the repository to `better-interface` directly, without a scope block, statuses, or a pre-existing section.
50
+
51
+ ## Renames
52
+
53
+ Rename detection is on by default for `--name-status`, reporting `R100 old/path new/path`. Raise the window with `--find-renames=40% --find-copies-harder` when a file was moved and edited in the same change.
54
+
55
+ Review a rename as a move, not a delete plus an add. Everything that survived the move is unchanged code, and only the genuine edits are in scope.
56
+
57
+ ## Excluded paths
58
+
59
+ Exclude these and name what you excluded in the scope block. They are machine-authored and carry no interface rules.
60
+
61
+ | Category | Patterns |
62
+ | --- | --- |
63
+ | Lockfiles | `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, `bun.lockb`, `Cargo.lock`, `composer.lock`, `Gemfile.lock`, `poetry.lock`, `uv.lock` |
64
+ | Snapshots and fixtures | `__snapshots__/`, `*.snap`, `*.approved.*`, `test-results/`, `playwright-report/` |
65
+ | Generated output | `dist/`, `build/`, `out/`, `.next/`, `.turbo/`, `.svelte-kit/`, `coverage/`, `storybook-static/`, `*.min.js`, `*.min.css`, `*.map` |
66
+ | Generated sources | `*.gen.ts`, `*.generated.*`, `*.d.ts` emitted by a build, GraphQL and Prisma client output |
67
+ | Vendored code | `vendor/`, `third_party/`, `node_modules/` |
68
+ | Binaries and media | `*.png`, `*.jpg`, `*.webp`, `*.avif`, `*.woff2`, `*.mp4`, `*.pdf` |
69
+
70
+ Two exceptions stay in scope. A **font file** added or swapped is a `better-typography` change. An **image** added to a component is a `better-ui` and `better-accessibility` change, through its `alt` text and its outline. Review the code that references them, not the bytes.
71
+
72
+ Apply the exclusions as pathspecs so the file count in the scope block is the reviewed count. Two traps under-exclude silently. `*.lock` catches `yarn.lock` and `Cargo.lock` but not `package-lock.json` or `pnpm-lock.yaml`, so cover every suffix in the table. And `**` needs `glob` magic: without it `*` never crosses `/`, so `**/dist/**` excludes `packages/a/dist/` but misses a root-level `dist/`. Run the diff with and without the pathspecs and confirm the count dropped by exactly the files you named.
73
+
74
+ ## Expanding to consumers
75
+
76
+ **A diff is not a surface** expands one hop, two for tokens and primitives. Use the project's own resolver where one exists, otherwise import paths.
77
+
78
+ `git grep` searches the working tree by default. Pass the reviewed ref after the pattern, or on a pull request you search a different revision and miss importers the change itself added. Results come back as `<rev>:path/to/file`; read them with `git show`, never the working-tree copy. Pass `-e` when the pattern starts with a dash, such as a `--color-*` token, or git parses it as an option.
79
+
80
+ For a changed token or theme value, search the token name rather than the file, since consumers reference the name and never import it.
81
+
82
+ Order the consumers by a rule you can evaluate, so the cutoff is reproducible instead of a guess:
83
+
84
+ 1. **Route and layout entry points first**, whatever the framework treats as a rendered surface: `app/**/page.*`, `app/**/layout.*`, `pages/**`, `routes/**`, `src/views/**`, `*.astro` pages. Everything else only appears inside one.
85
+ 2. **Then by importer count**, since a component pulled in by twenty files carries more of the change than one pulled in by two.
86
+ 3. **Break ties by proximity**, same package or feature directory first.
87
+
88
+ Review the first five, state how many you did not expand and say plainly if the ordering was arbitrary past a point.
@@ -0,0 +1,101 @@
1
+ # Screen readers
2
+
3
+ Visually hidden content, live regions, toasts, alt text and SVG.
4
+
5
+ ## Visually hidden content
6
+
7
+ The canonical `.sr-only` pattern hides content visually while keeping it in the accessibility tree:
8
+
9
+ ```css
10
+ .sr-only {
11
+ position: absolute;
12
+ width: 1px;
13
+ height: 1px;
14
+ padding: 0;
15
+ margin: -1px;
16
+ overflow: hidden;
17
+ clip: rect(0 0 0 0);
18
+ clip-path: inset(50%);
19
+ white-space: nowrap;
20
+ border: 0;
21
+ }
22
+ ```
23
+
24
+ Use `1px` boxes, not `0`, because some screen readers skip zero-sized elements. `white-space: nowrap` stops words being read as one run-together string. Never `display: none` or `visibility: hidden`, which remove the content from assistive tech entirely.
25
+
26
+ Tailwind ships this as `sr-only`. Skip links add a focus variant that un-hides it, `focus:not-sr-only` or an override on `:focus`.
27
+
28
+ Use it for context sighted users get visually: `<span class="sr-only">Opens in new tab</span>`, table captions, or an icon-only control's label where `aria-label` isn't an option.
29
+
30
+ ## Choosing how to announce a change
31
+
32
+ Work down this list and stop at the first match:
33
+
34
+ 1. **Focus moves there anyway**, as with an opened modal or the first invalid field. Nothing extra needed; the focus move is the announcement.
35
+ 2. **Tied to a specific control**, such as a field error or character count: `aria-describedby` on the control, announced with the field.
36
+ 3. **Non-urgent, not tied to a control**, such as a toast, "Saved", a result count, or a loading state: a polite live region, `role="status"`.
37
+ 4. **Urgent and not tied to a control**, such as a form-level failure or session expiry: `role="alert"`.
38
+
39
+ ## Live regions
40
+
41
+ Live regions announce content that changes without a page load: toasts, validation, search-result counts, loading states.
42
+
43
+ | Mechanism | Politeness | Use for |
44
+ | --- | --- | --- |
45
+ | `role="status"` (= `aria-live="polite"` + `aria-atomic="true"`) | Waits for a pause | Toasts, "Saved", result counts, loading updates |
46
+ | `role="alert"` (= `aria-live="assertive"` + `aria-atomic="true"`) | Interrupts immediately | Errors and urgent problems only |
47
+
48
+ Rules for reliable announcements:
49
+
50
+ - For repeated polite updates, keep a stable empty region in the DOM before changing its text. Inserting a new polite region with its content is announced inconsistently.
51
+ - Dynamically inserted `role="alert"` content is usually announced, but behavior varies. Use it only for urgent errors not tied to a control, and test the target browser and screen-reader combinations.
52
+ - Default to polite. Overusing `assertive` is the most common live-region mistake, because it interrupts whatever the user was reading.
53
+ - Keep messages short and self-contained. `aria-atomic="true"` re-reads the whole region on change.
54
+ - Never move focus to a toast. Announce it and leave focus where the user is working. Give toasts a generous timeout or a dismiss button, and never put the only path to an action inside an auto-dismissing one.
55
+
56
+ ```tsx
57
+ // Region rendered from the start, message injected later
58
+ <div role="status" className="sr-only">
59
+ {statusMessage}
60
+ </div>
61
+ ```
62
+
63
+ For loading states: set `aria-busy="true"` on the updating region, announce "Loading…" politely, then announce the outcome ("Loaded, 12 results").
64
+
65
+ ## aria-hidden
66
+
67
+ `aria-hidden="true"` removes an element and its whole subtree from assistive tech. Use it for decorative icons and content duplicated for visual effect. Never put it on or above a focusable element, which creates stops you can Tab to that do not exist for a screen reader. Hiding something interactive means removing it from the tab order too.
68
+
69
+ ## Alt text
70
+
71
+ Choose by purpose, not by what the image looks like:
72
+
73
+ | Purpose | Alt | Example |
74
+ | --- | --- | --- |
75
+ | Decorative, or redundant with adjacent text | `alt=""` (empty, but present) | Logo next to the company name in text |
76
+ | Informative | Describe the meaning it adds | `alt="Ticket QR code"` |
77
+ | Functional (image is the link/button) | Describe the action or destination | Search icon → `alt="Search"`, not `alt="magnifying glass"` |
78
+ | Image of text | The exact text (better: use real text) | `alt="50% off everything"` |
79
+ | Complex (chart, diagram) | Short summary in `alt`, full data as a table or text nearby | `alt="Revenue by quarter, described below"` |
80
+
81
+ A missing `alt` is worse than an empty one, because screen readers fall back to reading the file name.
82
+
83
+ ## SVG
84
+
85
+ - Decorative SVG: `aria-hidden="true"` and `focusable="false"`, the latter for legacy Edge and IE tabbing. No title needed.
86
+ - Meaningful inline SVG: `role="img"` plus `aria-label="…"`, or a `<title>` as the first child referenced by `aria-labelledby`.
87
+ - Simple cases: `<img src="icon.svg" alt="…">` is the most reliable delivery.
88
+
89
+ ```tsx
90
+ // Decorative icon inside a labeled button
91
+ <button aria-label="Close">
92
+ <svg aria-hidden="true" focusable="false">…</svg>
93
+ </button>
94
+
95
+ // Standalone meaningful icon
96
+ <svg role="img" aria-label="Verified account">…</svg>
97
+ ```
98
+
99
+ ## Video and audio
100
+
101
+ Prerecorded video needs captions; provide transcripts for audio. Never autoplay with sound, and always render controls.
@@ -0,0 +1,84 @@
1
+ # Semantics and ARIA
2
+
3
+ Native elements first, landmarks, accessible names and the ARIA rules that keep custom widgets honest.
4
+
5
+ ## The rules of ARIA
6
+
7
+ 1. If a native HTML element with the semantics and behavior you need exists, use it instead of repurposing another element with ARIA.
8
+ 2. Don't change native semantics unless you really have to.
9
+ 3. Every interactive ARIA control must be keyboard-operable; a role is a promise of the full keyboard model, states and behavior.
10
+ 4. Never put `role="presentation"` or `aria-hidden="true"` on a focusable element.
11
+ 5. All interactive elements must have an accessible name.
12
+
13
+ No ARIA is better than bad ARIA. A screen reader trusts your roles, so a wrong one is worse than none.
14
+
15
+ ## Button vs link vs div
16
+
17
+ | Element | Use for | Why |
18
+ | --- | --- | --- |
19
+ | `<a href>` | Navigation: anything that goes somewhere or changes the URL | Free Cmd/Ctrl/middle-click, right-click → copy link, Enter activation |
20
+ | `<button>` | Actions: submit, toggle, open, delete | Free focus, Enter *and* Space activation, form semantics |
21
+ | `<div onClick>` | Nothing | No role, no focus, no keyboard; screen readers see plain text |
22
+
23
+ ```tsx
24
+ // Bad: invisible to keyboard and screen readers
25
+ <div onClick={openSettings}>Settings</div>
26
+
27
+ // Good: focus, Enter/Space activation and semantics for free
28
+ <button onClick={openSettings}>Settings</button>
29
+ ```
30
+
31
+ If it looks clickable it must be clickable, and if it's clickable it must be a real interactive element. Rebuilding a link as a button, or the reverse, breaks user expectations. A "button" that navigates is a styled `<a>`.
32
+
33
+ Where a native element is truly impossible, the full polyfill is `role="button"` plus `tabindex="0"` plus Enter and Space handlers, which is why the native element is always less code.
34
+
35
+ ## Landmarks and headings
36
+
37
+ - Expose one visible primary `<main>` landmark. `<header>`, `<nav>`, `<aside>`, `<footer>` map to landmarks screen-reader users jump between.
38
+ - Multiple landmarks of the same type need distinguishing labels: `<nav aria-label="Primary">`, `<nav aria-label="Breadcrumbs">`.
39
+ - Do not report the one-`<h1>` or the no-skipped-levels convention as a standalone WCAG failure without a concrete navigation or comprehension impact. Headings are structure, not styling; style a heading level with CSS instead of picking the tag by size.
40
+ - `<title>` matches the current context, most specific first: `Billing · Settings · Acme`.
41
+
42
+ ## Accessible names
43
+
44
+ Name precedence: `aria-labelledby` > `aria-label` > native label (`<label>`, text content, `alt`) > `title` attribute.
45
+
46
+ - Prefer visible text or `aria-labelledby` over `aria-label`, which is invisible, drifts out of sync with the UI and is handled inconsistently by translation tools.
47
+ - Icon-only buttons always need a name: `<button aria-label="Close">` with the icon `aria-hidden="true"`.
48
+ - WCAG 2.5.3 Label in Name: a button showing "Send" with `aria-label="Submit message"` breaks voice control users who say "click Send".
49
+ - Accessible names must exist even when the design omits visible labels.
50
+
51
+ ```tsx
52
+ // Good: name from visible text, icon hidden
53
+ <button>
54
+ <TrashIcon aria-hidden="true" /> Delete
55
+ </button>
56
+
57
+ // Good: icon-only, explicit name
58
+ <button aria-label="Delete">
59
+ <TrashIcon aria-hidden="true" />
60
+ </button>
61
+ ```
62
+
63
+ Add `translate="no"` to brand names, code tokens and identifiers so auto-translation doesn't garble them.
64
+
65
+ ## Common ARIA mistakes
66
+
67
+ | Mistake | Why it fails |
68
+ | --- | --- |
69
+ | `aria-label` on a plain `<div>` or `<span>` | Most screen readers ignore names on non-interactive, role-less elements |
70
+ | `<button role="button">` | Redundant role; adds noise, no benefit |
71
+ | `aria-hidden="true"` on or above a focusable element | Creates elements you can Tab to but that don't exist for screen readers |
72
+ | `aria-labelledby`/`aria-describedby` pointing at a missing ID | Silently produces no name or description |
73
+ | `role="menu"` on a nav list | `menu` promises app-style arrow-key behavior; site navigation is `<nav>` with a list |
74
+
75
+ ## Disabled states
76
+
77
+ Native `disabled` supplies the platform's complete disabled behavior. It removes the control from the tab order, suppresses activation, applies `:disabled` and excludes form controls from submission. Use it when a native control is genuinely unavailable. `aria-disabled="true"` only announces the state, changing neither focusability, nor behavior, nor styling.
78
+
79
+ - Never disable submit buttons. Keep them enabled, validate on submit and focus the first error ([forms.md](forms.md)).
80
+ - A natively `disabled` control suppresses pointer events and leaves the tab order, so a tooltip on it never opens for keyboard or touch users and is unreliable for mouse users. Put the reason in visible text beside the control, or switch to `aria-disabled="true"`, which keeps it focusable and hoverable and can carry a tooltip.
81
+ - Use `aria-disabled="true"` where keeping a control discoverable in the tab order is intentional, or where a custom control cannot use native `disabled`.
82
+ - With `aria-disabled="true"`, block pointer and keyboard activation in the handler, prevent form submission where applicable, add explicit styling including forced-colors support and explain nearby why the action is unavailable.
83
+ - Never set both `disabled` and `aria-disabled` on the same element.
84
+ - Disabled controls are exempt from contrast minimums. Keep them legible anyway.
@@ -0,0 +1,159 @@
1
+ # Spacing and adaptivity
2
+
3
+ Space between controls, margins against the viewport, hints at off-screen content and layouts that survive resizing and translation.
4
+
5
+ ## Breathing room between targets
6
+
7
+ Controls placed too close get mis-tapped and read as one unit. Where the project has no density scale, start here:
8
+
9
+ | Between | Starting point |
10
+ | --- | --- |
11
+ | Adjacent bordered/filled controls (buttons, inputs) | `12px` |
12
+ | Around borderless controls (text buttons, icon buttons) | `24px` |
13
+ | Unrelated control groups | `24px`+ (2× the intra-group gap) |
14
+
15
+ Borderless controls need more clearance, because nothing marks where one target ends and the next begins. The space is the boundary. Compact professional tools may use less where hit areas stay distinct and never overlap. Preserve an established, usable density rather than expanding controls to match these values.
16
+
17
+ ```html
18
+ <!-- Good: bordered buttons at 12px, icon buttons given room -->
19
+ <div class="flex gap-3">
20
+ <button class="rounded-lg border px-4 py-2">Cancel</button>
21
+ <button class="rounded-lg bg-blue-600 px-4 py-2 text-white">Save</button>
22
+ </div>
23
+
24
+ <!-- Bad: three borderless icon buttons packed at 4px -->
25
+ <div class="flex gap-1">
26
+ <button><TrashIcon /></button>
27
+ <button><ArchiveIcon /></button>
28
+ <button><ShareIcon /></button>
29
+ </div>
30
+ ```
31
+
32
+ WCAG target-size requirements, larger usability targets and pseudo-element expansion belong to `better-accessibility`; these clearances are in addition, so expanded hit areas never overlap.
33
+
34
+ ## Inset buttons from the edges
35
+
36
+ In content layouts, buttons pressed against the viewport look like system chrome and clip against curved corners or gesture zones. Keep them inside the layout margins. Edge-to-edge actions stay valid where they are deliberately platform chrome and account for safe areas:
37
+
38
+ ```css
39
+ /* Good: inset action bar */
40
+ .action-bar {
41
+ padding-inline: 16px;
42
+ padding-bottom: calc(16px + env(safe-area-inset-bottom));
43
+ }
44
+ .action-bar button { width: 100%; border-radius: 12px; }
45
+
46
+ /* Bad: button glued to all three edges */
47
+ .action-bar button {
48
+ width: 100vw;
49
+ border-radius: 0;
50
+ position: fixed;
51
+ bottom: 0;
52
+ }
53
+ ```
54
+
55
+ Start near `16px` inline margin on mobile where the project has no layout token. The button can still span the full content width inside them.
56
+
57
+ ## Progressive disclosure needs an affordance
58
+
59
+ Hiding complexity is good; hiding it without a cue is a trap. Every piece of off-screen or collapsed content needs a visible hint that it exists. Keep the product's established scroll indicator or disclosure pattern, and use the recipes below only where no cue exists:
60
+
61
+ - **Peeking items.** In a horizontal scroller or carousel, size items so the next one peeks `16–32px` past the container edge. A row of cards that ends exactly at the edge looks complete, and nobody scrolls it.
62
+ - **Disclosure controls.** Collapsed sections get a chevron or "Show more", labelled with what is hidden: "Show 12 more results", not "More".
63
+ - **Truncation cues.** Clamped text shows an ellipsis and a way to expand. Truncation mechanics are `better-typography`'s.
64
+
65
+ In the peeking-scroller recipe, the container's padding creates the peek and snap points stay on the content edge.
66
+
67
+ ```css
68
+ .scroller {
69
+ display: flex;
70
+ gap: 12px;
71
+ overflow-x: auto;
72
+ padding-inline: 24px;
73
+ scroll-padding-inline: 24px;
74
+ scroll-snap-type: x mandatory;
75
+ }
76
+ .scroller > * {
77
+ flex: 0 0 calc(100% - 48px - 24px); /* container minus margins minus peek */
78
+ scroll-snap-align: start;
79
+ }
80
+ ```
81
+
82
+ ```html
83
+ <!-- Tailwind: the 80% width keeps the next card's leading 16-32px visible -->
84
+ <div class="flex gap-3 overflow-x-auto px-6 [scroll-padding-inline:1.5rem] snap-x snap-mandatory">
85
+ <div class="w-[80%] shrink-0 snap-start">…</div>
86
+ <div class="w-[80%] shrink-0 snap-start">…</div>
87
+ </div>
88
+ ```
89
+
90
+ ## Content bleeds, controls float
91
+
92
+ The two layers behave differently at the edges:
93
+
94
+ - **Content layer**: backgrounds, hero media and scrollable lists extend to the viewport edges.
95
+ - **Control layer**: text and controls stay inside the layout margins and safe areas, floating above the content.
96
+
97
+ ```css
98
+ /* Good: full-bleed media inside a constrained article */
99
+ .article {
100
+ display: grid;
101
+ grid-template-columns: 1fr min(65ch, calc(100% - 48px)) 1fr;
102
+ }
103
+ .article > * { grid-column: 2; }
104
+ .article > .full-bleed { grid-column: 1 / -1; }
105
+ ```
106
+
107
+ Sticky headers and floating action buttons account for safe areas:
108
+
109
+ ```css
110
+ .fab {
111
+ position: fixed;
112
+ inset-inline-end: calc(16px + env(safe-area-inset-right));
113
+ bottom: calc(16px + env(safe-area-inset-bottom));
114
+ }
115
+ ```
116
+
117
+ ## Hold structure until it breaks
118
+
119
+ Breakpoints belong to the content, not the device catalog:
120
+
121
+ - Break where the layout actually stops fitting, not at `768px` because a preset says so. That is where the sidebar squeezes content below its minimum measure, or the card grid drops below a usable column width.
122
+ - Collapse late. A layout keeping its expanded structure as long as it genuinely fits stays stable and familiar. Premature collapsing throws away space users paid for.
123
+ - Prefer **container queries** for components. A card adapts to the column it is in, not to the viewport.
124
+
125
+ ```css
126
+ /* Good: component adapts to its container */
127
+ .card-list { container-type: inline-size; }
128
+ @container (max-width: 400px) {
129
+ .card { grid-template-columns: 1fr; }
130
+ }
131
+
132
+ /* Bad: viewport media query breaks the card inside a narrow sidebar */
133
+ @media (max-width: 768px) {
134
+ .card { grid-template-columns: 1fr; }
135
+ }
136
+ ```
137
+
138
+ Test the smallest and largest supported sizes first, since those break first, then the sizes between.
139
+
140
+ ## Plan for growth and clipping
141
+
142
+ Layouts fail in two directions. Content grows, and viewports shrink.
143
+
144
+ **String expansion varies by language and by source-string length.** Never rely on one universal percentage.
145
+
146
+ - No fixed widths sized to English labels. Use `max-width` plus wrapping.
147
+ - No fixed heights on text containers. Use `min-height` where a floor is needed.
148
+ - Buttons size themselves from their label (`padding-inline`), never a hardcoded width.
149
+ - Test with pseudo-localization or a long-string locale before shipping.
150
+
151
+ ```css
152
+ /* Good: label defines the size */
153
+ .button { padding-inline: 16px; white-space: nowrap; }
154
+
155
+ /* Bad: German will overflow or truncate */
156
+ .button { width: 96px; overflow: hidden; }
157
+ ```
158
+
159
+ **Clipping.** Never park a critical action where it can be cut off: the bottom edge of a resizable pane, below the fold of a fixed-height modal, behind an expanding keyboard. Keep primary actions in stable chrome, a sticky footer with safe-area padding or the top of the view. Where a modal's content scrolls, its action row does not.
@@ -0,0 +1,121 @@
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 { font-size: var(--text-2xl); }
50
+ h2 { font-size: var(--text-xl); }
51
+ h3 { font-size: var(--text-lg); }
52
+ ```
53
+
54
+ 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.
55
+
56
+ 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.
57
+
58
+ Heading semantics and outline quality belong to `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.
59
+
60
+ ## Kerning and letter-spacing
61
+
62
+ - **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`.
63
+ - **`letter-spacing`** adds the same space between every character.
64
+
65
+ ```css
66
+ /* Good */
67
+ .display-heading {
68
+ letter-spacing: -0.02em;
69
+ }
70
+
71
+ .uppercase-label {
72
+ text-transform: uppercase;
73
+ letter-spacing: 0.05em;
74
+ }
75
+ ```
76
+
77
+ ## Line-height
78
+
79
+ | Text | Value |
80
+ | --- | --- |
81
+ | Headings | ~`1.1` |
82
+ | Body copy | `1.5`–`1.6` |
83
+
84
+ 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.
85
+
86
+ ```css
87
+ /* Bad: card description at heading leading */
88
+ .card-description { line-height: 1.1; }
89
+
90
+ /* Good: it wraps to 3 lines, so it reads as body text */
91
+ .card-description { line-height: 1.4; }
92
+ ```
93
+
94
+ ## Text trimming with text-box
95
+
96
+ 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:
97
+
98
+ | Keyword | Trims at |
99
+ | --- | --- |
100
+ | `cap` | The cap height (top) |
101
+ | `alphabetic` | The baseline (bottom) |
102
+ | `text` | The font's own text edge, keeping room for descenders |
103
+
104
+ ```css
105
+ /* trim top and bottom */
106
+ .badge {
107
+ text-box: trim-both cap alphabetic;
108
+ }
109
+
110
+ /* trim only the top */
111
+ .heading {
112
+ text-box: trim-start cap;
113
+ }
114
+
115
+ /* trim only the bottom */
116
+ .label {
117
+ text-box: trim-end alphabetic;
118
+ }
119
+ ```
120
+
121
+ Supported in Chromium (133+) and Safari (18.2+), not yet Firefox. Treat it as progressive enhancement, where unsupported browsers keep the default leading.