@bompus/house-rules 0.0.0-stage → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +54 -0
- package/LICENSE +21 -0
- package/README.md +347 -2
- package/THIRD_PARTY_NOTICES.md +188 -0
- package/compose.mjs +119 -0
- package/composition.mjs +353 -0
- package/config-view.mjs +194 -0
- package/config.mjs +320 -0
- package/docs/assets/banner-dark.png +0 -0
- package/docs/assets/banner-light.png +0 -0
- package/docs/assets/overview.png +0 -0
- package/docs/configuration.md +193 -0
- package/docs/npm-release.md +45 -0
- package/docs/skill-names.md +50 -0
- package/examples/person/house-rules.json +15 -0
- package/examples/person/rules/10-tooling.md +8 -0
- package/examples/person/rules/20-reporting.md +9 -0
- package/examples/project/AGENTS.md +18 -0
- package/package.json +40 -4
- package/rule-references.mjs +91 -0
- package/rules/core.md +431 -0
- package/rules/modifiers/coded-offers.md +40 -0
- package/rules/modifiers/effort-estimates.md +41 -0
- package/rules/modifiers/land-when-done.md +12 -0
- package/rules/modifiers/low-quota-handoff.md +19 -0
- package/rules/modifiers/multi-agent.md +34 -0
- package/rules/modifiers/no-attribution.md +10 -0
- package/rules/modifiers/plan-files.md +50 -0
- package/rules/modifiers/question-cards.md +48 -0
- package/rules/modifiers/release-batching.md +13 -0
- package/rules/modifiers/scratch-on-disk.md +15 -0
- package/rules/modifiers/shared-host-load.md +48 -0
- package/rules/modifiers/solo-operator.md +13 -0
- package/rules/modifiers/squash-landing.md +22 -0
- package/rules/modifiers/swarmail.md +12 -0
- package/rules/references/github-actions.md +45 -0
- package/rules/references/landing.md +39 -0
- package/rules/references/multi-agent.md +47 -0
- package/rules/references/release-batching.md +43 -0
- package/rules/references/squash-landing.md +44 -0
- package/setup.mjs +375 -0
- package/skills/hr-agent-guidance-audit/LICENSE +21 -0
- package/skills/hr-agent-guidance-audit/SKILL.md +86 -0
- package/skills/hr-agent-guidance-audit/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-audit/references/records-and-lessons.md +60 -0
- package/skills/hr-agent-guidance-refresh/LICENSE +21 -0
- package/skills/hr-agent-guidance-refresh/SKILL.md +68 -0
- package/skills/hr-agent-guidance-refresh/agents/openai.yaml +2 -0
- package/skills/hr-agent-guidance-refresh/references/verification.md +47 -0
- package/skills/hr-api-exposure-check/SKILL.md +159 -0
- package/skills/hr-api-exposure-check/agents/openai.yaml +3 -0
- package/skills/hr-audit-choices/SKILL.md +67 -0
- package/skills/hr-audit-choices/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/NOTICE.md +36 -0
- package/skills/hr-benchmarking/SKILL.md +316 -0
- package/skills/hr-benchmarking/agents/openai.yaml +2 -0
- package/skills/hr-benchmarking/references/javascript.md +17 -0
- package/skills/hr-benchmarking/references/priority-findings-20261006.md +115 -0
- package/skills/hr-benchmarking/references/tools.md +160 -0
- package/skills/hr-benchmarking/scripts/process-attribution.ps1 +20 -0
- package/skills/hr-benchmarking/scripts/process-attribution.py +107 -0
- package/skills/hr-better-accessibility/LICENSE +22 -0
- package/skills/hr-better-accessibility/NOTICE.md +46 -0
- package/skills/hr-better-accessibility/SKILL.md +119 -0
- package/skills/hr-better-accessibility/agents/openai.yaml +3 -0
- package/skills/hr-better-accessibility/focus-and-keyboard.md +130 -0
- package/skills/hr-better-accessibility/forms.md +82 -0
- package/skills/hr-better-accessibility/hit-areas.md +98 -0
- package/skills/hr-better-accessibility/motion-and-zoom.md +81 -0
- package/skills/hr-better-accessibility/screen-readers.md +101 -0
- package/skills/hr-better-accessibility/semantics-and-aria.md +84 -0
- package/skills/hr-better-colors/LICENSE +21 -0
- package/skills/hr-better-colors/NOTICE.md +37 -0
- package/skills/hr-better-colors/SKILL.md +114 -0
- package/skills/hr-better-colors/agents/openai.yaml +3 -0
- package/skills/hr-better-colors/color-formats.md +90 -0
- package/skills/hr-better-colors/color-usage.md +140 -0
- package/skills/hr-better-colors/contrast.md +80 -0
- package/skills/hr-better-colors/palette-generation.md +104 -0
- package/skills/hr-better-colors/palette-structure.md +76 -0
- package/skills/hr-better-colors/token-naming.md +97 -0
- package/skills/hr-better-layout/LICENSE +22 -0
- package/skills/hr-better-layout/NOTICE.md +48 -0
- package/skills/hr-better-layout/SKILL.md +92 -0
- package/skills/hr-better-layout/agents/openai.yaml +3 -0
- package/skills/hr-better-layout/grouping-and-alignment.md +144 -0
- package/skills/hr-better-layout/spacing-and-adaptivity.md +215 -0
- package/skills/hr-better-typography/LICENSE +22 -0
- package/skills/hr-better-typography/NOTICE.md +46 -0
- package/skills/hr-better-typography/SKILL.md +172 -0
- package/skills/hr-better-typography/agents/openai.yaml +3 -0
- package/skills/hr-better-typography/choosing-fonts.md +69 -0
- package/skills/hr-better-typography/css-cheat-sheet.md +65 -0
- package/skills/hr-better-typography/details-and-accessibility.md +119 -0
- package/skills/hr-better-typography/spacing-and-sizing.md +131 -0
- package/skills/hr-better-typography/variable-fonts-and-opentype.md +105 -0
- package/skills/hr-better-typography/wrapping-and-punctuation.md +57 -0
- package/skills/hr-better-writing/LICENSE +22 -0
- package/skills/hr-better-writing/NOTICE.md +46 -0
- package/skills/hr-better-writing/SKILL.md +122 -0
- package/skills/hr-better-writing/agents/openai.yaml +3 -0
- package/skills/hr-change-impact/SKILL.md +113 -0
- package/skills/hr-change-impact/agents/openai.yaml +5 -0
- package/skills/hr-code-review/LICENSE +21 -0
- package/skills/hr-code-review/NOTICE.md +26 -0
- package/skills/hr-code-review/SKILL.md +147 -0
- package/skills/hr-code-review/agents/openai.yaml +3 -0
- package/skills/hr-code-review/references/evidence.md +57 -0
- package/skills/hr-design-exploration/SKILL.md +82 -0
- package/skills/hr-design-exploration/agents/openai.yaml +4 -0
- package/skills/hr-diagnosing-bugs/NOTICE.md +45 -0
- package/skills/hr-diagnosing-bugs/SKILL.md +108 -0
- package/skills/hr-diagnosing-bugs/agents/openai.yaml +3 -0
- package/skills/hr-diagnosing-bugs/references/reporting.md +72 -0
- package/skills/hr-explain-code/NOTICE.md +37 -0
- package/skills/hr-explain-code/SKILL.md +110 -0
- package/skills/hr-explain-code/agents/openai.yaml +5 -0
- package/skills/hr-explain-code/references/helper-brief.md +39 -0
- package/skills/hr-extract-shared-steps/SKILL.md +134 -0
- package/skills/hr-extract-shared-steps/agents/openai.yaml +3 -0
- package/skills/hr-handoff/SKILL.md +91 -0
- package/skills/hr-handoff/agents/openai.yaml +5 -0
- package/skills/hr-handoff/assets/ledger.example.json +10 -0
- package/skills/hr-handoff/assets/receipt.example.json +76 -0
- package/skills/hr-handoff/assets/source.example.json +20 -0
- package/skills/hr-handoff/references/reconciliation.md +105 -0
- package/skills/hr-handoff/scripts/check-reconciliation.mjs +282 -0
- package/skills/hr-house-rules-setup/SKILL.md +100 -0
- package/skills/hr-house-rules-setup/agents/openai.yaml +5 -0
- package/skills/hr-issue-tracker-setup/SKILL.md +49 -0
- package/skills/hr-issue-tracker-setup/agents/openai.yaml +5 -0
- package/skills/hr-lean-plan/SKILL.md +142 -0
- package/skills/hr-lean-plan/agents/openai.yaml +5 -0
- package/skills/hr-maintainability-review/SKILL.md +176 -0
- package/skills/hr-maintainability-review/agents/openai.yaml +5 -0
- package/skills/hr-manual-qa/NOTICE.md +36 -0
- package/skills/hr-manual-qa/SKILL.md +37 -0
- package/skills/hr-manual-qa/agents/openai.yaml +3 -0
- package/skills/hr-navigation-retro/LICENSE +21 -0
- package/skills/hr-navigation-retro/NOTICE.md +5 -0
- package/skills/hr-navigation-retro/SKILL.md +86 -0
- package/skills/hr-navigation-retro/agents/openai.yaml +4 -0
- package/skills/hr-navigation-retro/references/inputs.md +67 -0
- package/skills/hr-navigation-retro/scripts/retro.py +552 -0
- package/skills/hr-navigation-retro/scripts/transcripts.py +205 -0
- package/skills/hr-ordering-tests/SKILL.md +51 -0
- package/skills/hr-ordering-tests/agents/openai.yaml +2 -0
- package/skills/hr-plain-prose/SKILL.md +261 -0
- package/skills/hr-plain-prose/agents/openai.yaml +3 -0
- package/skills/hr-plain-prose/scripts/check.mjs +331 -0
- package/skills/hr-pr-followup/LICENSE +21 -0
- package/skills/hr-pr-followup/NOTICE.md +9 -0
- package/skills/hr-pr-followup/SKILL.md +71 -0
- package/skills/hr-pr-followup/agents/openai.yaml +2 -0
- package/skills/hr-pr-followup/references/review-helpers.md +90 -0
- package/skills/hr-pr-followup/scripts/check-pr-review.ts +510 -0
- package/skills/hr-pr-followup/scripts/resolve-pr-thread.ts +219 -0
- package/skills/hr-progress-report/SKILL.md +49 -0
- package/skills/hr-progress-report/agents/openai.yaml +5 -0
- package/skills/hr-read-reddit/SKILL.md +44 -0
- package/skills/hr-read-reddit/agents/openai.yaml +2 -0
- package/skills/hr-read-reddit/scripts/reddit.ts +252 -0
- package/skills/hr-read-x-links/SKILL.md +42 -0
- package/skills/hr-read-x-links/agents/openai.yaml +2 -0
- package/skills/hr-read-x-links/scripts/x-post.ts +264 -0
- package/skills/hr-split-to-prs/LICENSE +21 -0
- package/skills/hr-split-to-prs/NOTICE.md +9 -0
- package/skills/hr-split-to-prs/SKILL.md +61 -0
- package/skills/hr-split-to-prs/agents/openai.yaml +2 -0
- package/skills/hr-stock-ui-audit/SKILL.md +132 -0
- package/skills/hr-stock-ui-audit/agents/openai.yaml +3 -0
- package/skills/hr-stock-ui-audit/scripts/scan.mjs +619 -0
- package/skills/hr-tdd/LICENSE +21 -0
- package/skills/hr-tdd/NOTICE.md +10 -0
- package/skills/hr-tdd/SKILL.md +40 -0
- package/skills/hr-tdd/agents/openai.yaml +3 -0
- package/skills/hr-tdd/mocking.md +39 -0
- package/skills/hr-tdd/tests.md +77 -0
- package/skills/hr-test-audit/SKILL.md +146 -0
- package/skills/hr-test-audit/agents/openai.yaml +3 -0
- package/skills/hr-test-audit/scripts/floor-guard.ts +137 -0
- package/skills/hr-what-next/SKILL.md +61 -0
- package/skills/hr-what-next/agents/openai.yaml +4 -0
- package/skills/hr-what-next/references/reporting.md +68 -0
- package/skills/hr-writing-for-agents/NOTICE.md +46 -0
- package/skills/hr-writing-for-agents/SKILL-MECHANICS.md +83 -0
- package/skills/hr-writing-for-agents/SKILL.md +108 -0
- package/skills/hr-writing-for-agents/agents/openai.yaml +3 -0
- package/skills/hr-writing-pr/NOTICE.md +48 -0
- package/skills/hr-writing-pr/SKILL.md +104 -0
- package/skills/hr-writing-pr/agents/openai.yaml +2 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Token naming
|
|
2
|
+
|
|
3
|
+
Naming is what makes a palette usable by anyone who did not build it. For which ramps exist and what each step does, see [palette-structure.md](palette-structure.md).
|
|
4
|
+
|
|
5
|
+
## Two tiers
|
|
6
|
+
|
|
7
|
+
**Primitives** name a value. They are the ramp, named by hue and step: `--blue-500`, `--neutral-200`. A primitive describes what the color _is_, so it never changes meaning between themes and is never applied directly in a component.
|
|
8
|
+
|
|
9
|
+
**Semantics** name a job. They point at a primitive and take the name of the role they fill: `--color-text-secondary`, `--color-border-subtle`. Components only ever reference this tier.
|
|
10
|
+
|
|
11
|
+
```css
|
|
12
|
+
:root {
|
|
13
|
+
/* Tier 1: primitives, named by appearance. Never used directly. */
|
|
14
|
+
--blue-500: #3b82f6;
|
|
15
|
+
--neutral-200: #e5e7eb;
|
|
16
|
+
--neutral-700: #374151;
|
|
17
|
+
|
|
18
|
+
/* Tier 2: semantics, named by role. This is what components use. */
|
|
19
|
+
--color-accent-solid: var(--blue-500);
|
|
20
|
+
--color-border: var(--neutral-200);
|
|
21
|
+
--color-text-secondary: var(--neutral-700);
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The tiering is what makes theming possible. Dark mode, a white-label theme and an increased-contrast variant all repoint the semantic tier, leaving the primitives and every component untouched. A codebase applying `--blue-500` directly in components has no theming seam. Adding one later means auditing every usage to work out which meant "the accent" and which just wanted blue.
|
|
26
|
+
|
|
27
|
+
Add a third, component-level tier (`--color-button-danger-bg`) only where a component genuinely and intentionally diverges from the system. One component token is a documented exception; twenty mean the semantic tier is missing roles.
|
|
28
|
+
|
|
29
|
+
## The role inventory
|
|
30
|
+
|
|
31
|
+
A system is complete when every role below has a token. Build against this list rather than adding tokens as components demand them, or the palette ends up shaped like whichever screen came first.
|
|
32
|
+
|
|
33
|
+
| Group | Roles |
|
|
34
|
+
| -------- | ----------------------------------------------------------------------------------------- |
|
|
35
|
+
| Surfaces | page background, surface, raised (menus, popovers), sunken (inputs, wells), overlay scrim |
|
|
36
|
+
| Text | primary, secondary, disabled, inverse, on-accent |
|
|
37
|
+
| Borders | subtle, default, strong, focus ring, separator |
|
|
38
|
+
| Accent | subtle background, border, solid, solid hover, text |
|
|
39
|
+
| Status | per status shipped: subtle background, border, solid, text |
|
|
40
|
+
|
|
41
|
+
Separator and border are separate roles even when they share a value today. A separator divides content; a border encloses a control. They diverge the first time someone restyles inputs, and a system that conflated them gets untangled at that moment.
|
|
42
|
+
|
|
43
|
+
## Naming grammar
|
|
44
|
+
|
|
45
|
+
Use one shape and never deviate: `--color-{role}-{variant}-{state}`.
|
|
46
|
+
|
|
47
|
+
```css
|
|
48
|
+
--color-bg-surface
|
|
49
|
+
--color-text-secondary
|
|
50
|
+
--color-border-strong
|
|
51
|
+
--color-accent-solid-hover
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Pick one word per concept and use only that word. Consistency matters more than the vocabulary. A reader who has seen `--color-text-primary` must be able to guess `--color-text-disabled` without looking:
|
|
55
|
+
|
|
56
|
+
| Concept | Pick one | Never mix in |
|
|
57
|
+
| ----------- | -------- | ------------------------------------------------ |
|
|
58
|
+
| Foreground | `text` | `fg`, `foreground`, `content`, `ink` |
|
|
59
|
+
| Background | `bg` | `background`, `surface` as a synonym, `fill` |
|
|
60
|
+
| Edge | `border` | `stroke`, `outline`, `line` |
|
|
61
|
+
| Brand color | `accent` | `primary`, `brand`, `theme` used interchangeably |
|
|
62
|
+
|
|
63
|
+
Reserve `primary` for exactly one meaning. `--color-text-primary` for body text beside `--color-primary` for the brand is the most common naming collision there is, and it makes every `primary` token ambiguous until you open its definition. Use `accent` for the brand and let `primary` mean "the most prominent of its group".
|
|
64
|
+
|
|
65
|
+
## Anti-patterns
|
|
66
|
+
|
|
67
|
+
| Name | Problem | Instead |
|
|
68
|
+
| -------------------------------- | ----------------------------------------------------------------------- | ------------------------------ |
|
|
69
|
+
| `--color-blue-button` | Appearance at the semantic tier; lies the moment the brand changes | `--color-accent-solid` |
|
|
70
|
+
| `--color-sidebar-gray` | Named for where it was used first; the second usage makes it nonsense | `--color-bg-surface` |
|
|
71
|
+
| `--color-light-gray` | Lies in dark mode, where it is the dark one | `--neutral-200` as a primitive |
|
|
72
|
+
| `--color-text-2` | Numbered semantics carry no meaning; nobody can guess what `3` would be | `--color-text-secondary` |
|
|
73
|
+
| `--color-gray-hover` | Mixes a hue with a state and belongs to no tier | `--color-bg-surface-hover` |
|
|
74
|
+
| `--blue-500` used in a component | Skips the semantic tier and removes the theming seam | Point a semantic token at it |
|
|
75
|
+
|
|
76
|
+
Every one of them is a case of **Use a token only in its role**. See [color-usage.md](color-usage.md).
|
|
77
|
+
|
|
78
|
+
## In Tailwind projects
|
|
79
|
+
|
|
80
|
+
Tailwind v4 generates utilities from `@theme`, so names declared there become the API. Declare primitives and semantics in the same block; the `--color-*` namespace is what produces `bg-*`, `text-*` and `border-*`:
|
|
81
|
+
|
|
82
|
+
```css
|
|
83
|
+
@theme {
|
|
84
|
+
/* Primitives */
|
|
85
|
+
--color-brand-50: #eff6ff;
|
|
86
|
+
--color-brand-500: #3b82f6;
|
|
87
|
+
--color-brand-900: #1e3a8a;
|
|
88
|
+
|
|
89
|
+
/* Semantics: what templates should use */
|
|
90
|
+
--color-accent-solid: var(--color-brand-500);
|
|
91
|
+
--color-text-secondary: var(--color-neutral-700);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
That yields `bg-accent-solid` and `text-text-secondary` alongside `bg-brand-500`, because Tailwind v4 appends everything after `--color-` to the utility prefix. Both are reachable, so the discipline is a convention rather than a constraint. Templates use the semantic utilities, and a raw `bg-brand-500` in a component is the thing to flag.
|
|
96
|
+
|
|
97
|
+
Opacity modifiers work on either tier, as in `bg-accent-solid/50`. But a color carrying alpha cannot be contrast-checked against a static background, because what it renders depends on what sits behind it. Use solid tokens for anything with text on it.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jakub Krehel
|
|
4
|
+
Copyright (c) 2026 Emil Kowalski
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Source notices
|
|
2
|
+
|
|
3
|
+
This notice accompanies the hr-better-layout bundle when copied on its own.
|
|
4
|
+
|
|
5
|
+
## jakubkrehel/skills
|
|
6
|
+
|
|
7
|
+
Source: https://github.com/jakubkrehel/skills (compared at revision `267330e1adfc66a718fb65fa6918c1f06d0a689e`)
|
|
8
|
+
|
|
9
|
+
- `skills/hr-better-accessibility/`, `skills/hr-better-colors/`, `skills/hr-better-layout/`,
|
|
10
|
+
`skills/hr-better-typography/` and `skills/hr-better-writing/`: adapted skill bundles,
|
|
11
|
+
including their supporting references and discovery metadata.
|
|
12
|
+
|
|
13
|
+
Copyright (c) 2026 Jakub Krehel
|
|
14
|
+
|
|
15
|
+
## emilkowalski/skills
|
|
16
|
+
|
|
17
|
+
Source: https://github.com/emilkowalski/skills (compared at revision `e8a175de22ae1e49370fc144c1f3bb9aeedf988d`)
|
|
18
|
+
|
|
19
|
+
- `skills/hr-better-accessibility/` and `skills/hr-better-layout/`: mobile behavior and
|
|
20
|
+
viewport guidance adapted from `skills/mobile-native/SKILL.md`.
|
|
21
|
+
- `skills/hr-better-layout/`, `skills/hr-better-typography/` and `skills/hr-better-writing/`:
|
|
22
|
+
clipping, truncation and plural-form guidance adapted from `skills/break-ui/SKILL.md`.
|
|
23
|
+
|
|
24
|
+
Copyright (c) 2026 Emil Kowalski
|
|
25
|
+
|
|
26
|
+
## house-rules and MIT permission notice
|
|
27
|
+
|
|
28
|
+
MIT License
|
|
29
|
+
|
|
30
|
+
Copyright (c) 2026 Aaron Queen
|
|
31
|
+
|
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
33
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
34
|
+
in the Software without restriction, including without limitation the rights
|
|
35
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
36
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
37
|
+
furnished to do so, subject to the following conditions:
|
|
38
|
+
|
|
39
|
+
The above copyright notice and this permission notice shall be included in all
|
|
40
|
+
copies or substantial portions of the Software.
|
|
41
|
+
|
|
42
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
43
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
44
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
45
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
46
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
47
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
48
|
+
SOFTWARE.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hr-better-layout
|
|
3
|
+
description: "Layout rules for web UI: grouping, alignment, spacing, reading order, responsive breakpoints, logical properties and clipping at narrow widths. Use when building or reviewing page or component layout CSS."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
upstream: https://github.com/jakubkrehel/skills
|
|
7
|
+
upstream_ref: 267330e1adfc66a718fb65fa6918c1f06d0a689e
|
|
8
|
+
upstream_path: skills/better-layout/
|
|
9
|
+
local_patches: "Use-when description; peek-scroller arithmetic; better-ui handoff dropped (not vendored); dvh/svh, viewport-fit, theme-color and row-squeeze rules (from emilkowalski/skills)"
|
|
10
|
+
house_rules_patches: "Portable provenance and review verdict"
|
|
11
|
+
mobile_native_source: https://github.com/emilkowalski/skills/blob/e8a175de22ae1e49370fc144c1f3bb9aeedf988d/skills/mobile-native/SKILL.md
|
|
12
|
+
break_ui_source: https://github.com/emilkowalski/skills/blob/e8a175de22ae1e49370fc144c1f3bb9aeedf988d/skills/break-ui/SKILL.md
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Layout
|
|
16
|
+
|
|
17
|
+
Position, spacing and alignment carry hierarchy before a word is read. This skill builds that structure and stress-tests it: resize it, translate it, mirror it for RTL.
|
|
18
|
+
|
|
19
|
+
Write every fix in the project's styling system. The numbers below are starting points for interfaces with no established density system, and where one applies, use it as written rather than a familiar-looking substitute. Keep deliberate platform chrome, compact professional tools and project tokens where they still pass the stress tests.
|
|
20
|
+
|
|
21
|
+
Hit areas and focus behavior belong to `hr-better-accessibility`. Radius, shadows and animation are outside this skill. Line length and text spacing belong to `hr-better-typography`.
|
|
22
|
+
|
|
23
|
+
## Group with space, not lines
|
|
24
|
+
|
|
25
|
+
Space groups first, background shapes second, separator lines last and only where space alone can't carry the structure. The gap between groups must be at least 2× the gap within one (`8px` intra-group to `16px`+ inter-group), or the grouping reads as noise. Alignment edges and importance ordering are in [grouping-and-alignment.md](grouping-and-alignment.md).
|
|
26
|
+
|
|
27
|
+
## Keep controls distinct from content
|
|
28
|
+
|
|
29
|
+
Give every interactive element a background shape, a border, or a consistent placement zone. A control styled like the static text beside it does not read as a control.
|
|
30
|
+
|
|
31
|
+
## Align to shared edges
|
|
32
|
+
|
|
33
|
+
Pick alignment edges and stick to them; every stray edge reads as noise. Use one project spacing step per level of subordination, where `16px` is a useful default.
|
|
34
|
+
|
|
35
|
+
Use logical properties for direction-dependent layout: `padding-inline-start`, `margin-inline-end`. Reserve physical left and right for genuinely physical geometry.
|
|
36
|
+
|
|
37
|
+
## Order by importance
|
|
38
|
+
|
|
39
|
+
The most important content sits near the top and the leading edge. Reading order flows top-to-bottom, leading-to-trailing. Think in leading and trailing, not left and right.
|
|
40
|
+
|
|
41
|
+
## Hint at hidden content
|
|
42
|
+
|
|
43
|
+
Progressive disclosure needs a visible affordance. Use the project's established cue, or let the next item peek `16–32px` past the scroll edge, or show a disclosure control. Content hidden with zero cue may as well not exist.
|
|
44
|
+
|
|
45
|
+
## Breathing room between targets
|
|
46
|
+
|
|
47
|
+
Without an established density system, start with `12px` between adjacent bordered or filled controls and `24px` around borderless text- and icon-only ones. Compact layouts may use less, as long as `hr-better-accessibility` hit areas don't overlap and the controls stay distinct. Layout margins and breakpoint recipes are in [spacing-and-adaptivity.md](spacing-and-adaptivity.md).
|
|
48
|
+
|
|
49
|
+
## Inset buttons from the edges
|
|
50
|
+
|
|
51
|
+
In content layouts, keep full-width buttons inside the layout margins with a visible radius, starting near `16px` inline on mobile. Edge-to-edge actions work when they follow established platform chrome, account for safe areas and stay distinguishable from system UI.
|
|
52
|
+
|
|
53
|
+
## Content bleeds, controls float
|
|
54
|
+
|
|
55
|
+
Backgrounds and media extend to the viewport edges. Controls and text stay inside the layout margins and safe areas (`env(safe-area-inset-*)`). Sticky chrome floats above the content layer rather than blocking it.
|
|
56
|
+
|
|
57
|
+
## Hold structure until it breaks
|
|
58
|
+
|
|
59
|
+
Breakpoints come from the content, not device presets. Keep the expanded layout as long as it genuinely fits and collapse late. Prefer container queries for component-level adaptation, and test the smallest and largest sizes first.
|
|
60
|
+
|
|
61
|
+
## Plan for growth and clipping
|
|
62
|
+
|
|
63
|
+
Translated strings grow, and short ones grow proportionally more, so a one-word button label is the riskiest thing on the screen. Put no fixed width or height on a text container, and let rows wrap. Test with pseudo-localization and one representative locale rather than budgeting a percentage.
|
|
64
|
+
|
|
65
|
+
Never park a critical action where resizing or scrolling clips it. Keep it in the normal flow, or in stable chrome suited to the product.
|
|
66
|
+
|
|
67
|
+
## Before you finish
|
|
68
|
+
|
|
69
|
+
| Mistake | Fix |
|
|
70
|
+
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
71
|
+
| `margin-left` / `padding-right` in a localizable layout | `margin-inline-start` / `padding-inline-end` |
|
|
72
|
+
| Content-layout button touches the viewport edge | Inset within the project margins; keep intentional platform chrome |
|
|
73
|
+
| Breakpoints at 768/1024 because they're the defaults | Break where the content actually stops fitting |
|
|
74
|
+
| Fixed-width text container sized to one language | `max-width` and wrapping; test pseudo-localization |
|
|
75
|
+
| Primary action at the clip-prone bottom of a pane | Sticky positioning or stable chrome with safe-area padding |
|
|
76
|
+
| `100vh` app shell or hero on mobile | `100dvh` for a shell that tracks the browser chrome, `100svh` for a first screen |
|
|
77
|
+
| Long text pushes a row's trailing action off-screen or squashes its avatar | `min-width: 0` on the text column, `flex-shrink: 0` on fixed-size items |
|
|
78
|
+
|
|
79
|
+
## Reporting
|
|
80
|
+
|
|
81
|
+
**Severity.** `HIGH` blocks content or an action at a supported viewport. `MEDIUM` harms hierarchy, reading order, or adaptability. `LOW` is isolated alignment or spacing polish.
|
|
82
|
+
|
|
83
|
+
**Verification.** Without a browser: logical properties in place of physical ones, container and media queries against the supported viewport list and DOM order against the intended reading order. With one: every supported width, 200% zoom and the RTL mirror. Report every check you could not run as `Not verified`.
|
|
84
|
+
|
|
85
|
+
**Format.** Group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in:
|
|
86
|
+
|
|
87
|
+
| Severity | Location | Before | After | Why |
|
|
88
|
+
| -------- | -------- | ------ | ----- | --- |
|
|
89
|
+
|
|
90
|
+
`Location` is `path/to/file:line`. `Why` names the principle and the user impact.
|
|
91
|
+
|
|
92
|
+
Report `Block` when any `HIGH` remains, `Approve` otherwise, leaving the rest in the table as work to do. Never `Approve` coverage you did not inspect. With nothing to report, state "No actionable layout findings" and report verification.
|
|
@@ -0,0 +1,144 @@
|
|
|
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 {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: 8px;
|
|
21
|
+
}
|
|
22
|
+
.form {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: 24px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Bad: uniform spacing plus lines to compensate */
|
|
29
|
+
.form > * {
|
|
30
|
+
margin-bottom: 12px;
|
|
31
|
+
border-bottom: 1px solid var(--separator);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<!-- Good: Tailwind -->
|
|
37
|
+
<div class="space-y-6">
|
|
38
|
+
<div class="space-y-2">…field group…</div>
|
|
39
|
+
<div class="space-y-2">…field group…</div>
|
|
40
|
+
</div>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Where a separator is genuinely needed, keep it quiet: hairline width, low contrast, never combined with a large gap that already did the job.
|
|
44
|
+
|
|
45
|
+
## Keep controls distinct from content
|
|
46
|
+
|
|
47
|
+
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.
|
|
48
|
+
|
|
49
|
+
```html
|
|
50
|
+
<!-- Bad: action looks exactly like the description text next to it -->
|
|
51
|
+
<p class="text-zinc-600">Your trial ends soon. Upgrade now</p>
|
|
52
|
+
|
|
53
|
+
<!-- Good: the action reads as an action -->
|
|
54
|
+
<p class="text-zinc-600">Your trial ends soon.</p>
|
|
55
|
+
<button class="font-medium text-blue-600">Upgrade now</button>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The inverse holds too. A non-clickable badge shaped exactly like the buttons beside it collects dead clicks.
|
|
59
|
+
|
|
60
|
+
## Align to shared edges
|
|
61
|
+
|
|
62
|
+
Pick a small set of alignment edges and put everything on them, because the eye tracks straight edges to scan content.
|
|
63
|
+
|
|
64
|
+
- 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.
|
|
65
|
+
- Use one project spacing step to express hierarchy. `16px` is a useful default where no scale exists, and deeper nesting repeats the same step.
|
|
66
|
+
- Numbers in tables align to the trailing edge, text to the leading edge. Tabular figures are `hr-better-typography`'s.
|
|
67
|
+
|
|
68
|
+
```css
|
|
69
|
+
/* Good: one shared leading edge, one indent step */
|
|
70
|
+
.section {
|
|
71
|
+
padding-inline: 24px;
|
|
72
|
+
}
|
|
73
|
+
.section .child {
|
|
74
|
+
margin-inline-start: 16px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Bad: three unrelated leading edges in one column */
|
|
78
|
+
.header {
|
|
79
|
+
padding-inline-start: 20px;
|
|
80
|
+
}
|
|
81
|
+
.list-item {
|
|
82
|
+
padding-inline-start: 14px;
|
|
83
|
+
}
|
|
84
|
+
.footer {
|
|
85
|
+
padding-inline-start: 24px;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Logical properties, not physical
|
|
90
|
+
|
|
91
|
+
Express direction-dependent horizontal position as leading/trailing so the layout mirrors automatically under `dir="rtl"`:
|
|
92
|
+
|
|
93
|
+
| Physical (avoid) | Logical (use) |
|
|
94
|
+
| ------------------ | ----------------------- |
|
|
95
|
+
| `margin-left` | `margin-inline-start` |
|
|
96
|
+
| `padding-right` | `padding-inline-end` |
|
|
97
|
+
| `left: 0` | `inset-inline-start: 0` |
|
|
98
|
+
| `text-align: left` | `text-align: start` |
|
|
99
|
+
| `border-right` | `border-inline-end` |
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<!-- Good: Tailwind logical utilities -->
|
|
103
|
+
<div class="ms-4 pe-6 text-start">…</div>
|
|
104
|
+
|
|
105
|
+
<!-- Bad: breaks in RTL -->
|
|
106
|
+
<div class="ml-4 pr-6 text-left">…</div>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
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.
|
|
110
|
+
|
|
111
|
+
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 `hr-better-typography`.
|
|
112
|
+
|
|
113
|
+
## Order by importance
|
|
114
|
+
|
|
115
|
+
Readers scan top-to-bottom and leading-to-trailing. Place content accordingly:
|
|
116
|
+
|
|
117
|
+
- The most important information sits near the top and the leading edge. The further down and trailing something sits, the less attention it gets.
|
|
118
|
+
- 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.
|
|
119
|
+
- Within a row, identifying content leads and metadata and actions trail.
|
|
120
|
+
|
|
121
|
+
```html
|
|
122
|
+
<!-- Good: primary fact first, detail demoted -->
|
|
123
|
+
<div>
|
|
124
|
+
<p class="text-2xl font-semibold">$4,320.00</p>
|
|
125
|
+
<p class="text-sm text-zinc-500">Available balance</p>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<!-- Bad: the key fact is buried below the fold of the card -->
|
|
129
|
+
<div>
|
|
130
|
+
<p class="text-sm">Account 4402 · Opened 2019 · Standard tier</p>
|
|
131
|
+
<p class="text-sm">Last statement: June 30</p>
|
|
132
|
+
<p class="text-sm">Balance: $4,320.00</p>
|
|
133
|
+
</div>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
With logical properties, the same hierarchy mirrors correctly in RTL locales.
|
|
137
|
+
|
|
138
|
+
## Don't overload the entry point
|
|
139
|
+
|
|
140
|
+
The first screenful is a table of contents, not the whole book. If everything is prominent, nothing is:
|
|
141
|
+
|
|
142
|
+
- One primary action per view. `hr-better-colors` owns how color enforces it.
|
|
143
|
+
- Group secondary actions behind a menu once they exceed two or three.
|
|
144
|
+
- Prefer a short view that links deeper over a long view that shows everything at level one.
|
|
@@ -0,0 +1,215 @@
|
|
|
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 `hr-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 {
|
|
45
|
+
width: 100%;
|
|
46
|
+
border-radius: 12px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Bad: button glued to all three edges */
|
|
50
|
+
.action-bar button {
|
|
51
|
+
width: 100vw;
|
|
52
|
+
border-radius: 0;
|
|
53
|
+
position: fixed;
|
|
54
|
+
bottom: 0;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
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.
|
|
59
|
+
|
|
60
|
+
## Progressive disclosure needs an affordance
|
|
61
|
+
|
|
62
|
+
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:
|
|
63
|
+
|
|
64
|
+
- **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.
|
|
65
|
+
- **Disclosure controls.** Collapsed sections get a chevron or "Show more", labelled with what is hidden: "Show 12 more results", not "More".
|
|
66
|
+
- **Truncation cues.** Clamped text shows an ellipsis and a way to expand. Truncation mechanics are `hr-better-typography`'s.
|
|
67
|
+
|
|
68
|
+
In the peeking-scroller recipe, the container's padding creates the peek and snap points stay on the content edge. A percentage basis resolves against the content box, which already excludes that padding, so each item subtracts only the gap and the next item shows through the `24px` inline padding.
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
.scroller {
|
|
72
|
+
display: flex;
|
|
73
|
+
gap: 12px;
|
|
74
|
+
overflow-x: auto;
|
|
75
|
+
padding-inline: 24px;
|
|
76
|
+
scroll-padding-inline: 24px;
|
|
77
|
+
scroll-snap-type: x mandatory;
|
|
78
|
+
}
|
|
79
|
+
.scroller > * {
|
|
80
|
+
flex: 0 0 calc(100% - 12px); /* content box minus the gap; the peek is the 24px padding */
|
|
81
|
+
scroll-snap-align: start;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<!-- Tailwind: the content box minus the gap leaves the next card's leading 24px visible -->
|
|
87
|
+
<div class="flex gap-3 overflow-x-auto px-6 [scroll-padding-inline:1.5rem] snap-x snap-mandatory">
|
|
88
|
+
<div class="w-[calc(100%-0.75rem)] shrink-0 snap-start">…</div>
|
|
89
|
+
<div class="w-[calc(100%-0.75rem)] shrink-0 snap-start">…</div>
|
|
90
|
+
</div>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Content bleeds, controls float
|
|
94
|
+
|
|
95
|
+
The two layers behave differently at the edges:
|
|
96
|
+
|
|
97
|
+
- **Content layer**: backgrounds, hero media and scrollable lists extend to the viewport edges.
|
|
98
|
+
- **Control layer**: text and controls stay inside the layout margins and safe areas, floating above the content.
|
|
99
|
+
|
|
100
|
+
```css
|
|
101
|
+
/* Good: full-bleed media inside a constrained article */
|
|
102
|
+
.article {
|
|
103
|
+
display: grid;
|
|
104
|
+
grid-template-columns: 1fr min(65ch, calc(100% - 48px)) 1fr;
|
|
105
|
+
}
|
|
106
|
+
.article > * {
|
|
107
|
+
grid-column: 2;
|
|
108
|
+
}
|
|
109
|
+
.article > .full-bleed {
|
|
110
|
+
grid-column: 1 / -1;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Sticky headers and floating action buttons account for safe areas:
|
|
115
|
+
|
|
116
|
+
```css
|
|
117
|
+
.fab {
|
|
118
|
+
position: fixed;
|
|
119
|
+
inset-inline-end: calc(16px + env(safe-area-inset-right));
|
|
120
|
+
bottom: calc(16px + env(safe-area-inset-bottom));
|
|
121
|
+
}
|
|
122
|
+
.fab:dir(rtl) {
|
|
123
|
+
inset-inline-end: calc(16px + env(safe-area-inset-left));
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`env(safe-area-inset-*)` reads `0px` unless the viewport meta includes `viewport-fit=cover`.
|
|
128
|
+
|
|
129
|
+
Full-height layouts pick their viewport unit by job. On mobile `100vh` is the largest viewport, measured with the URL bar collapsed, so on load it overflows and hides a bottom-pinned action under the browser chrome.
|
|
130
|
+
|
|
131
|
+
```css
|
|
132
|
+
/* App shell, sheet or drawer: tracks the visible area as the chrome shows and hides */
|
|
133
|
+
.app {
|
|
134
|
+
height: 100dvh;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* First screen or hero: the smallest viewport, so nothing is cut off and nothing shifts mid-scroll */
|
|
138
|
+
.hero {
|
|
139
|
+
min-height: 100svh;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Match `theme-color` to the color at the top of the page, once per color scheme.
|
|
144
|
+
It suggests browser chrome colors in supporting browsers; verify the effect on supported platforms:
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff" />
|
|
148
|
+
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0a0a0a" />
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
A theme switched by class rather than the OS setting updates the tag from script on toggle.
|
|
152
|
+
|
|
153
|
+
## Hold structure until it breaks
|
|
154
|
+
|
|
155
|
+
Breakpoints belong to the content, not the device catalog:
|
|
156
|
+
|
|
157
|
+
- 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.
|
|
158
|
+
- 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.
|
|
159
|
+
- Prefer **container queries** for components. A card adapts to the column it is in, not to the viewport.
|
|
160
|
+
|
|
161
|
+
```css
|
|
162
|
+
/* Good: component adapts to its container */
|
|
163
|
+
.card-list {
|
|
164
|
+
container-type: inline-size;
|
|
165
|
+
}
|
|
166
|
+
@container (max-width: 400px) {
|
|
167
|
+
.card {
|
|
168
|
+
grid-template-columns: 1fr;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Bad: viewport media query breaks the card inside a narrow sidebar */
|
|
173
|
+
@media (max-width: 768px) {
|
|
174
|
+
.card {
|
|
175
|
+
grid-template-columns: 1fr;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Test the smallest and largest supported sizes first, since those break first, then the sizes between.
|
|
181
|
+
|
|
182
|
+
## Plan for growth and clipping
|
|
183
|
+
|
|
184
|
+
Layouts fail in two directions. Content grows, and viewports shrink.
|
|
185
|
+
|
|
186
|
+
**String expansion varies by language and by source-string length.** Never rely on one universal percentage.
|
|
187
|
+
|
|
188
|
+
- No fixed widths sized to English labels. Use `max-width` plus wrapping.
|
|
189
|
+
- No fixed heights on text containers. Use `min-height` where a floor is needed.
|
|
190
|
+
- Buttons size themselves from their label (`padding-inline`), never a hardcoded width.
|
|
191
|
+
- Test with pseudo-localization or a long-string locale before shipping.
|
|
192
|
+
|
|
193
|
+
**Rows squeeze from the middle.** In a row of avatar, text and trailing action, long text pushes the action off the row unless the text column may shrink below its content width:
|
|
194
|
+
|
|
195
|
+
- `min-width: 0` on the flex text column, or `minmax(0, 1fr)` for a grid track. The default `min-width: auto` refuses to shrink.
|
|
196
|
+
- `flex-shrink: 0` on avatars, icons, badges and trailing actions, so they keep their size instead of squashing into ovals.
|
|
197
|
+
- Top-align the row (`align-items: flex-start`) once the text can wrap to several lines.
|
|
198
|
+
|
|
199
|
+
```css
|
|
200
|
+
/* Good: label defines the size */
|
|
201
|
+
.button {
|
|
202
|
+
padding-inline: 16px;
|
|
203
|
+
max-inline-size: 100%;
|
|
204
|
+
white-space: normal;
|
|
205
|
+
overflow-wrap: anywhere;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Bad: German will overflow or truncate */
|
|
209
|
+
.button {
|
|
210
|
+
width: 96px;
|
|
211
|
+
overflow: hidden;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**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,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jakub Krehel
|
|
4
|
+
Copyright (c) 2026 Emil Kowalski
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|