@mohammadhprp/system-prompt 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/framework/agents/README.md +0 -1
- package/framework/commands/mr.md +8 -9
- package/framework/mcps/README.md +0 -3
- package/framework/skills/README.md +5 -4
- package/framework/skills/effective-html/SKILL.md +63 -0
- package/framework/skills/effective-html/examples.md +19 -0
- package/framework/skills/effective-html/references/charts-and-data.md +32 -0
- package/framework/skills/effective-html/references/creative-direction.md +48 -0
- package/framework/skills/effective-html/references/design-artifact.md +78 -0
- package/framework/skills/effective-html/references/diagrams.md +68 -0
- package/framework/skills/effective-html/references/documents-and-presentations.md +28 -0
- package/framework/skills/effective-html/references/html-diagram.md +43 -0
- package/framework/skills/effective-html/references/html-plan.md +40 -0
- package/framework/skills/effective-html/references/html-prototype.md +97 -0
- package/framework/skills/effective-html/references/html-wireframe.md +81 -0
- package/framework/skills/effective-html/references/html.md +72 -0
- package/framework/skills/effective-html/references/interfaces.md +17 -0
- package/framework/skills/glab/SKILL.md +222 -0
- package/framework/skills/glab/references/commands-detailed.md +616 -0
- package/framework/skills/glab/references/quick-reference.md +145 -0
- package/framework/skills/glab/references/troubleshooting.md +669 -0
- package/framework/skills/great-interface/SKILL.md +43 -0
- package/framework/skills/great-interface/references/animations.md +205 -0
- package/framework/skills/great-interface/references/better-accessibility.md +106 -0
- package/framework/skills/great-interface/references/better-colors.md +100 -0
- package/framework/skills/great-interface/references/better-interface.md +132 -0
- package/framework/skills/great-interface/references/better-layout.md +76 -0
- package/framework/skills/great-interface/references/better-typography.md +157 -0
- package/framework/skills/great-interface/references/better-ui.md +107 -0
- package/framework/skills/great-interface/references/better-writing.md +109 -0
- package/framework/skills/great-interface/references/choosing-fonts.md +64 -0
- package/framework/skills/great-interface/references/color-formats.md +90 -0
- package/framework/skills/great-interface/references/color-usage.md +118 -0
- package/framework/skills/great-interface/references/contrast.md +79 -0
- package/framework/skills/great-interface/references/css-cheat-sheet.md +65 -0
- package/framework/skills/great-interface/references/details-and-accessibility.md +119 -0
- package/framework/skills/great-interface/references/enter-exit.md +147 -0
- package/framework/skills/great-interface/references/explain-interface.md +126 -0
- package/framework/skills/great-interface/references/find-the-effect.md +94 -0
- package/framework/skills/great-interface/references/focus-and-keyboard.md +131 -0
- package/framework/skills/great-interface/references/forms.md +84 -0
- package/framework/skills/great-interface/references/from-an-image.md +55 -0
- package/framework/skills/great-interface/references/grouping-and-alignment.md +123 -0
- package/framework/skills/great-interface/references/hit-areas.md +94 -0
- package/framework/skills/great-interface/references/icon-transitions.md +102 -0
- package/framework/skills/great-interface/references/icons.md +110 -0
- package/framework/skills/great-interface/references/interface-review.md +148 -0
- package/framework/skills/great-interface/references/motion-and-zoom.md +79 -0
- package/framework/skills/great-interface/references/no-browser.md +73 -0
- package/framework/skills/great-interface/references/palette-generation.md +104 -0
- package/framework/skills/great-interface/references/palette-structure.md +76 -0
- package/framework/skills/great-interface/references/performance.md +88 -0
- package/framework/skills/great-interface/references/picker.md +76 -0
- package/framework/skills/great-interface/references/read-the-system.md +178 -0
- package/framework/skills/great-interface/references/removed-signals.md +38 -0
- package/framework/skills/great-interface/references/review-format.md +46 -0
- package/framework/skills/great-interface/references/scope-resolution.md +88 -0
- package/framework/skills/great-interface/references/screen-readers.md +101 -0
- package/framework/skills/great-interface/references/semantics-and-aria.md +84 -0
- package/framework/skills/great-interface/references/spacing-and-adaptivity.md +159 -0
- package/framework/skills/great-interface/references/spacing-and-sizing.md +121 -0
- package/framework/skills/great-interface/references/surfaces.md +219 -0
- package/framework/skills/great-interface/references/token-naming.md +97 -0
- package/framework/skills/great-interface/references/variable-fonts-and-opentype.md +105 -0
- package/framework/skills/great-interface/references/variant.md +104 -0
- package/framework/skills/great-interface/references/wrapping-and-punctuation.md +55 -0
- package/framework/skills/improve/SKILL.md +137 -0
- package/framework/skills/improve/examples.md +19 -0
- package/framework/skills/improve/references/audit-playbook.md +130 -0
- package/framework/skills/improve/references/closing-the-loop.md +96 -0
- package/framework/skills/improve/references/plan-template.md +197 -0
- package/framework/skills/jira-cli/SKILL.md +260 -0
- package/framework/skills/jira-cli/references/commands-detailed.md +268 -0
- package/framework/skills/jira-cli/references/quick-reference.md +111 -0
- package/framework/skills/jira-cli/references/troubleshooting.md +114 -0
- package/framework/styles/README.md +9 -4
- package/framework/styles/factory/DESIGN.md +360 -0
- package/framework/styles/factory/README.md +32 -0
- package/framework/styles/factory/assets/preview.jpg +0 -0
- package/framework/styles/huly/DESIGN.md +449 -0
- package/framework/styles/huly/README.md +32 -0
- package/framework/styles/huly/assets/preview.jpg +0 -0
- package/framework/styles/notion/DESIGN.md +423 -0
- package/framework/styles/notion/README.md +32 -0
- package/framework/styles/notion/assets/preview.jpg +0 -0
- package/package.json +1 -1
- package/src/catalog.js +8 -8
- package/framework/agents/backend-architect.md +0 -146
- package/framework/mcps/github-mcp/README.md +0 -51
- package/framework/mcps/github-mcp/capabilities.md +0 -83
- package/framework/mcps/github-mcp/configs/.env.example +0 -1
- package/framework/mcps/github-mcp/configs/opencode.json +0 -13
- package/framework/mcps/github-mcp/install.md +0 -60
- package/framework/mcps/github-mcp/troubleshooting.md +0 -79
- package/framework/mcps/gitlab-mcp/README.md +0 -53
- package/framework/mcps/gitlab-mcp/capabilities.md +0 -216
- package/framework/mcps/gitlab-mcp/configs/.env.example +0 -2
- package/framework/mcps/gitlab-mcp/configs/opencode.json +0 -13
- package/framework/mcps/gitlab-mcp/install.md +0 -99
- package/framework/mcps/gitlab-mcp/troubleshooting.md +0 -116
- package/framework/mcps/jira-mcp/README.md +0 -52
- package/framework/mcps/jira-mcp/capabilities.md +0 -79
- package/framework/mcps/jira-mcp/configs/.env.example +0 -2
- package/framework/mcps/jira-mcp/configs/opencode.json +0 -13
- package/framework/mcps/jira-mcp/install.md +0 -94
- package/framework/mcps/jira-mcp/troubleshooting.md +0 -113
- package/framework/skills/brand-guidelines/LICENSE.txt +0 -202
- package/framework/skills/brand-guidelines/SKILL.md +0 -73
- package/framework/skills/gitlab-mcp/SKILL.md +0 -83
- package/framework/skills/gitlab-mcp/examples.md +0 -31
- package/framework/skills/gitlab-mcp/references/code-review.md +0 -110
- package/framework/skills/gitlab-mcp/references/issues.md +0 -141
- package/framework/skills/gitlab-mcp/references/merge-requests.md +0 -120
- package/framework/skills/gitlab-mcp/references/pipelines.md +0 -67
- package/framework/skills/gitlab-mcp/references/search.md +0 -17
- package/framework/skills/gitlab-mcp/references/webhooks.md +0 -32
- package/framework/skills/gitlab-mcp/references/work-items.md +0 -50
- package/framework/skills/jira-mcp/SKILL.md +0 -57
- package/framework/skills/jira-mcp/examples.md +0 -31
- package/framework/skills/jira-mcp/references/comments.md +0 -27
- package/framework/skills/jira-mcp/references/issues.md +0 -97
- package/framework/skills/jira-mcp/references/projects.md +0 -39
- package/framework/skills/lavish/SKILL.md +0 -67
- package/framework/skills/lavish/examples.md +0 -31
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Focus and keyboard
|
|
2
|
+
|
|
3
|
+
Focus rings, skip links, tabindex, focus trapping and the APG keyboard patterns.
|
|
4
|
+
|
|
5
|
+
## Focus rings
|
|
6
|
+
|
|
7
|
+
Style `:focus-visible`, not bare `:focus`. The browser shows it for keyboard and assistive-tech focus and suppresses it for mouse clicks, where focus is already obvious. Never write `outline: none` or `focus:outline-none` without a visible replacement, which removes keyboard navigation for sighted keyboard users.
|
|
8
|
+
|
|
9
|
+
Prefer the browser's unmodified focus indicator, which adapts to platform and forced-color settings without the author predicting every background. Adding only `outline-offset` preserves it. A custom `outline: 2px solid` with no color renders `currentColor`, which is not automatically accessible, because the outline may cross colors unlike the text's own background. The preference order:
|
|
10
|
+
|
|
11
|
+
```css
|
|
12
|
+
/* Best: keep the browser ring, just give it breathing room */
|
|
13
|
+
:focus-visible {
|
|
14
|
+
outline-offset: 2px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Custom ring when the design requires one: use the project's verified token */
|
|
18
|
+
:focus-visible {
|
|
19
|
+
outline: 2px solid var(--focus-ring);
|
|
20
|
+
outline-offset: 2px;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
// Tailwind: use the project's focus token or established focus-ring utility
|
|
26
|
+
<button className="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--focus-ring)]">
|
|
27
|
+
Save
|
|
28
|
+
</button>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A custom focus indicator must meet the applicable project or WCAG target for visible area and change of contrast. Inspect the whole perimeter against every adjacent color it crosses: component fills, page surfaces, images, gradients, hover and selected states. A token, brand color, or `currentColor` passes only when that rendered check does.
|
|
32
|
+
|
|
33
|
+
In `forced-colors: active` (Windows High Contrast), keep the default color adjustment or name a system color such as `Highlight`. `forced-color-adjust: none` freezes the authored color, so use it only where you have checked the control stays perceivable.
|
|
34
|
+
|
|
35
|
+
Group focus styles with `:focus-within` when a wrapper should light up while an inner input has focus (e.g. a search box with an icon inside the border).
|
|
36
|
+
|
|
37
|
+
## Skip link
|
|
38
|
+
|
|
39
|
+
Target `<main id="main">` and visually hide the link until focused:
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
.skip-link {
|
|
43
|
+
position: absolute;
|
|
44
|
+
inset-inline-start: -999px;
|
|
45
|
+
}
|
|
46
|
+
.skip-link:focus {
|
|
47
|
+
inset-inline-start: 16px;
|
|
48
|
+
top: 16px;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<body>
|
|
54
|
+
<a class="skip-link" href="#main">Skip to content</a>
|
|
55
|
+
<header>…</header>
|
|
56
|
+
<main id="main">…</main>
|
|
57
|
+
</body>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Give in-page anchor targets `scroll-margin-top`, such as `80px` under a sticky header, so the target isn't hidden when jumped to.
|
|
61
|
+
|
|
62
|
+
## tabindex rules
|
|
63
|
+
|
|
64
|
+
- `tabindex="0"`: adds an element to the natural tab order. Only for custom interactive elements that aren't natively focusable.
|
|
65
|
+
- `tabindex="-1"`: focusable via JavaScript only (`el.focus()`). Use for headings you move focus to, modal containers and roving-tabindex members.
|
|
66
|
+
- Positive `tabindex`: never. It hijacks the tab order for the whole page. Fix the DOM order instead.
|
|
67
|
+
|
|
68
|
+
### Roving tabindex
|
|
69
|
+
|
|
70
|
+
Composite widgets, meaning tabs, menus, toolbars and radio groups, occupy one Tab stop. The active item has `tabindex="0"`, all others `tabindex="-1"`, and arrow keys move both focus and the `0`:
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<div role="tablist">
|
|
74
|
+
{tabs.map((tab, i) => (
|
|
75
|
+
<button
|
|
76
|
+
role="tab"
|
|
77
|
+
tabIndex={i === activeIndex ? 0 : -1}
|
|
78
|
+
aria-selected={i === activeIndex}
|
|
79
|
+
onKeyDown={handleArrowKeys} // ArrowLeft/ArrowRight move activeIndex, wrapping
|
|
80
|
+
>
|
|
81
|
+
{tab.label}
|
|
82
|
+
</button>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Focus trapping and restoration
|
|
88
|
+
|
|
89
|
+
Modals must trap focus. Put `inert` on everything behind the dialog, which removes background content from the tab order and from assistive tech in one move:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
// On open
|
|
93
|
+
document.getElementById("app-content").inert = true;
|
|
94
|
+
const dialog = dialogRef.current;
|
|
95
|
+
(dialog.querySelector("[autofocus]") ??
|
|
96
|
+
dialog.querySelector("button, [href], input, select, textarea"))?.focus();
|
|
97
|
+
|
|
98
|
+
// On close
|
|
99
|
+
document.getElementById("app-content").inert = false;
|
|
100
|
+
triggerRef.current?.focus(); // always return focus to the element that opened it
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Prefer native `<dialog>` with `showModal()`, which gives you the trap, the `inert` background and Escape handling for free. A custom overlay that can't use it needs `role="dialog"`, `aria-modal="true"` and an accessible name via `aria-labelledby`. Either way:
|
|
104
|
+
|
|
105
|
+
- On open, focus the first focusable element. For destructive confirmations, focus the least destructive action instead.
|
|
106
|
+
- On close, return focus to the trigger, or to the nearest logical container if the trigger is gone.
|
|
107
|
+
- Add `overscroll-behavior: contain` on the dialog so scrolling inside never scrolls the page behind it.
|
|
108
|
+
|
|
109
|
+
## Keyboard patterns (ARIA APG)
|
|
110
|
+
|
|
111
|
+
Native elements come with these behaviors; custom widgets must implement them. A role is a promise. Give something `role="tab"` and users expect the full tab keyboard model.
|
|
112
|
+
|
|
113
|
+
| Widget | Keys |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| Dialog | Tab/Shift+Tab cycle inside (wrap at ends); Escape closes |
|
|
116
|
+
| Tabs | Arrow keys move between tabs (wrapping); Tab exits to the panel; Home/End jump to first/last |
|
|
117
|
+
| Menu button | Enter/Space/ArrowDown opens and focuses first item; ArrowUp opens and focuses last; arrows navigate; Escape closes and refocuses the button |
|
|
118
|
+
| Disclosure / accordion | Header is a `<button aria-expanded>`; Enter and Space toggle |
|
|
119
|
+
| Combobox | ArrowDown opens/moves into the list; Enter accepts; Escape closes and returns to the input; typing filters |
|
|
120
|
+
| Listbox / radio group | Arrow keys move selection; one Tab stop for the whole group |
|
|
121
|
+
|
|
122
|
+
Universal rules:
|
|
123
|
+
|
|
124
|
+
- Escape dismisses whatever opened last: tooltip, then menu, then dialog.
|
|
125
|
+
- Arrow keys, not Tab, move within a composite widget; Tab moves between widgets.
|
|
126
|
+
- Tabs choose activation mode: automatic (panel switches on arrow focus) when panels render instantly, manual (Enter/Space to activate) when switching is expensive.
|
|
127
|
+
- Enter submits the focused input's form. In `<textarea>`, Enter inserts a newline and ⌘/Ctrl+Enter submits.
|
|
128
|
+
|
|
129
|
+
## SPA route changes
|
|
130
|
+
|
|
131
|
+
Client-side navigation doesn't reset focus or announce anything. On route change, update `document.title` to match the new context, then move focus to the new view's `<h1>` (given `tabindex="-1"`) or to `<main>`. Restore scroll position on back and forward navigation, and scroll to top on forward navigation.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Forms
|
|
2
|
+
|
|
3
|
+
Labels, autocomplete, error messaging, input types and submit behavior.
|
|
4
|
+
|
|
5
|
+
## Labels
|
|
6
|
+
|
|
7
|
+
Every control needs a programmatic label: `<label for>` pointing at the input's `id`, or a wrapping `<label>`. A placeholder is never a label. It disappears the moment the user types and usually fails contrast.
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<!-- Good: explicit association -->
|
|
11
|
+
<label for="email">Email</label>
|
|
12
|
+
<input id="email" type="email" autocomplete="email" />
|
|
13
|
+
|
|
14
|
+
<!-- Good: wrapping label, so label and control share one hit target -->
|
|
15
|
+
<label>
|
|
16
|
+
<input type="checkbox" /> Send me updates
|
|
17
|
+
</label>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Label and control share one hit target, so clicking "Send me updates" toggles the checkbox with no dead zone between them. Mark required fields with native `required` plus a visible indicator explained once per form ("* required").
|
|
21
|
+
|
|
22
|
+
A placeholder used *in addition to* a label shows an example of the expected format: `placeholder="name@company.com"`.
|
|
23
|
+
|
|
24
|
+
## Error messaging
|
|
25
|
+
|
|
26
|
+
The complete pattern:
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<label for="email">Email</label>
|
|
30
|
+
<input
|
|
31
|
+
id="email"
|
|
32
|
+
type="email"
|
|
33
|
+
autocomplete="email"
|
|
34
|
+
aria-invalid="true"
|
|
35
|
+
aria-describedby="email-error"
|
|
36
|
+
/>
|
|
37
|
+
<p id="email-error">Enter a valid email address.</p>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `aria-invalid="true"` on the failing field, removed once fixed.
|
|
41
|
+
- `aria-describedby` links the field to its inline error so screen readers announce it with the field.
|
|
42
|
+
- Errors render inline beside their fields, with an icon or text. Never a red border alone, which is a color-only cue.
|
|
43
|
+
- On submit, focus the first invalid field.
|
|
44
|
+
- Allow incomplete submission so validation can surface. Never disable submit until valid (see below).
|
|
45
|
+
- Accept free text and validate after. Never block typing or filter characters as the user types. Trim values before validating, because autocomplete and text expansion add trailing spaces.
|
|
46
|
+
|
|
47
|
+
## Autocomplete and input types
|
|
48
|
+
|
|
49
|
+
`autocomplete` with a meaningful `name` fills forms in one tap and is a WCAG requirement (1.3.5) for fields about the user. The common tokens:
|
|
50
|
+
|
|
51
|
+
| Field | `autocomplete` |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| Name | `name` (or `given-name` / `family-name`) |
|
|
54
|
+
| Email | `email` |
|
|
55
|
+
| Phone | `tel` |
|
|
56
|
+
| Address | `street-address`, `address-line1`, `postal-code`, `country` |
|
|
57
|
+
| Card | `cc-number`, `cc-exp`, `cc-csc`, `cc-name` |
|
|
58
|
+
| Login | `username`, `current-password` |
|
|
59
|
+
| Signup / reset | `new-password` |
|
|
60
|
+
| 2FA code | `one-time-code` |
|
|
61
|
+
|
|
62
|
+
Prefix with a section where relevant: `autocomplete="shipping street-address"`.
|
|
63
|
+
|
|
64
|
+
Correct `type` and `inputmode` pick the right mobile keyboard:
|
|
65
|
+
|
|
66
|
+
| Input | Use |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| Email, URL, phone | `type="email"`, `type="url"`, `type="tel"` |
|
|
69
|
+
| OTP / PIN / card number | `type="text" inputmode="numeric"` (keeps text semantics, no spinner) |
|
|
70
|
+
| Money, decimals | `type="text" inputmode="decimal"` |
|
|
71
|
+
| True numeric quantity | `type="number"` |
|
|
72
|
+
|
|
73
|
+
Disable spellcheck on emails, codes and usernames: `spellcheck="false"`.
|
|
74
|
+
|
|
75
|
+
## Never fight the user's tools
|
|
76
|
+
|
|
77
|
+
- Stay compatible with password managers and 2FA autofill: real `<form>`, correct `autocomplete`, no fake inputs.
|
|
78
|
+
|
|
79
|
+
## Submit behavior
|
|
80
|
+
|
|
81
|
+
- Keep submit enabled until the request starts, then disable it and show a spinner *beside the original label*. "Save" with a spinner, not a bare spinner. The label is what tells assistive tech which button is busy.
|
|
82
|
+
- Announce results. Success goes through a polite live region. On failure, focus the first invalid field, which is itself the announcement. Reserve `role="alert"` for form-level errors not tied to a field ([screen-readers.md](screen-readers.md)).
|
|
83
|
+
- Warn on unsaved changes before navigation, and never lose typed input to a re-render. Hydration must preserve focus and value.
|
|
84
|
+
- Enter submits from any focused input; in `<textarea>`, ⌘/Ctrl+Enter submits.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Reading a screenshot
|
|
2
|
+
|
|
3
|
+
The method for a screenshot, where the answer is a reconstruction rather than a reading. Which property lands in which bucket:
|
|
4
|
+
|
|
5
|
+
| Exact from pixels | Ratios only | Unavailable |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| Color values | Type sizes | Tokens and their names |
|
|
8
|
+
| Contrast between any two sampled colors | Spacing values | The stack and styling system |
|
|
9
|
+
| Relative proportion of anything measurable | Radii | Breakpoints |
|
|
10
|
+
| Which colors repeat and where | Stroke and border weights | Motion, easing, duration |
|
|
11
|
+
| | | Every state but the captured one |
|
|
12
|
+
|
|
13
|
+
You do not know the capture's scale. A screenshot may be at 1×, 2×, or browser zoom, so a measured 30px could be 15pt of type or 30. Never report a `px` size or spacing from an image alone.
|
|
14
|
+
|
|
15
|
+
One exception. Where the image contains text you can identify as body copy, assume `16px` and express everything as a multiple of it. Say you did, since the assumption may be wrong.
|
|
16
|
+
|
|
17
|
+
## Colors, which are the reliable part
|
|
18
|
+
|
|
19
|
+
Sample the actual pixels rather than describing what you see. Then hand the values to `better-colors`:
|
|
20
|
+
|
|
21
|
+
- Convert each to OKLCH, so lightness is comparable across hues.
|
|
22
|
+
- Sort by lightness to see whether the samples form a ramp and whether steps sit denser at the light end.
|
|
23
|
+
- Check hue constancy across the ramp. A drifting hue means someone picked the ramp by eye.
|
|
24
|
+
- Measure contrast on every foreground and background pair you can isolate. It is exact and the most valuable single number an image gives you.
|
|
25
|
+
- Watch for a tinted neutral. A gray carrying a few percent of the accent hue is a deliberate choice worth naming.
|
|
26
|
+
|
|
27
|
+
## Type, by category not by name
|
|
28
|
+
|
|
29
|
+
You cannot identify a typeface from a screenshot with confidence, so never claim one. You can read its category and features, which is what transfers:
|
|
30
|
+
|
|
31
|
+
- **Category:** geometric sans, grotesque, humanist, transitional serif, slab. Say which and why.
|
|
32
|
+
- **Tells worth naming:** single or double-storey `a` and `g`, terminal angle, aperture, x-height against cap height. Also whether the digits are lining or old-style and whether the figures look tabular.
|
|
33
|
+
- **Scale:** count the distinct sizes. Express them as multiples of the body size and derive the ratio.
|
|
34
|
+
- **Weight contrast:** how many weights and how far apart. Two weights three steps apart reads deliberate, four weights one step apart accidental.
|
|
35
|
+
- **Measure:** count characters on a full line of body copy and compare against `better-typography`'s 60 to 75 range.
|
|
36
|
+
|
|
37
|
+
Where the actual face matters, name the category and suggest identifying it from the live page.
|
|
38
|
+
|
|
39
|
+
## Spacing, as a rhythm
|
|
40
|
+
|
|
41
|
+
Measure in the image, then divide everything by the smallest repeated gap. That quotient set is the rhythm, and it survives not knowing the scale.
|
|
42
|
+
|
|
43
|
+
Then check the ratio `better-layout` owns, the gap between groups against the gap within one. At or above 2, space is carrying the grouping. Below that, something else is, usually a border or a background shape.
|
|
44
|
+
|
|
45
|
+
## Say what the image hid
|
|
46
|
+
|
|
47
|
+
Close by naming what a screenshot could not show, since that is where the reader would otherwise assume you looked:
|
|
48
|
+
|
|
49
|
+
- Hover, focus, active, disabled, loading, empty and error states.
|
|
50
|
+
- Whether anything animates and how.
|
|
51
|
+
- Behavior at any other width.
|
|
52
|
+
- Whether it is keyboard reachable and whether focus is visible.
|
|
53
|
+
- The other appearance, light or dark.
|
|
54
|
+
|
|
55
|
+
Where any of these matter to the reader's question, say that a live URL would answer it and this image cannot.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Grouping and alignment
|
|
2
|
+
|
|
3
|
+
How spacing, shapes, shared edges and ordering communicate what belongs together and what matters most.
|
|
4
|
+
|
|
5
|
+
## Group with space, not lines
|
|
6
|
+
|
|
7
|
+
Three tools create grouping, in order of preference:
|
|
8
|
+
|
|
9
|
+
1. **Negative space**, the default. Related items sit close, unrelated ones far apart.
|
|
10
|
+
2. **Background shapes**, a card or filled container, where a group must read as one unit such as a selectable row or a draggable card.
|
|
11
|
+
3. **Separator lines**, a last resort for dense data where space costs too much, such as tables and long settings lists.
|
|
12
|
+
|
|
13
|
+
The structural rule is that the gap between groups is at least 2× the gap within one. At `8px` inside a group, groups need `16px`+ between them, or the eye can't tell where one ends.
|
|
14
|
+
|
|
15
|
+
```css
|
|
16
|
+
/* Good: spacing alone communicates the grouping */
|
|
17
|
+
.field-group { display: flex; flex-direction: column; gap: 8px; }
|
|
18
|
+
.form { display: flex; flex-direction: column; gap: 24px; }
|
|
19
|
+
|
|
20
|
+
/* Bad: uniform spacing plus lines to compensate */
|
|
21
|
+
.form > * { margin-bottom: 12px; border-bottom: 1px solid var(--separator); }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<!-- Good: Tailwind -->
|
|
26
|
+
<div class="space-y-6">
|
|
27
|
+
<div class="space-y-2">…field group…</div>
|
|
28
|
+
<div class="space-y-2">…field group…</div>
|
|
29
|
+
</div>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Where a separator is genuinely needed, keep it quiet: hairline width, low contrast, never combined with a large gap that already did the job.
|
|
33
|
+
|
|
34
|
+
## Keep controls distinct from content
|
|
35
|
+
|
|
36
|
+
Interactive elements need a visual signal: a background, a border, an underline, or placement in a consistent control zone such as a toolbar or footer row. A control styled identically to static text is invisible.
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<!-- Bad: action looks exactly like the description text next to it -->
|
|
40
|
+
<p class="text-zinc-600">Your trial ends soon. Upgrade now</p>
|
|
41
|
+
|
|
42
|
+
<!-- Good: the action reads as an action -->
|
|
43
|
+
<p class="text-zinc-600">Your trial ends soon.</p>
|
|
44
|
+
<button class="font-medium text-blue-600">Upgrade now</button>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The inverse holds too. A non-clickable badge shaped exactly like the buttons beside it collects dead clicks.
|
|
48
|
+
|
|
49
|
+
## Align to shared edges
|
|
50
|
+
|
|
51
|
+
Pick a small set of alignment edges and put everything on them, because the eye tracks straight edges to scan content.
|
|
52
|
+
|
|
53
|
+
- Every stray edge reads as noise even when nobody can name it: an icon 2px off the text edge, a card padded unlike its neighbor.
|
|
54
|
+
- Use one project spacing step to express hierarchy. `16px` is a useful default where no scale exists, and deeper nesting repeats the same step.
|
|
55
|
+
- Numbers in tables align to the trailing edge, text to the leading edge. Tabular figures are `better-typography`'s.
|
|
56
|
+
|
|
57
|
+
```css
|
|
58
|
+
/* Good: one shared leading edge, one indent step */
|
|
59
|
+
.section { padding-inline: 24px; }
|
|
60
|
+
.section .child { margin-inline-start: 16px; }
|
|
61
|
+
|
|
62
|
+
/* Bad: three unrelated leading edges in one column */
|
|
63
|
+
.header { padding-inline-start: 20px; }
|
|
64
|
+
.list-item { padding-inline-start: 14px; }
|
|
65
|
+
.footer { padding-inline-start: 24px; }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Logical properties, not physical
|
|
69
|
+
|
|
70
|
+
Express direction-dependent horizontal position as leading/trailing so the layout mirrors automatically under `dir="rtl"`:
|
|
71
|
+
|
|
72
|
+
| Physical (avoid) | Logical (use) |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `margin-left` | `margin-inline-start` |
|
|
75
|
+
| `padding-right` | `padding-inline-end` |
|
|
76
|
+
| `left: 0` | `inset-inline-start: 0` |
|
|
77
|
+
| `text-align: left` | `text-align: start` |
|
|
78
|
+
| `border-right` | `border-inline-end` |
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<!-- Good: Tailwind logical utilities -->
|
|
82
|
+
<div class="ms-4 pe-6 text-start">…</div>
|
|
83
|
+
|
|
84
|
+
<!-- Bad: breaks in RTL -->
|
|
85
|
+
<div class="ml-4 pr-6 text-left">…</div>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Reserve physical properties for things that refer to physical screen sides whatever the language, such as positioning against a device notch or matching a gesture direction.
|
|
89
|
+
|
|
90
|
+
Where arrangement encodes progression, as in star ratings, step indicators and progress bars, the sequence mirrors in RTL and stars fill from the trailing side. Flexbox and grid with logical properties mirror automatically; hand-positioned elements do not. Digit order inside numbers never reverses, which with other bidi rules belongs to `better-typography`.
|
|
91
|
+
|
|
92
|
+
## Order by importance
|
|
93
|
+
|
|
94
|
+
Readers scan top-to-bottom and leading-to-trailing. Place content accordingly:
|
|
95
|
+
|
|
96
|
+
- The most important information sits near the top and the leading edge. The further down and trailing something sits, the less attention it gets.
|
|
97
|
+
- Give essential information room. Never bury the one number the user came for under rows of secondary detail. Push that into collapsed sections, tabs, or detail views.
|
|
98
|
+
- Within a row, identifying content leads and metadata and actions trail.
|
|
99
|
+
|
|
100
|
+
```html
|
|
101
|
+
<!-- Good: primary fact first, detail demoted -->
|
|
102
|
+
<div>
|
|
103
|
+
<p class="text-2xl font-semibold">$4,320.00</p>
|
|
104
|
+
<p class="text-sm text-zinc-500">Available balance</p>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Bad: the key fact is buried below the fold of the card -->
|
|
108
|
+
<div>
|
|
109
|
+
<p class="text-sm">Account 4402 · Opened 2019 · Standard tier</p>
|
|
110
|
+
<p class="text-sm">Last statement: June 30</p>
|
|
111
|
+
<p class="text-sm">Balance: $4,320.00</p>
|
|
112
|
+
</div>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
With logical properties, the same hierarchy mirrors correctly in RTL locales.
|
|
116
|
+
|
|
117
|
+
## Don't overload the entry point
|
|
118
|
+
|
|
119
|
+
The first screenful is a table of contents, not the whole book. If everything is prominent, nothing is:
|
|
120
|
+
|
|
121
|
+
- One primary action per view. `better-colors` owns how color enforces it.
|
|
122
|
+
- Group secondary actions behind a menu once they exceed two or three.
|
|
123
|
+
- Prefer a short view that links deeper over a long view that shows everything at level one.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Hit areas
|
|
2
|
+
|
|
3
|
+
Target sizes, expanding hit areas without changing visual size and collision rules.
|
|
4
|
+
|
|
5
|
+
## Target sizes
|
|
6
|
+
|
|
7
|
+
Separate the conformance baseline from larger usability targets:
|
|
8
|
+
|
|
9
|
+
| Standard | Minimum |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| WCAG 2.5.8 (AA) | 24×24px, the hard floor |
|
|
12
|
+
| WCAG 2.5.5 (AAA) | 44×44px |
|
|
13
|
+
| Apple HIG | 44×44pt |
|
|
14
|
+
| Material Design | 48×48dp |
|
|
15
|
+
|
|
16
|
+
WCAG 2.5.8 Level AA requires a 24×24 CSS-pixel target or one of its exceptions. Treat 44px as the recommended touch target for primary controls and 40px as a useful desktop target where density permits. Smaller controls are not automatic failures. Check the spacing, equivalent-control, inline, user-agent and essential exceptions before reporting one.
|
|
17
|
+
|
|
18
|
+
Under the spacing exception, an undersized target passes when a 24px circle centered on its bounding box intersects no other target and no other undersized target's circle. In the simple case, 20px targets need a 4px gap.
|
|
19
|
+
|
|
20
|
+
The visible element can stay small; the hit area is what must be big. Anything that looks clickable must be clickable across its whole visual extent, with no dead zones. A checkbox and its label share one hit target.
|
|
21
|
+
|
|
22
|
+
## Expanding the hit area
|
|
23
|
+
|
|
24
|
+
Where the visible element is smaller, say a 20×20 checkbox, extend the hit area with a pseudo-element. Put it on the wrapping `<label>` or `<button>`, never on the `<input>`, because replaced elements don't render `::before`/`::after` reliably.
|
|
25
|
+
|
|
26
|
+
### CSS example
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
/* Small checkbox with expanded 44px hit area, on the wrapping label */
|
|
30
|
+
.checkbox-label {
|
|
31
|
+
position: relative;
|
|
32
|
+
width: 20px;
|
|
33
|
+
height: 20px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.checkbox-label::after {
|
|
37
|
+
content: "";
|
|
38
|
+
position: absolute;
|
|
39
|
+
top: 50%;
|
|
40
|
+
left: 50%; /* physical centering: direction-independent */
|
|
41
|
+
transform: translate(-50%, -50%);
|
|
42
|
+
width: 44px;
|
|
43
|
+
height: 44px;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Tailwind example
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<button className="relative size-5 after:absolute after:top-1/2 after:left-1/2 after:size-11 after:-translate-1/2">
|
|
51
|
+
<CheckIcon />
|
|
52
|
+
</button>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Layout alternative
|
|
56
|
+
|
|
57
|
+
Where the element can afford real box size, skip the pseudo-element and let the box be the target. That hands the browser real geometry for scrolling and gestures:
|
|
58
|
+
|
|
59
|
+
```css
|
|
60
|
+
.icon-button {
|
|
61
|
+
min-width: 44px;
|
|
62
|
+
min-height: 44px;
|
|
63
|
+
display: inline-grid;
|
|
64
|
+
place-items: center;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Collision rule
|
|
69
|
+
|
|
70
|
+
Where the extended hit area overlaps another interactive element, shrink the pseudo-element to the largest size that does not collide. Two interactive elements never have overlapping hit areas.
|
|
71
|
+
|
|
72
|
+
## Decorative layers
|
|
73
|
+
|
|
74
|
+
A decorative layer painted over interactive content absorbs every pointer event its box covers: a gradient scrim, a glow, a blurred sheen, a full-bleed `::after`. The control underneath looks live and does nothing, and no hit-area sizing fixes it.
|
|
75
|
+
|
|
76
|
+
Give each one `pointer-events: none` (Tailwind `pointer-events-none`) so events reach the control below, plus `aria-hidden="true"` to keep it out of the accessibility tree:
|
|
77
|
+
|
|
78
|
+
```css
|
|
79
|
+
.card-glow {
|
|
80
|
+
position: absolute;
|
|
81
|
+
inset: 0;
|
|
82
|
+
pointer-events: none;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Keep pointer events on any layer the user is meant to hit: a modal scrim that dismisses on click is a control, not decoration.
|
|
87
|
+
|
|
88
|
+
## Touch behavior
|
|
89
|
+
|
|
90
|
+
- Add `touch-action: manipulation` to interactive elements to remove the double-tap-to-zoom delay on mobile.
|
|
91
|
+
- Set `touch-action: none` on a surface implementing its own pan, zoom, or drag gestures, so the browser stops claiming them for scrolling and pinch-zoom. Scope it to that surface; at page level it removes scrolling.
|
|
92
|
+
- Set `-webkit-tap-highlight-color` to match the design instead of the default gray flash.
|
|
93
|
+
- Put hover-only styling behind `@media (hover: hover)`. On touch, `:hover` latches after a tap and holds until the user taps elsewhere, so it reads as a stuck selected state. Tailwind 4's `hover:` variant already compiles under this query.
|
|
94
|
+
- Prefer generous targets and clear affordances over finicky interactions such as tiny drag handles and precise hover zones.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Icon transitions
|
|
2
|
+
|
|
3
|
+
Cross-fading an icon when it changes contextually or by state, with and without a motion library. Icon weight, color and direction live in [icons.md](icons.md).
|
|
4
|
+
|
|
5
|
+
## Contextual icon animations
|
|
6
|
+
|
|
7
|
+
When icons appear or disappear contextually, on hover or a state change, animate them with `opacity`, `scale` and `blur` rather than toggling visibility.
|
|
8
|
+
|
|
9
|
+
### Motion example
|
|
10
|
+
|
|
11
|
+
This uses the `motion` package. Where the project has `framer-motion`, import the same APIs from `"framer-motion"`. Never mix an installed package with the other's import path.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
15
|
+
|
|
16
|
+
function IconButton({ isActive, icon: Icon }) {
|
|
17
|
+
return (
|
|
18
|
+
<button>
|
|
19
|
+
<AnimatePresence mode="popLayout">
|
|
20
|
+
<motion.span
|
|
21
|
+
key={isActive ? "active" : "inactive"}
|
|
22
|
+
initial={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
|
|
23
|
+
animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
|
|
24
|
+
exit={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
|
|
25
|
+
transition={{ type: "spring", duration: 0.3, bounce: 0 }}
|
|
26
|
+
>
|
|
27
|
+
<Icon />
|
|
28
|
+
</motion.span>
|
|
29
|
+
</AnimatePresence>
|
|
30
|
+
</button>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### CSS transition approach (no Motion)
|
|
36
|
+
|
|
37
|
+
Without Motion or Framer Motion, keep both icons in the DOM and cross-fade with CSS transitions. Neither unmounts, so enter and exit both animate smoothly.
|
|
38
|
+
|
|
39
|
+
One icon is absolutely positioned on top of the other. Toggling state cross-fades them, the entering icon scaling up from `0.25` while the exiting one scales down to `0.25`, both with opacity and blur.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
function IconButton({ isActive, ActiveIcon, InactiveIcon }) {
|
|
43
|
+
return (
|
|
44
|
+
<button>
|
|
45
|
+
<div className="relative">
|
|
46
|
+
<div
|
|
47
|
+
className={cn(
|
|
48
|
+
"absolute inset-0 flex items-center justify-center",
|
|
49
|
+
"transition-[opacity,filter,scale] duration-300",
|
|
50
|
+
"ease-[cubic-bezier(0.2,0,0,1)]",
|
|
51
|
+
isActive
|
|
52
|
+
? "scale-100 opacity-100 blur-0"
|
|
53
|
+
: "scale-[0.25] opacity-0 blur-[4px]"
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
<ActiveIcon />
|
|
57
|
+
</div>
|
|
58
|
+
<div
|
|
59
|
+
className={cn(
|
|
60
|
+
"transition-[opacity,filter,scale] duration-300",
|
|
61
|
+
"ease-[cubic-bezier(0.2,0,0,1)]",
|
|
62
|
+
isActive
|
|
63
|
+
? "scale-[0.25] opacity-0 blur-[4px]"
|
|
64
|
+
: "scale-100 opacity-100 blur-0"
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
<InactiveIcon />
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</button>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The non-absolute icon, `InactiveIcon`, defines the layout size. The absolute one, `ActiveIcon`, overlays it without affecting flow.
|
|
76
|
+
|
|
77
|
+
### Choosing between Motion and CSS
|
|
78
|
+
|
|
79
|
+
| | Motion (Framer Motion) | CSS transitions (both icons in DOM) |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| **Enter animation** | Yes | Yes |
|
|
82
|
+
| **Exit animation** | Yes (via `AnimatePresence`) | Yes (cross-fade, icon never unmounts) |
|
|
83
|
+
| **Spring physics** | Yes | No, use `cubic-bezier(0.2, 0, 0, 1)` as approximation |
|
|
84
|
+
| **When to use** | Project already uses `motion` or `framer-motion` | No motion dependency, or keeping bundle small |
|
|
85
|
+
|
|
86
|
+
Check the project's `package.json`. Import from `"motion/react"` when `motion` is installed, or `"framer-motion"` when that is. Where both exist, follow the imports the component or its nearest peers already use. Where neither is present, use the CSS cross-fade and never add a dependency just for icon transitions.
|
|
87
|
+
|
|
88
|
+
### When to animate icons
|
|
89
|
+
|
|
90
|
+
| Animate | Don't animate |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| Icons that appear on hover (action buttons) | Static navigation icons |
|
|
93
|
+
| State change icons (play → pause, like → liked) | Decorative icons |
|
|
94
|
+
| Icons in contextual toolbars | Icons that are always visible |
|
|
95
|
+
| Loading/success state indicators | Icon labels (text next to icon) |
|
|
96
|
+
|
|
97
|
+
Use exactly these values for contextual icon animations. Do not deviate:
|
|
98
|
+
- `scale`: `0.25` → `1` (never use `0.5` or `0.6`)
|
|
99
|
+
- `opacity`: `0` → `1`
|
|
100
|
+
- `filter`: `"blur(4px)"` → `"blur(0px)"`
|
|
101
|
+
- `transition`: `{ type: "spring", duration: 0.3, bounce: 0 }`; **bounce must always be `0`**, never `0.1` or any other value
|
|
102
|
+
|