@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.
- package/framework/agents/researcher.md +1 -1
- package/framework/mcps/github-mcp/README.md +1 -1
- package/framework/mcps/github-mcp/configs/.env.example +1 -1
- package/framework/plugins/ponytail/README.md +1 -1
- package/framework/plugins/ponytail/capabilities.md +1 -1
- package/framework/references/standards/pull-requests.md +1 -1
- package/framework/skills/README.md +3 -3
- package/framework/skills/diagram-design/SKILL.md +1 -1
- 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/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/taste/SKILL.md +47 -0
- package/framework/skills/taste/examples.md +73 -0
- package/framework/skills/taste/references/brandkit.md +793 -0
- package/framework/skills/taste/references/design-taste-frontend-v1.md +221 -0
- package/framework/skills/taste/references/design-taste-frontend.md +1201 -0
- package/framework/skills/taste/references/full-output-enforcement.md +44 -0
- package/framework/skills/taste/references/gpt-taste.md +69 -0
- package/framework/skills/taste/references/high-end-visual-design.md +93 -0
- package/framework/skills/taste/references/image-to-code.md +1223 -0
- package/framework/skills/taste/references/imagegen-frontend-mobile.md +1460 -0
- package/framework/skills/taste/references/imagegen-frontend-web.md +982 -0
- package/framework/skills/taste/references/industrial-brutalist-ui.md +87 -0
- package/framework/skills/taste/references/minimalist-ui.md +80 -0
- package/framework/skills/taste/references/redesign-existing-projects.md +173 -0
- package/framework/skills/taste/references/stitch-design-taste-design.md +121 -0
- package/framework/skills/taste/references/stitch-design-taste.md +179 -0
- package/package.json +1 -1
- package/src/agent-configs.js +6 -2
- package/src/catalog.js +3 -2
- package/src/cli.js +82 -2
- package/src/doctor.js +84 -0
- package/src/installer.js +337 -154
- package/framework/skills/brand-guidelines/LICENSE.txt +0 -202
- package/framework/skills/brand-guidelines/SKILL.md +0 -73
- package/framework/skills/lavish/SKILL.md +0 -67
- package/framework/skills/lavish/examples.md +0 -31
|
@@ -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
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Icons
|
|
2
|
+
|
|
3
|
+
Icon weight, states, sizing and direction, the details that make icons sit naturally in an interface.
|
|
4
|
+
|
|
5
|
+
## Match icon stroke to text weight
|
|
6
|
+
|
|
7
|
+
A hairline icon beside semibold text reads as broken; a heavy icon beside regular text shouts.
|
|
8
|
+
|
|
9
|
+
| Adjacent text | Icon stroke width (24px grid) |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Regular (400), 14–16px | `1.5px` |
|
|
12
|
+
| Medium/Semibold (500–600) | `2px` |
|
|
13
|
+
| Bold (700), or emphasized standalone | `2.5px` |
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<!-- Good: stroke tuned to the label weight -->
|
|
17
|
+
<button class="flex items-center gap-2 font-semibold">
|
|
18
|
+
<PlusIcon stroke-width="2" class="size-4" />
|
|
19
|
+
New project
|
|
20
|
+
</button>
|
|
21
|
+
|
|
22
|
+
<!-- Bad: default 1.5px stroke against a bold label -->
|
|
23
|
+
<button class="flex items-center gap-2 font-bold">
|
|
24
|
+
<PlusIcon stroke-width="1.5" class="size-4" />
|
|
25
|
+
New project
|
|
26
|
+
</button>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Two related consistency rules:
|
|
30
|
+
|
|
31
|
+
- **One optical strategy per surface.** Never mix icon libraries with incompatible stroke conventions on one toolbar. Where the library supports stroke variants, match them to adjacent text as above; otherwise keep the set's native stroke and use size or color for emphasis.
|
|
32
|
+
- **Size icons relative to the text's cap height**, typically `1em`–`1.25em` when inline with text, so the pair scales together.
|
|
33
|
+
|
|
34
|
+
## One SVG, recolored per state
|
|
35
|
+
|
|
36
|
+
Never ship separate assets for default, hover, selected and disabled states. Use one SVG drawn with `currentColor` and let CSS state drive the color:
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<!-- Good: one asset, states are CSS -->
|
|
40
|
+
<svg fill="none" stroke="currentColor" stroke-width="2">…</svg>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```css
|
|
44
|
+
.icon-button { color: oklch(0.552 0.016 285.938); }
|
|
45
|
+
.icon-button:hover { color: oklch(0.21 0.006 285.885); }
|
|
46
|
+
.icon-button[aria-pressed="true"] { color: oklch(0.623 0.188 259.815); }
|
|
47
|
+
.icon-button:disabled { opacity: 0.4; }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<!-- Tailwind -->
|
|
52
|
+
<button class="text-zinc-500 hover:text-zinc-900 aria-pressed:text-blue-600 disabled:opacity-40">
|
|
53
|
+
<BookmarkIcon />
|
|
54
|
+
</button>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Hardcoded fills inside the SVG, such as `fill="#666"`, break this. Strip them to `currentColor` when importing icons.
|
|
58
|
+
|
|
59
|
+
## Outline default, fill active
|
|
60
|
+
|
|
61
|
+
Where an icon set offers outline and filled variants, use them as a state pair, never interchangeably:
|
|
62
|
+
|
|
63
|
+
| Variant | Use for |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| Outline | Default state: toolbars, list rows, inline with text |
|
|
66
|
+
| Fill | Selected/active state: the active tab, a toggled bookmark, a liked heart |
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// Good: variant communicates state
|
|
70
|
+
<TabIcon variant={isActive ? "solid" : "outline"} />
|
|
71
|
+
|
|
72
|
+
// Bad: filled icons everywhere, so the active tab has no state signal
|
|
73
|
+
<TabIcon variant="solid" />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The swap between variants is a contextual icon animation. Use the exact cross-fade values in [icon-transitions.md](icon-transitions.md).
|
|
77
|
+
|
|
78
|
+
## Design at render size
|
|
79
|
+
|
|
80
|
+
An icon that looks great at 48px collapses into mush at 16px. Thin interior lines, tight counters and fine texture all blur or alias when small.
|
|
81
|
+
|
|
82
|
+
- Test every icon at the smallest size it will render, often `16px`. It must stay recognizable there.
|
|
83
|
+
- Prefer simplified glyphs for small contexts over scaling down detailed artwork.
|
|
84
|
+
- Keep icons on the pixel grid at their render size. A 16px icon drawn on a 24px grid with fractional scaling renders soft, so use the set's native grid sizes (`16`, `20`, `24`) rather than arbitrary scales.
|
|
85
|
+
- Always SVG, never raster, so the same asset stays crisp at every density.
|
|
86
|
+
|
|
87
|
+
## Icons in RTL
|
|
88
|
+
|
|
89
|
+
Under `dir="rtl"`, flip icons whose meaning is tied to reading direction, and leave the rest alone:
|
|
90
|
+
|
|
91
|
+
| Flip | Don't flip |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| Back/forward arrows, chevrons in navigation | Logos and brand marks |
|
|
94
|
+
| Text-block glyphs (alignment, lists, indent) | Checkmarks |
|
|
95
|
+
| Speaker/volume waves (emanate in reading direction) | Physical objects: clocks, cups, pencils |
|
|
96
|
+
| "Send" style directional glyphs | Media playback (play/rewind refer to tape direction, convention keeps them LTR) |
|
|
97
|
+
|
|
98
|
+
```css
|
|
99
|
+
/* Good: mirror only direction-dependent icons */
|
|
100
|
+
[dir="rtl"] .icon-directional {
|
|
101
|
+
scale: -1 1;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<!-- Tailwind -->
|
|
107
|
+
<ChevronRightIcon class="icon-directional rtl:-scale-x-100" />
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Analyze composite icons part by part. A badge or slash overlay may keep its position even when the base glyph flips. Accessible names for icon-only buttons belong to `better-accessibility`.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Change review
|
|
2
|
+
|
|
3
|
+
This skill reviews a change rather than a screen. It resolves the scope, expands the changed files to the surfaces they affect, reads both sides of the diff and classifies every finding.
|
|
4
|
+
|
|
5
|
+
Scope is all it owns. Domain rules belong to the `better-*` skills. Severity, consolidation, coverage, the cap and the verdict belong to `better-interface`, which this skill hands the review to.
|
|
6
|
+
|
|
7
|
+
Correctness, tests, security and performance belong to the project's general code review. Name the concern once and move on.
|
|
8
|
+
|
|
9
|
+
## The change, not the codebase
|
|
10
|
+
|
|
11
|
+
The author is asking "did I make this worse?". Report what the change caused and stay mostly quiet about what it merely touched. Three pre-existing findings is a courtesy; thirty is a different review and one nobody asked for.
|
|
12
|
+
|
|
13
|
+
Read the change before forming an opinion of it. The stated intent decides what counts as incomplete, and a skimmed diff produces findings about code the next hunk already fixed.
|
|
14
|
+
|
|
15
|
+
## Core principles
|
|
16
|
+
|
|
17
|
+
### 1. Resolve the change scope first
|
|
18
|
+
|
|
19
|
+
The whole invocation is the target, so `/interface-review pr 482` reviews pull request 482. [Scope resolution]() holds the accepted targets and how each resolves.
|
|
20
|
+
|
|
21
|
+
With no target supplied, resolve in this order and stop at the first match:
|
|
22
|
+
|
|
23
|
+
1. `HEAD` is ahead of `git merge-base origin/<default-branch> HEAD`: that range **plus** any uncommitted changes, with the commit count and the uncommitted file count stated separately.
|
|
24
|
+
2. The working tree is dirty: the uncommitted changes.
|
|
25
|
+
3. Neither: there is no change to review. Stop and ask, per **With no change, ask rather than invent one**.
|
|
26
|
+
|
|
27
|
+
Order matters. Check the working tree first and one stray formatting edit shadows a twelve-commit branch, with the report still claiming full coverage.
|
|
28
|
+
|
|
29
|
+
Exclude lockfiles, snapshots, generated output, vendored code and binaries, and name what you excluded. An empty scope after exclusions reaches the same place by a different route.
|
|
30
|
+
|
|
31
|
+
### 2. With no change, ask rather than invent one
|
|
32
|
+
|
|
33
|
+
A clean tree with nothing ahead of the merge base means the user asked to review a change that does not exist. Never fall back to `HEAD~1..HEAD` on your own. The last commit is whatever happened to land, often a merge, often someone else's work, and a report on it is indistinguishable from a report on what the user meant.
|
|
34
|
+
|
|
35
|
+
State the repository facts you found, then offer the routes and wait. [Nothing to review]() holds the facts to gather:
|
|
36
|
+
|
|
37
|
+
- **The last commit**, `HEAD~1..HEAD`, named by short SHA and subject, so the user sees what they would get before choosing it.
|
|
38
|
+
- **A target they name**: `pr <n>`, a branch, a ref, or a range, resolved per **Resolve the change scope first**.
|
|
39
|
+
- **A whole-repository interface audit**, which is not a change review. Hand it to `better-interface` as a repository-scope review, without this skill's scope block, statuses, or pre-existing section. With no change, every finding is pre-existing and the classification says nothing.
|
|
40
|
+
|
|
41
|
+
Check for an open pull request on the current branch before asking, and offer it first. A branch whose commits already landed resolves to no change, while its pull request is still exactly what the user meant.
|
|
42
|
+
|
|
43
|
+
Where the scope emptied out after exclusions, say which files were excluded and ask the same way. Never report a review of nothing as `Approve`.
|
|
44
|
+
|
|
45
|
+
### 3. A diff is not a surface
|
|
46
|
+
|
|
47
|
+
A changed file is evidence, not the review subject. Its **blast radius** is the set of surfaces it renders in; review those.
|
|
48
|
+
|
|
49
|
+
Expand the blast radius one hop by default: the direct importers and callers. Expand a second hop only for design tokens, theme values and shared primitives, where one line reaches the whole product.
|
|
50
|
+
|
|
51
|
+
Review at most five consumers, ordered by [the rule in Scope resolution](), then state how many you did not expand. A sweep with no bound cannot support the coverage it claims, and an unstated cutoff reads as completeness.
|
|
52
|
+
|
|
53
|
+
### 4. Read the removed lines
|
|
54
|
+
|
|
55
|
+
Regressions are invisible in the post-change state. Read the `-` side of every hunk against [Removed signals]().
|
|
56
|
+
|
|
57
|
+
A signal is a lead, not a finding. A removal is only a regression when nothing in the change replaces it, and the domain skill owns that judgement. Route each unmatched removal to its owner, report only what that skill confirms and status it `Regression`. That tells the author they broke something that worked rather than made a new mistake.
|
|
58
|
+
|
|
59
|
+
### 5. Classify every finding
|
|
60
|
+
|
|
61
|
+
Give every finding one status:
|
|
62
|
+
|
|
63
|
+
- `Introduced`: the change created it.
|
|
64
|
+
- `Regression`: the change weakened something previously correct.
|
|
65
|
+
- `Pre-existing`: present in the touched code but not caused by this change.
|
|
66
|
+
|
|
67
|
+
Status by what the diff touched, not by which file it sits in: a line the change never touched is `Pre-existing` even three lines from a hunk. Confirm against the base ref when it matters:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
git blame -L <line>,<line> "$BASE" -- path/to/file
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Hand every finding up with its status attached and let `better-interface` apply its cap and verdict rules.
|
|
74
|
+
|
|
75
|
+
### 6. Hold the change to its stated intent
|
|
76
|
+
|
|
77
|
+
Read the pull request title and body, the linked issue and the commit messages, then review whether the interface delivers what they claim.
|
|
78
|
+
|
|
79
|
+
This is what surfaces the **incomplete** change. A surface review cannot see it, because it inspects the states that are present, and here the point is the ones that are absent:
|
|
80
|
+
|
|
81
|
+
- A new variant, size, or theme applied to some states but not all: hover, focus, active, disabled, loading, selected.
|
|
82
|
+
- A new user-facing string with no entry in the translation catalogue the project maintains.
|
|
83
|
+
- A new component with no empty, loading, error, disabled, or narrow-width state.
|
|
84
|
+
- A control added to one surface but not to the siblings that already carry its peers.
|
|
85
|
+
|
|
86
|
+
Do not report scope creep. Whether a change does too much is a process question, not an interface one.
|
|
87
|
+
|
|
88
|
+
### 7. Hand the review to `better-interface`
|
|
89
|
+
|
|
90
|
+
Hand `better-interface` the scope block, the affected surfaces and a status on every finding. It routes to the domain skills, applies severity, consolidates, enforces the cap and issues the verdict.
|
|
91
|
+
|
|
92
|
+
If `better-interface` is unavailable, report the resolved scope and the file inventory, name it as the missing skill and stop. Do not invent a severity scale, a cap, or a verdict.
|
|
93
|
+
|
|
94
|
+
### 8. Never mutate the working tree
|
|
95
|
+
|
|
96
|
+
A change review is read-only, including the checkout. Fetch pull request refs; never check them out. `git fetch` writes only to `.git` and is permitted. `gh pr checkout`, `git checkout`, `git switch` and `git stash` rewrite the files the author has open. They fail against local edits or discard them, so they are never permitted.
|
|
97
|
+
|
|
98
|
+
Rendered verification is opt-in. Mark visual and runtime claims **Not verified** unless the project exposes a cheap preview or the user asks for a rendered review. When they do, use an isolated worktree (`git worktree add /tmp/review-<n> refs/remotes/pr/<n>`) and remove it when done.
|
|
99
|
+
|
|
100
|
+
## Before you finish
|
|
101
|
+
|
|
102
|
+
| Mistake | Fix |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| One stray edit reviewed instead of the branch | Check `merge-base` before the working tree, and report both counts |
|
|
105
|
+
| The last commit reviewed because there was no change | State the facts and offer the last commit, a named target, or a repository audit |
|
|
106
|
+
| Hunks reviewed without their consumers | Expand one hop, two for tokens and primitives, and name what you skipped |
|
|
107
|
+
| Only the `+` side of the diff read | Search the `-` side for removed accessibility, focus, motion and text signals |
|
|
108
|
+
| An equivalent replacement reported as a regression | Route the removal to its owner; report only what it confirms |
|
|
109
|
+
| A removal reported as a new mistake | Status it `Regression` so the author knows it used to work |
|
|
110
|
+
| A line near a hunk statused `Introduced` | Status by what the diff touched, confirmed with `git blame` against the base ref |
|
|
111
|
+
| A pull request checked out to review it | Fetch the ref and review it in place |
|
|
112
|
+
| Line numbers cited that do not exist on the reviewed ref | Cite against the head ref named in the scope block |
|
|
113
|
+
| The severity scale or the finding cap restated here | Defer to `better-interface` |
|
|
114
|
+
| Correctness, test, or security findings in the report | Name the concern once, point at the project's code review and drop it |
|
|
115
|
+
|
|
116
|
+
## Review output format
|
|
117
|
+
|
|
118
|
+
Open with the scope block:
|
|
119
|
+
|
|
120
|
+
| Field | Value |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| Target | `branch`, `working`, `staged`, `pr 482`, or the range as entered |
|
|
123
|
+
| Base ref | `origin/main` at `a1b2c3d` |
|
|
124
|
+
| Head ref | `refs/remotes/pr/482` at `e4f5g6h` |
|
|
125
|
+
| Commits | 7 committed, 2 files uncommitted |
|
|
126
|
+
| Files in scope | 12 after exclusions |
|
|
127
|
+
| Excluded | `pnpm-lock.yaml`, `src/__snapshots__/`: lockfile and snapshots |
|
|
128
|
+
| Surfaces expanded | `CheckoutPage`, `SettingsPanel`; 3 further `Button` consumers not expanded |
|
|
129
|
+
|
|
130
|
+
The coverage table follows it unchanged. A domain with no evidence in the change scope is `Not reviewed: no evidence in the change scope`, which is a coverage statement rather than a gap.
|
|
131
|
+
|
|
132
|
+
Then the findings, with a `Status` column per **Classify every finding**:
|
|
133
|
+
|
|
134
|
+
| Severity | Domain | Status | Location | Before | After | Why |
|
|
135
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
136
|
+
| HIGH | Accessibility | Regression | `src/Dialog.tsx:42` | `aria-label="Close"` removed in this change | Restore `aria-label="Close"` on the icon-only control | The close control had an accessible name before this change and no longer does |
|
|
137
|
+
|
|
138
|
+
With no `Introduced` or `Regression` findings, omit the table and state "No actionable interface findings in this change."
|
|
139
|
+
|
|
140
|
+
Then `Pre-existing` findings, at most three, highest severity first, stated plainly as not this change's responsibility. Omit the section when there are none.
|
|
141
|
+
|
|
142
|
+
| Severity | Domain | Location | Issue |
|
|
143
|
+
| --- | --- | --- | --- |
|
|
144
|
+
| MEDIUM | Typography | `src/Toolbar.tsx:7` | Numeric badges use proportional figures; predates this change |
|
|
145
|
+
|
|
146
|
+
The cap and the verdict cover `Introduced` and `Regression` only. `Pre-existing` findings sit outside the cap, so touching a legacy file cannot turn into a full-file audit. They sit outside the verdict too, so a change whose only findings are pre-existing is an `Approve`.
|
|
147
|
+
|
|
148
|
+
End with `Block` when any `HIGH` remains and `Approve` otherwise, leaving the remaining findings in the table as work to do. When `better-interface` is available, the severity scale and the cap come from it.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Motion and zoom
|
|
2
|
+
|
|
3
|
+
`prefers-reduced-motion`, zoom and reflow, and unit choices that respect user settings.
|
|
4
|
+
|
|
5
|
+
## prefers-reduced-motion
|
|
6
|
+
|
|
7
|
+
Make motion opt-in. Wrap animations in `@media (prefers-reduced-motion: no-preference)` so users who asked for reduced motion get the static version by default, rather than you chasing every animation with an override.
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
/* Good: motion is opt-in */
|
|
11
|
+
.card {
|
|
12
|
+
/* static styles */
|
|
13
|
+
}
|
|
14
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
15
|
+
.card {
|
|
16
|
+
transition: transform 200ms ease-out;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// Tailwind: motion-safe / motion-reduce variants
|
|
23
|
+
<div className="motion-safe:transition-transform motion-safe:hover:-translate-y-1" />
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For an existing codebase where opt-in isn't feasible, the global kill switch is the fallback:
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
@media (prefers-reduced-motion: reduce) {
|
|
30
|
+
*, *::before, *::after {
|
|
31
|
+
animation-duration: 0.01ms !important;
|
|
32
|
+
animation-iteration-count: 1 !important;
|
|
33
|
+
transition-duration: 0.01ms !important;
|
|
34
|
+
scroll-behavior: auto !important;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`0.01ms` rather than `none`, so `animationend` and `transitionend` still fire and JS waiting on them doesn't hang.
|
|
40
|
+
|
|
41
|
+
### What to disable vs reduce
|
|
42
|
+
|
|
43
|
+
Reduced motion means reduced, not eliminated. It targets vestibular triggers, not feedback.
|
|
44
|
+
|
|
45
|
+
| Disable entirely | Replace | Keep |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| Parallax scrolling | Slide/scale/zoom transitions → opacity crossfade | Loading spinners and progress |
|
|
48
|
+
| Autoplaying video, GIFs, looping decoration | Smooth scrolling → instant jump | Instant state changes (hover color, focus ring) |
|
|
49
|
+
| Spinning, large-scale movement across the screen | Auto-rotating carousels → start paused | Brief functional feedback (button press) |
|
|
50
|
+
|
|
51
|
+
Animations are interruptible and driven by user input. Nothing autoplays or refuses to stop, and under reduced motion carousels start paused.
|
|
52
|
+
|
|
53
|
+
## Autoplay and timed UI
|
|
54
|
+
|
|
55
|
+
Motion the user didn't ask for and UI acting on its own schedule:
|
|
56
|
+
|
|
57
|
+
- **No autoplaying media without visible controls** (WCAG 2.2.2). Anything moving, blinking, or updating on its own for more than 5 seconds needs a visible pause or stop control, muted looping hero videos included.
|
|
58
|
+
- **Prefer explicit dismissal over timers.** Auto-dismissal suits low-stakes confirmations and nothing else. A toast carrying an action, an error, or information the user may need stays until dismissed. Where one must time out, 5 seconds is the floor, and hovering or focusing it pauses the timer.
|
|
59
|
+
- **Never put critical information only in a timed element.** A vanished toast with the only link to an undo action is data loss on a schedule.
|
|
60
|
+
|
|
61
|
+
## Zoom and reflow
|
|
62
|
+
|
|
63
|
+
- **200% zoom** (WCAG 1.4.4). All content and functionality survives text scaled to 200%, and the viewport leaves the reader able to zoom.
|
|
64
|
+
- **Reflow at 320px** (WCAG 1.4.10). At 400% zoom on a 1280px viewport, equivalent to a 320px one, the page must work with vertical scrolling alone. Genuinely 2D content is the exception: tables, maps and code blocks scroll inside their own container.
|
|
65
|
+
|
|
66
|
+
Fixed heights are what break under zoom. Use `min-height` on anything containing text and let containers grow.
|
|
67
|
+
|
|
68
|
+
### rem vs px
|
|
69
|
+
|
|
70
|
+
Respect how the codebase is set up. Where the project sizes in `px`, or on an established Tailwind scale, stay consistent and never introduce mixed units into someone else's system. Where you do have the choice, in new code or a codebase already on `rem`, `rem` respects the user's base font size and `px` ignores it:
|
|
71
|
+
|
|
72
|
+
| Use `rem` | Use `px` |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `font-size` | Borders and hairlines |
|
|
75
|
+
| `max-width` of text containers | Focus outline width and offset |
|
|
76
|
+
| Media-query breakpoints (`@media (min-width: 48rem)`) | `box-shadow` details |
|
|
77
|
+
| Spacing that should scale with text | Fixed-size decorations |
|
|
78
|
+
|
|
79
|
+
Breakpoints are where the choice matters most. At a larger base font size an `em` or `rem` query switches to the mobile layout when the text needs it, and a `px` query does not.
|