ariadna 1.3.1 → 2.0.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.
- checksums.yaml +4 -4
- data/ariadna.gemspec +0 -1
- data/data/agents/ariadna-codebase-mapper.md +34 -722
- data/data/agents/ariadna-debugger.md +44 -1139
- data/data/agents/ariadna-executor.md +75 -396
- data/data/agents/ariadna-planner.md +78 -1215
- data/data/agents/ariadna-roadmapper.md +55 -582
- data/data/agents/ariadna-verifier.md +60 -702
- data/data/ariadna/templates/config.json +8 -33
- data/data/ariadna/workflows/debug.md +28 -0
- data/data/ariadna/workflows/execute-phase.md +31 -513
- data/data/ariadna/workflows/map-codebase.md +20 -319
- data/data/ariadna/workflows/new-milestone.md +20 -365
- data/data/ariadna/workflows/new-project.md +19 -880
- data/data/ariadna/workflows/plan-phase.md +24 -443
- data/data/ariadna/workflows/progress.md +20 -376
- data/data/ariadna/workflows/quick.md +19 -221
- data/data/ariadna/workflows/roadmap-ops.md +28 -0
- data/data/ariadna/workflows/verify-work.md +23 -560
- data/data/commands/ariadna/add-phase.md +11 -22
- data/data/commands/ariadna/debug.md +11 -143
- data/data/commands/ariadna/execute-phase.md +12 -30
- data/data/commands/ariadna/insert-phase.md +7 -14
- data/data/commands/ariadna/map-codebase.md +16 -49
- data/data/commands/ariadna/new-milestone.md +12 -25
- data/data/commands/ariadna/new-project.md +22 -26
- data/data/commands/ariadna/plan-phase.md +13 -22
- data/data/commands/ariadna/progress.md +16 -6
- data/data/commands/ariadna/quick.md +9 -11
- data/data/commands/ariadna/remove-phase.md +9 -12
- data/data/commands/ariadna/verify-work.md +14 -19
- data/data/skills/rails-backend/API.md +138 -0
- data/data/skills/rails-backend/CONTROLLERS.md +154 -0
- data/data/skills/rails-backend/JOBS.md +132 -0
- data/data/skills/rails-backend/MODELS.md +213 -0
- data/data/skills/rails-backend/SKILL.md +169 -0
- data/data/skills/rails-frontend/ASSETS.md +154 -0
- data/data/skills/rails-frontend/COMPONENTS.md +253 -0
- data/data/skills/rails-frontend/SKILL.md +187 -0
- data/data/skills/rails-frontend/VIEWS.md +168 -0
- data/data/skills/rails-performance/PROFILING.md +106 -0
- data/data/skills/rails-performance/SKILL.md +217 -0
- data/data/skills/rails-security/AUDIT.md +118 -0
- data/data/skills/rails-security/SKILL.md +422 -0
- data/data/skills/rails-testing/FIXTURES.md +78 -0
- data/data/skills/rails-testing/SKILL.md +160 -0
- data/data/skills/rails-testing/SYSTEM-TESTS.md +73 -0
- data/lib/ariadna/installer.rb +11 -15
- data/lib/ariadna/tools/cli.rb +0 -12
- data/lib/ariadna/tools/config_manager.rb +10 -72
- data/lib/ariadna/tools/frontmatter.rb +23 -1
- data/lib/ariadna/tools/init.rb +201 -401
- data/lib/ariadna/tools/model_profiles.rb +6 -14
- data/lib/ariadna/tools/phase_manager.rb +1 -10
- data/lib/ariadna/tools/state_manager.rb +170 -451
- data/lib/ariadna/tools/template_filler.rb +4 -12
- data/lib/ariadna/tools/verification.rb +21 -399
- data/lib/ariadna/uninstaller.rb +9 -0
- data/lib/ariadna/version.rb +1 -1
- metadata +20 -91
- data/data/agents/ariadna-backend-executor.md +0 -261
- data/data/agents/ariadna-frontend-executor.md +0 -259
- data/data/agents/ariadna-integration-checker.md +0 -418
- data/data/agents/ariadna-phase-researcher.md +0 -469
- data/data/agents/ariadna-plan-checker.md +0 -622
- data/data/agents/ariadna-project-researcher.md +0 -618
- data/data/agents/ariadna-research-synthesizer.md +0 -236
- data/data/agents/ariadna-test-executor.md +0 -266
- data/data/ariadna/references/checkpoints.md +0 -772
- data/data/ariadna/references/continuation-format.md +0 -249
- data/data/ariadna/references/decimal-phase-calculation.md +0 -65
- data/data/ariadna/references/git-integration.md +0 -248
- data/data/ariadna/references/git-planning-commit.md +0 -38
- data/data/ariadna/references/model-profile-resolution.md +0 -32
- data/data/ariadna/references/model-profiles.md +0 -73
- data/data/ariadna/references/phase-argument-parsing.md +0 -61
- data/data/ariadna/references/planning-config.md +0 -194
- data/data/ariadna/references/questioning.md +0 -153
- data/data/ariadna/references/rails-conventions.md +0 -416
- data/data/ariadna/references/tdd.md +0 -267
- data/data/ariadna/references/ui-brand.md +0 -160
- data/data/ariadna/references/verification-patterns.md +0 -853
- data/data/ariadna/templates/codebase/architecture.md +0 -481
- data/data/ariadna/templates/codebase/concerns.md +0 -380
- data/data/ariadna/templates/codebase/conventions.md +0 -434
- data/data/ariadna/templates/codebase/integrations.md +0 -328
- data/data/ariadna/templates/codebase/stack.md +0 -189
- data/data/ariadna/templates/codebase/structure.md +0 -418
- data/data/ariadna/templates/codebase/testing.md +0 -606
- data/data/ariadna/templates/context.md +0 -283
- data/data/ariadna/templates/continue-here.md +0 -78
- data/data/ariadna/templates/debug-subagent-prompt.md +0 -91
- data/data/ariadna/templates/phase-prompt.md +0 -609
- data/data/ariadna/templates/planner-subagent-prompt.md +0 -117
- data/data/ariadna/templates/research-project/ARCHITECTURE.md +0 -439
- data/data/ariadna/templates/research-project/FEATURES.md +0 -168
- data/data/ariadna/templates/research-project/PITFALLS.md +0 -406
- data/data/ariadna/templates/research-project/STACK.md +0 -251
- data/data/ariadna/templates/research-project/SUMMARY.md +0 -247
- data/data/ariadna/templates/state.md +0 -176
- data/data/ariadna/templates/summary-complex.md +0 -59
- data/data/ariadna/templates/summary-minimal.md +0 -41
- data/data/ariadna/templates/summary-standard.md +0 -48
- data/data/ariadna/templates/user-setup.md +0 -310
- data/data/ariadna/workflows/add-phase.md +0 -111
- data/data/ariadna/workflows/add-todo.md +0 -157
- data/data/ariadna/workflows/audit-milestone.md +0 -241
- data/data/ariadna/workflows/check-todos.md +0 -176
- data/data/ariadna/workflows/complete-milestone.md +0 -644
- data/data/ariadna/workflows/diagnose-issues.md +0 -219
- data/data/ariadna/workflows/discovery-phase.md +0 -289
- data/data/ariadna/workflows/discuss-phase.md +0 -408
- data/data/ariadna/workflows/execute-plan.md +0 -448
- data/data/ariadna/workflows/help.md +0 -470
- data/data/ariadna/workflows/insert-phase.md +0 -129
- data/data/ariadna/workflows/list-phase-assumptions.md +0 -178
- data/data/ariadna/workflows/pause-work.md +0 -122
- data/data/ariadna/workflows/plan-milestone-gaps.md +0 -256
- data/data/ariadna/workflows/remove-phase.md +0 -154
- data/data/ariadna/workflows/research-phase.md +0 -74
- data/data/ariadna/workflows/resume-project.md +0 -306
- data/data/ariadna/workflows/set-profile.md +0 -80
- data/data/ariadna/workflows/settings.md +0 -145
- data/data/ariadna/workflows/transition.md +0 -493
- data/data/ariadna/workflows/update.md +0 -212
- data/data/ariadna/workflows/verify-phase.md +0 -226
- data/data/commands/ariadna/add-todo.md +0 -42
- data/data/commands/ariadna/audit-milestone.md +0 -42
- data/data/commands/ariadna/check-todos.md +0 -41
- data/data/commands/ariadna/complete-milestone.md +0 -136
- data/data/commands/ariadna/discuss-phase.md +0 -86
- data/data/commands/ariadna/help.md +0 -22
- data/data/commands/ariadna/list-phase-assumptions.md +0 -50
- data/data/commands/ariadna/pause-work.md +0 -35
- data/data/commands/ariadna/plan-milestone-gaps.md +0 -40
- data/data/commands/ariadna/reapply-patches.md +0 -110
- data/data/commands/ariadna/research-phase.md +0 -187
- data/data/commands/ariadna/resume-work.md +0 -40
- data/data/commands/ariadna/set-profile.md +0 -34
- data/data/commands/ariadna/settings.md +0 -36
- data/data/commands/ariadna/update.md +0 -37
- data/data/guides/backend.md +0 -3069
- data/data/guides/frontend.md +0 -1479
- data/data/guides/performance.md +0 -1193
- data/data/guides/security.md +0 -1522
- data/data/guides/style-guide.md +0 -1091
- data/data/guides/testing.md +0 -504
- data/data/templates.md +0 -94
data/data/guides/style-guide.md
DELETED
|
@@ -1,1091 +0,0 @@
|
|
|
1
|
-
# Style Guide
|
|
2
|
-
|
|
3
|
-
This document defines the CSS architecture, design tokens, and styling conventions.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
Use **pure, custom CSS** with no external frameworks (no Tailwind, Bootstrap, etc.). The design system is built on modern CSS features including CSS custom properties, OKLCH colors, CSS layers, and logical properties.
|
|
8
|
-
|
|
9
|
-
## CSS Architecture
|
|
10
|
-
|
|
11
|
-
### Layer Organization
|
|
12
|
-
|
|
13
|
-
Styles are organized using CSS `@layer` rules for predictable specificity:
|
|
14
|
-
|
|
15
|
-
```css
|
|
16
|
-
@layer reset; /* Browser normalization */
|
|
17
|
-
@layer base; /* Base element styles */
|
|
18
|
-
@layer components; /* Component-specific styles */
|
|
19
|
-
@layer modules; /* Feature modules */
|
|
20
|
-
@layer utilities; /* Utility classes */
|
|
21
|
-
@layer native; /* Native app overrides */
|
|
22
|
-
@layer platform; /* Platform-specific styles */
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### File Organization
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
app/assets/stylesheets/
|
|
29
|
-
├── _global.css # Root variables and @layer definitions
|
|
30
|
-
├── base.css # Base element styling
|
|
31
|
-
├── utilities.css # Utility classes
|
|
32
|
-
├── buttons.css # Button components
|
|
33
|
-
├── inputs.css # Form inputs
|
|
34
|
-
├── cards.css # Card components
|
|
35
|
-
├── layout.css # Main layout
|
|
36
|
-
├── header.css # Header component
|
|
37
|
-
├── dialog.css # Dialogs/modals
|
|
38
|
-
├── animation.css # Keyframes
|
|
39
|
-
└── [feature].css # Feature-specific styles
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Color System
|
|
45
|
-
|
|
46
|
-
### OKLCH Color Model
|
|
47
|
-
|
|
48
|
-
Use the OKLCH color space for better perceptual uniformity across light and dark modes. Colors are defined as lightness, chroma, and hue values:
|
|
49
|
-
|
|
50
|
-
```css
|
|
51
|
-
--lch-black: 0% 0 0
|
|
52
|
-
--lch-white: 100% 0 0
|
|
53
|
-
--color-ink: oklch(var(--lch-black))
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Semantic Colors
|
|
57
|
-
|
|
58
|
-
Use semantic color variables rather than raw values:
|
|
59
|
-
|
|
60
|
-
| Variable | Purpose |
|
|
61
|
-
|----------|---------|
|
|
62
|
-
| `--color-ink` | Primary text color |
|
|
63
|
-
| `--color-ink-light` | Secondary text |
|
|
64
|
-
| `--color-ink-lighter` | Tertiary/muted text |
|
|
65
|
-
| `--color-canvas` | Background color |
|
|
66
|
-
| `--color-link` | Interactive elements (blue) |
|
|
67
|
-
| `--color-positive` | Success states (green) |
|
|
68
|
-
| `--color-negative` | Error states (red) |
|
|
69
|
-
| `--color-highlight` | Emphasis/markers (yellow) |
|
|
70
|
-
|
|
71
|
-
### Color Palette
|
|
72
|
-
|
|
73
|
-
The palette includes 10 color families, each with 7 intensity levels:
|
|
74
|
-
|
|
75
|
-
- **Ink** (neutrals): `--lch-ink-1` through `--lch-ink-8`
|
|
76
|
-
- **Red, Yellow, Lime, Green, Aqua, Blue, Violet, Purple, Pink**: `--lch-[color]-1` through `--lch-[color]-7`
|
|
77
|
-
|
|
78
|
-
### Card Colors
|
|
79
|
-
|
|
80
|
-
Cards use 8 distinct colors for visual categorization:
|
|
81
|
-
|
|
82
|
-
```css
|
|
83
|
-
--color-card-1 through --color-card-8
|
|
84
|
-
--color-card-default
|
|
85
|
-
--color-card-complete
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Card backgrounds use color mixing for subtlety:
|
|
89
|
-
|
|
90
|
-
```css
|
|
91
|
-
background: color-mix(in srgb, var(--card-color) 4%, var(--color-canvas));
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## Typography
|
|
97
|
-
|
|
98
|
-
### Font Stack
|
|
99
|
-
|
|
100
|
-
```css
|
|
101
|
-
--font-sans: "Adwaita Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
102
|
-
"Noto Sans", Helvetica, Arial, sans-serif,
|
|
103
|
-
"Apple Color Emoji", "Segoe UI Emoji";
|
|
104
|
-
--font-serif: ui-serif, serif;
|
|
105
|
-
--font-mono: ui-monospace, monospace;
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Text Size Scale
|
|
109
|
-
|
|
110
|
-
| Variable | Desktop | Mobile |
|
|
111
|
-
|----------|---------|--------|
|
|
112
|
-
| `--text-xx-small` | 0.55rem | 0.65rem |
|
|
113
|
-
| `--text-x-small` | 0.75rem | 0.85rem |
|
|
114
|
-
| `--text-small` | 0.85rem | 0.95rem |
|
|
115
|
-
| `--text-normal` | 1rem | 1.1rem |
|
|
116
|
-
| `--text-medium` | 1.1rem | 1.2rem |
|
|
117
|
-
| `--text-large` | 1.5rem | 1.5rem |
|
|
118
|
-
| `--text-x-large` | 1.8rem | 1.8rem |
|
|
119
|
-
| `--text-xx-large` | 2.5rem | 2.5rem |
|
|
120
|
-
|
|
121
|
-
### Typography Utilities
|
|
122
|
-
|
|
123
|
-
```css
|
|
124
|
-
.txt-xx-small, .txt-x-small, .txt-small, .txt-normal, .txt-medium, .txt-large
|
|
125
|
-
.txt-ink, .txt-subtle, .txt-negative, .txt-positive, .txt-alert
|
|
126
|
-
.txt-tight-lines /* Reduced line-height */
|
|
127
|
-
.font-weight-black /* 900 */
|
|
128
|
-
.font-weight-normal /* 400 */
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Font Rendering
|
|
132
|
-
|
|
133
|
-
Global settings for consistent rendering:
|
|
134
|
-
|
|
135
|
-
```css
|
|
136
|
-
-webkit-font-smoothing: antialiased;
|
|
137
|
-
-moz-osx-font-smoothing: grayscale;
|
|
138
|
-
text-rendering: optimizeLegibility;
|
|
139
|
-
line-height: 1.375;
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Spacing System
|
|
145
|
-
|
|
146
|
-
### Logical Properties
|
|
147
|
-
|
|
148
|
-
Use CSS logical properties for RTL support. Use `block` (vertical) and `inline` (horizontal) instead of top/bottom/left/right.
|
|
149
|
-
|
|
150
|
-
### Base Spacing Variables
|
|
151
|
-
|
|
152
|
-
```css
|
|
153
|
-
--inline-space: 1ch; /* Character width */
|
|
154
|
-
--inline-space-half: 0.5ch;
|
|
155
|
-
--inline-space-double: 2ch;
|
|
156
|
-
|
|
157
|
-
--block-space: 1rem; /* Vertical rhythm */
|
|
158
|
-
--block-space-half: 0.5rem;
|
|
159
|
-
--block-space-double: 2rem;
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Padding Utilities
|
|
163
|
-
|
|
164
|
-
```css
|
|
165
|
-
.pad /* Full padding */
|
|
166
|
-
.pad-double /* 2x padding */
|
|
167
|
-
.pad-block /* Vertical only */
|
|
168
|
-
.pad-block-start /* Top only */
|
|
169
|
-
.pad-block-end /* Bottom only */
|
|
170
|
-
.pad-block-half /* Half vertical */
|
|
171
|
-
.pad-inline /* Horizontal only */
|
|
172
|
-
.pad-inline-start /* Left only (LTR) */
|
|
173
|
-
.pad-inline-end /* Right only (LTR) */
|
|
174
|
-
.pad-inline-half /* Half horizontal */
|
|
175
|
-
.unpad, .unpad-block-end, .unpad-inline
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Margin Utilities
|
|
179
|
-
|
|
180
|
-
```css
|
|
181
|
-
.margin, .margin-block, .margin-inline
|
|
182
|
-
.margin-block-start, .margin-block-end
|
|
183
|
-
.margin-block-half, .margin-block-double
|
|
184
|
-
.margin-inline-start, .margin-inline-end
|
|
185
|
-
.center /* margin-inline: auto */
|
|
186
|
-
.margin-none, .margin-block-none, .margin-inline-none
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Layout Spacing
|
|
190
|
-
|
|
191
|
-
```css
|
|
192
|
-
--main-padding: clamp(1ch, 3vw, 3ch);
|
|
193
|
-
--main-width: 1400px;
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## Components
|
|
199
|
-
|
|
200
|
-
### Buttons
|
|
201
|
-
|
|
202
|
-
Buttons use CSS custom properties for variants:
|
|
203
|
-
|
|
204
|
-
```css
|
|
205
|
-
.btn {
|
|
206
|
-
--btn-background: var(--color-canvas);
|
|
207
|
-
--btn-border-color: var(--color-ink-lighter);
|
|
208
|
-
--btn-color: inherit;
|
|
209
|
-
--btn-padding: 0.5em 1.25em;
|
|
210
|
-
--btn-font-weight: 500;
|
|
211
|
-
--btn-border-radius: 99rem;
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
**Variants:**
|
|
216
|
-
|
|
217
|
-
| Class | Purpose |
|
|
218
|
-
|-------|---------|
|
|
219
|
-
| `.btn--link` | Text-only, no background |
|
|
220
|
-
| `.btn--plain` | Minimal styling |
|
|
221
|
-
| `.btn--circle` | Circular icon button |
|
|
222
|
-
| `.btn--negative` | Destructive action (red) |
|
|
223
|
-
| `.btn--positive` | Affirmative action (green) |
|
|
224
|
-
| `.btn--reversed` | Inverted colors |
|
|
225
|
-
| `.btn--circle-mobile` | Circle on small screens |
|
|
226
|
-
|
|
227
|
-
**States:**
|
|
228
|
-
- Disabled: `opacity: 0.3; pointer-events: none`
|
|
229
|
-
- Loading: Animated spinner overlay on form submit
|
|
230
|
-
|
|
231
|
-
### Cards
|
|
232
|
-
|
|
233
|
-
```css
|
|
234
|
-
.card {
|
|
235
|
-
--card-color: var(--color-card-default);
|
|
236
|
-
--card-bg-color: color-mix(in srgb, var(--card-color) 4%, var(--color-canvas));
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
.card__header
|
|
240
|
-
.card__board
|
|
241
|
-
.card__id
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### Dialogs
|
|
245
|
-
|
|
246
|
-
Dialogs use CSS transitions with `allow-discrete`:
|
|
247
|
-
|
|
248
|
-
```css
|
|
249
|
-
.dialog {
|
|
250
|
-
--dialog-duration: 150ms;
|
|
251
|
-
/* Scale from 0.2 to 1 with opacity */
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
## Icons & SVG
|
|
258
|
-
|
|
259
|
-
Use a **CSS mask-based icon system** with individual SVG files. Icons inherit color from their parent via `currentColor`, making them easy to style contextually.
|
|
260
|
-
|
|
261
|
-
### Icon Storage
|
|
262
|
-
|
|
263
|
-
All icons are stored as individual SVG files in `app/assets/images/`:
|
|
264
|
-
|
|
265
|
-
```
|
|
266
|
-
app/assets/images/
|
|
267
|
-
├── add.svg
|
|
268
|
-
├── check.svg
|
|
269
|
-
├── bell.svg
|
|
270
|
-
├── bell-alert.svg
|
|
271
|
-
├── bell-off.svg
|
|
272
|
-
├── bookmark.svg
|
|
273
|
-
├── bookmark-outline.svg
|
|
274
|
-
├── boost-color.svg # Special colored variant
|
|
275
|
-
└── ... (84 total icons)
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### Naming Conventions
|
|
279
|
-
|
|
280
|
-
| Pattern | Example | Purpose |
|
|
281
|
-
|---------|---------|---------|
|
|
282
|
-
| `name.svg` | `check.svg` | Standard icon |
|
|
283
|
-
| `name-variant.svg` | `bell-alert.svg` | State variant |
|
|
284
|
-
| `name-outline.svg` | `bookmark-outline.svg` | Outline style |
|
|
285
|
-
| `name--meta.svg` | `add--meta.svg` | Metadata context |
|
|
286
|
-
| `name-color.svg` | `boost-color.svg` | Multi-color icon |
|
|
287
|
-
|
|
288
|
-
### Icon Helper
|
|
289
|
-
|
|
290
|
-
Use the `icon_tag` helper to render icons:
|
|
291
|
-
|
|
292
|
-
```erb
|
|
293
|
-
<%# Basic usage %>
|
|
294
|
-
<%= icon_tag "check" %>
|
|
295
|
-
|
|
296
|
-
<%# With custom classes %>
|
|
297
|
-
<%= icon_tag "pencil", class: "txt-subtle" %>
|
|
298
|
-
|
|
299
|
-
<%# In a button with screen reader text %>
|
|
300
|
-
<%= button_to path, class: "btn" do %>
|
|
301
|
-
<%= icon_tag "trash" %>
|
|
302
|
-
<span class="for-screen-reader">Delete item</span>
|
|
303
|
-
<% end %>
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
The helper generates:
|
|
307
|
-
|
|
308
|
-
```html
|
|
309
|
-
<span class="icon icon--check" aria-hidden="true"></span>
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
### How It Works
|
|
313
|
-
|
|
314
|
-
Icons use CSS `mask-image` with `currentColor` for flexible styling:
|
|
315
|
-
|
|
316
|
-
```css
|
|
317
|
-
.icon {
|
|
318
|
-
background-color: currentColor;
|
|
319
|
-
block-size: var(--icon-size, 1em);
|
|
320
|
-
inline-size: var(--icon-size, 1em);
|
|
321
|
-
mask-image: var(--svg);
|
|
322
|
-
mask-position: center;
|
|
323
|
-
mask-repeat: no-repeat;
|
|
324
|
-
mask-size: var(--icon-size, 1em);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
.icon--check {
|
|
328
|
-
--svg: url("check.svg");
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
### Icon Sizing
|
|
333
|
-
|
|
334
|
-
Control size via the `--icon-size` custom property:
|
|
335
|
-
|
|
336
|
-
| Context | Size | Variable |
|
|
337
|
-
|---------|------|----------|
|
|
338
|
-
| Default | `1em` | `--icon-size: 1em` |
|
|
339
|
-
| Buttons | `1.3em` | `--btn-icon-size: 1.3em` |
|
|
340
|
-
| Header buttons | `1rem` | `--btn-icon-size: 1rem` |
|
|
341
|
-
| Popups | `24px` | `--popup-icon-size: 24px` |
|
|
342
|
-
| Navigation | `2em` | `--icon-size: 2em` |
|
|
343
|
-
| Card metadata | `0.9em` | `--icon-size: 0.9em` |
|
|
344
|
-
| Reactions | `1.3em` | `--btn-icon-size: 1.3em` |
|
|
345
|
-
|
|
346
|
-
### Icon Colors
|
|
347
|
-
|
|
348
|
-
Icons inherit `currentColor` by default. Override contextually:
|
|
349
|
-
|
|
350
|
-
```css
|
|
351
|
-
/* Inherits parent text color */
|
|
352
|
-
.icon {
|
|
353
|
-
background-color: currentColor;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/* Context-specific coloring */
|
|
357
|
-
.event-icon .icon {
|
|
358
|
-
background-color: var(--card-color);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/* Attachment type colors */
|
|
362
|
-
.attachment--red { --attachment-icon-color: oklch(var(--lch-red-medium)); }
|
|
363
|
-
.attachment--blue { --attachment-icon-color: oklch(var(--lch-blue-medium)); }
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### Icon Buttons
|
|
367
|
-
|
|
368
|
-
Icon-only buttons automatically adjust sizing:
|
|
369
|
-
|
|
370
|
-
```css
|
|
371
|
-
/* Buttons with only an icon (detected via aria-label or .for-screen-reader) */
|
|
372
|
-
.btn[aria-label]:where(:has(.icon)),
|
|
373
|
-
.btn:where(:has(.for-screen-reader):has(.icon)) {
|
|
374
|
-
--btn-padding: 0;
|
|
375
|
-
--icon-size: 75%;
|
|
376
|
-
aspect-ratio: 1;
|
|
377
|
-
display: grid;
|
|
378
|
-
place-items: center;
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
Usage pattern:
|
|
383
|
-
|
|
384
|
-
```erb
|
|
385
|
-
<%# Icon button with accessible label %>
|
|
386
|
-
<%= button_to path, class: "btn", aria: { label: "Edit" } do %>
|
|
387
|
-
<%= icon_tag "pencil" %>
|
|
388
|
-
<% end %>
|
|
389
|
-
|
|
390
|
-
<%# Or with screen reader text %>
|
|
391
|
-
<%= button_to path, class: "btn" do %>
|
|
392
|
-
<%= icon_tag "pencil" %>
|
|
393
|
-
<span class="for-screen-reader">Edit</span>
|
|
394
|
-
<% end %>
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### Icon Animations
|
|
398
|
-
|
|
399
|
-
Icons support animations, particularly in success states:
|
|
400
|
-
|
|
401
|
-
```css
|
|
402
|
-
@keyframes zoom-fade {
|
|
403
|
-
100% {
|
|
404
|
-
transform: translateY(-1.5em);
|
|
405
|
-
scale: 2;
|
|
406
|
-
opacity: 0;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
.btn--success .icon {
|
|
411
|
-
animation: zoom-fade 500ms cubic-bezier(0.25, 1.25, 0.5, 1);
|
|
412
|
-
}
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
### Multi-Color Icons
|
|
416
|
-
|
|
417
|
-
For icons requiring multiple colors (not mask-compatible), use `image_tag`:
|
|
418
|
-
|
|
419
|
-
```erb
|
|
420
|
-
<%# Colored icon (not masked) %>
|
|
421
|
-
<%= image_tag "boost-color.svg", aria: { hidden: true }, class: "icon" %>
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
### SVG File Requirements
|
|
425
|
-
|
|
426
|
-
When creating new icons:
|
|
427
|
-
|
|
428
|
-
1. **ViewBox**: Use `viewBox="0 0 24 24"` or `viewBox="0 0 32 32"`
|
|
429
|
-
2. **No fixed dimensions**: Omit `width` and `height` attributes
|
|
430
|
-
3. **Fill**: Use `fill="currentColor"` or no fill (for mask compatibility)
|
|
431
|
-
4. **Single color**: Icons should be monochrome for mask-image to work
|
|
432
|
-
5. **Optimized**: Remove unnecessary metadata and groups
|
|
433
|
-
|
|
434
|
-
Example SVG structure:
|
|
435
|
-
|
|
436
|
-
```xml
|
|
437
|
-
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
438
|
-
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
|
|
439
|
-
</svg>
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
### Available Icons
|
|
443
|
-
|
|
444
|
-
Common icons organized by category:
|
|
445
|
-
|
|
446
|
-
**Navigation:**
|
|
447
|
-
`arrow-left`, `arrow-right`, `arrow-up`, `chevron`, `caret-down`, `expand`, `collapse`
|
|
448
|
-
|
|
449
|
-
**Actions:**
|
|
450
|
-
`add`, `remove`, `close`, `check`, `pencil`, `copy-paste`, `trash`, `bookmark`, `pin`
|
|
451
|
-
|
|
452
|
-
**Status:**
|
|
453
|
-
`bell`, `bell-alert`, `bell-off`, `check-circle`, `close-circle`, `assigned`
|
|
454
|
-
|
|
455
|
-
**UI Elements:**
|
|
456
|
-
`menu-dots-horizontal`, `menu-dots-vertical`, `search`, `filter`, `sliders`
|
|
457
|
-
|
|
458
|
-
**Entities:**
|
|
459
|
-
`person`, `person-add`, `board`, `column-left`, `column-right`
|
|
460
|
-
|
|
461
|
-
**Application:**
|
|
462
|
-
`fizzy`, `boost`, `boost-color`, `golden-ticket`
|
|
463
|
-
|
|
464
|
-
---
|
|
465
|
-
|
|
466
|
-
## Forms & Inputs
|
|
467
|
-
|
|
468
|
-
Use CSS custom properties for flexible, themeable form controls. All inputs prevent iOS auto-zoom with `font-size: max(16px, 1em)`.
|
|
469
|
-
|
|
470
|
-
### Base Input Styling
|
|
471
|
-
|
|
472
|
-
```css
|
|
473
|
-
.input {
|
|
474
|
-
--input-accent-color: var(--color-ink);
|
|
475
|
-
--input-background: transparent;
|
|
476
|
-
--input-border-radius: 0.5em;
|
|
477
|
-
--input-border-color: var(--color-ink-medium);
|
|
478
|
-
--input-border-size: 1px;
|
|
479
|
-
--input-color: var(--color-ink);
|
|
480
|
-
--input-padding: 0.5em 0.8em;
|
|
481
|
-
|
|
482
|
-
font-size: max(16px, 1em); /* Prevents iOS zoom */
|
|
483
|
-
inline-size: 100%;
|
|
484
|
-
resize: none;
|
|
485
|
-
}
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### Input Variants
|
|
489
|
-
|
|
490
|
-
#### Text Input
|
|
491
|
-
|
|
492
|
-
Basic text input with full width:
|
|
493
|
-
|
|
494
|
-
```erb
|
|
495
|
-
<%= form.text_field :name, class: "input", placeholder: "Enter name..." %>
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
#### Actor Input Pattern
|
|
499
|
-
|
|
500
|
-
Wraps inputs in a label that acts as the visual input container, providing larger touch targets:
|
|
501
|
-
|
|
502
|
-
```erb
|
|
503
|
-
<label class="flex align-center gap input input--actor">
|
|
504
|
-
<%= icon_tag "search" %>
|
|
505
|
-
<%= form.text_field :query, class: "input full-width", placeholder: "Search..." %>
|
|
506
|
-
</label>
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
```css
|
|
510
|
-
.input--actor {
|
|
511
|
-
&:focus-within {
|
|
512
|
-
--input-border-color: var(--color-selected-dark);
|
|
513
|
-
outline: var(--focus-ring-size) solid var(--focus-ring-color);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
.input {
|
|
517
|
-
--input-padding: 0;
|
|
518
|
-
--input-border-size: 0;
|
|
519
|
-
--input-background: transparent;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
#### Select Dropdown
|
|
525
|
-
|
|
526
|
-
Custom styled select with SVG caret:
|
|
527
|
-
|
|
528
|
-
```erb
|
|
529
|
-
<%= form.select :status, options, {}, class: "input input--select" %>
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
```css
|
|
533
|
-
.input--select {
|
|
534
|
-
--input-border-radius: 2em;
|
|
535
|
-
--input-padding: 0.5em 1.8em 0.5em 1.2em;
|
|
536
|
-
appearance: none;
|
|
537
|
-
background-image: url("caret-down.svg");
|
|
538
|
-
background-position: right 0.5em center;
|
|
539
|
-
background-repeat: no-repeat;
|
|
540
|
-
background-size: 1em;
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
#### Textarea
|
|
545
|
-
|
|
546
|
-
Auto-resizing textarea using `field-sizing: content`:
|
|
547
|
-
|
|
548
|
-
```erb
|
|
549
|
-
<%= form.text_area :description, class: "input input--textarea", rows: 1 %>
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
```css
|
|
553
|
-
.input--textarea {
|
|
554
|
-
min-block-size: calc(3lh + (2 * var(--input-padding)));
|
|
555
|
-
|
|
556
|
-
@supports (field-sizing: content) {
|
|
557
|
-
field-sizing: content;
|
|
558
|
-
max-block-size: calc(3lh + (2 * var(--input-padding)));
|
|
559
|
-
min-block-size: calc(1lh + (2 * var(--input-padding)));
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
#### File Input
|
|
565
|
-
|
|
566
|
-
Hidden file input with custom button overlay:
|
|
567
|
-
|
|
568
|
-
```erb
|
|
569
|
-
<label class="btn input--file">
|
|
570
|
-
<%= icon_tag "upload" %>
|
|
571
|
-
<span>Choose file</span>
|
|
572
|
-
<%= form.file_field :attachment, class: "input", accept: "image/*" %>
|
|
573
|
-
</label>
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
```css
|
|
577
|
-
.input--file {
|
|
578
|
-
cursor: pointer;
|
|
579
|
-
display: grid;
|
|
580
|
-
place-items: center;
|
|
581
|
-
|
|
582
|
-
input[type="file"] {
|
|
583
|
-
cursor: pointer;
|
|
584
|
-
font-size: 0;
|
|
585
|
-
inset: 0;
|
|
586
|
-
opacity: 0;
|
|
587
|
-
position: absolute;
|
|
588
|
-
|
|
589
|
-
&::file-selector-button {
|
|
590
|
-
appearance: none;
|
|
591
|
-
cursor: pointer;
|
|
592
|
-
opacity: 0;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
#### Number Input (Inline)
|
|
599
|
-
|
|
600
|
-
Minimal inline number input that sizes to content:
|
|
601
|
-
|
|
602
|
-
```erb
|
|
603
|
-
<%= form.number_field :quantity, class: "input boost__input", min: 1 %>
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
```css
|
|
607
|
-
.input.boost__input {
|
|
608
|
-
--input-border-size: 0;
|
|
609
|
-
--input-padding: 0;
|
|
610
|
-
inline-size: min-content;
|
|
611
|
-
|
|
612
|
-
@supports (field-sizing: content) {
|
|
613
|
-
field-sizing: content;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
&:focus {
|
|
617
|
-
background-color: var(--color-highlight);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
#### One-Time Code Input
|
|
623
|
-
|
|
624
|
-
Styled for OTP/verification codes:
|
|
625
|
-
|
|
626
|
-
```erb
|
|
627
|
-
<%= form.text_field :code, class: "input", autocomplete: "one-time-code" %>
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
```css
|
|
631
|
-
.input[autocomplete='one-time-code'] {
|
|
632
|
-
font-family: var(--font-mono);
|
|
633
|
-
font-size: var(--text-large);
|
|
634
|
-
font-weight: 900;
|
|
635
|
-
inline-size: 18ch;
|
|
636
|
-
letter-spacing: 1ch;
|
|
637
|
-
text-align: center;
|
|
638
|
-
}
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
### Switch/Toggle
|
|
642
|
-
|
|
643
|
-
Binary toggle switch component:
|
|
644
|
-
|
|
645
|
-
```erb
|
|
646
|
-
<label class="switch">
|
|
647
|
-
<%= form.check_box :enabled, class: "switch__input" %>
|
|
648
|
-
<span class="switch__btn"></span>
|
|
649
|
-
<span class="for-screen-reader">Enable feature</span>
|
|
650
|
-
</label>
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
```css
|
|
654
|
-
.switch {
|
|
655
|
-
--switch-color: var(--color-ink-medium);
|
|
656
|
-
block-size: 1.75em;
|
|
657
|
-
inline-size: 3em;
|
|
658
|
-
border-radius: 2em;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
.switch__btn {
|
|
662
|
-
background-color: var(--switch-color);
|
|
663
|
-
transition: 150ms ease;
|
|
664
|
-
|
|
665
|
-
&::before { /* The toggle dot */
|
|
666
|
-
background-color: var(--color-ink-inverted);
|
|
667
|
-
block-size: 1.35em;
|
|
668
|
-
border-radius: 50%;
|
|
669
|
-
transition: 150ms ease;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
.switch__input:checked + .switch__btn {
|
|
674
|
-
--switch-color: var(--color-link);
|
|
675
|
-
|
|
676
|
-
&::before {
|
|
677
|
-
transform: translateX(1.2em);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
.switch__input:disabled + .switch__btn {
|
|
682
|
-
cursor: not-allowed;
|
|
683
|
-
opacity: 0.5;
|
|
684
|
-
}
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
### Checkbox & Radio as Buttons
|
|
688
|
-
|
|
689
|
-
Style checkboxes/radios as toggle buttons:
|
|
690
|
-
|
|
691
|
-
```erb
|
|
692
|
-
<label class="btn">
|
|
693
|
-
<%= form.radio_button :color, "red" %>
|
|
694
|
-
Red
|
|
695
|
-
</label>
|
|
696
|
-
```
|
|
697
|
-
|
|
698
|
-
```css
|
|
699
|
-
.btn:has(input[type=radio], input[type=checkbox]) {
|
|
700
|
-
input {
|
|
701
|
-
appearance: none;
|
|
702
|
-
cursor: pointer;
|
|
703
|
-
inset: 0;
|
|
704
|
-
position: absolute;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
&:has(input:checked) {
|
|
708
|
-
--btn-background: var(--color-ink);
|
|
709
|
-
--btn-color: var(--color-ink-inverted);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
### Input States
|
|
715
|
-
|
|
716
|
-
#### Focus State
|
|
717
|
-
|
|
718
|
-
Inputs use an internal focus ring (offset inside the border):
|
|
719
|
-
|
|
720
|
-
```css
|
|
721
|
-
.input {
|
|
722
|
-
--focus-ring-offset: -1px;
|
|
723
|
-
|
|
724
|
-
&:focus {
|
|
725
|
-
outline: var(--focus-ring-size) solid var(--focus-ring-color);
|
|
726
|
-
outline-offset: var(--focus-ring-offset);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
#### Disabled State
|
|
732
|
-
|
|
733
|
-
```css
|
|
734
|
-
.input:disabled {
|
|
735
|
-
cursor: not-allowed;
|
|
736
|
-
opacity: 0.5;
|
|
737
|
-
pointer-events: none;
|
|
738
|
-
}
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
#### Readonly State
|
|
742
|
-
|
|
743
|
-
```css
|
|
744
|
-
.input[readonly] {
|
|
745
|
-
--focus-ring-size: 0;
|
|
746
|
-
}
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
#### Autofill Styling
|
|
750
|
-
|
|
751
|
-
Override browser autofill colors:
|
|
752
|
-
|
|
753
|
-
```css
|
|
754
|
-
.input:autofill,
|
|
755
|
-
.input:-webkit-autofill {
|
|
756
|
-
-webkit-text-fill-color: var(--color-ink);
|
|
757
|
-
-webkit-box-shadow: 0 0 0px 1000px var(--color-selected) inset;
|
|
758
|
-
}
|
|
759
|
-
```
|
|
760
|
-
|
|
761
|
-
### Form Layout
|
|
762
|
-
|
|
763
|
-
Use flexbox utilities for form structure:
|
|
764
|
-
|
|
765
|
-
```erb
|
|
766
|
-
<%= form_with model: @user, class: "flex flex-column gap" do |form| %>
|
|
767
|
-
<div class="flex flex-column gap-half">
|
|
768
|
-
<label>Email</label>
|
|
769
|
-
<%= form.email_field :email, class: "input" %>
|
|
770
|
-
</div>
|
|
771
|
-
|
|
772
|
-
<div class="flex flex-column gap-half">
|
|
773
|
-
<label>Password</label>
|
|
774
|
-
<%= form.password_field :password, class: "input" %>
|
|
775
|
-
</div>
|
|
776
|
-
|
|
777
|
-
<button type="submit" class="btn btn--positive">Save</button>
|
|
778
|
-
<% end %>
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
### Error Display
|
|
782
|
-
|
|
783
|
-
Display validation errors with semantic styling:
|
|
784
|
-
|
|
785
|
-
```erb
|
|
786
|
-
<% if @user.errors.any? %>
|
|
787
|
-
<div class="txt-negative txt-small margin-block-half">
|
|
788
|
-
<p class="font-weight-bold margin-block-none">Your changes couldn't be saved:</p>
|
|
789
|
-
<ul class="margin-block-none">
|
|
790
|
-
<% @user.errors.full_messages.each do |message| %>
|
|
791
|
-
<li><%= message %></li>
|
|
792
|
-
<% end %>
|
|
793
|
-
</ul>
|
|
794
|
-
</div>
|
|
795
|
-
<% end %>
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
### Form Submission
|
|
799
|
-
|
|
800
|
-
#### Submit Button with Loading State
|
|
801
|
-
|
|
802
|
-
Forms with `aria-busy` show a spinner on disabled submit buttons:
|
|
803
|
-
|
|
804
|
-
```css
|
|
805
|
-
form[aria-busy] button:disabled {
|
|
806
|
-
> * {
|
|
807
|
-
visibility: hidden;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
&::after {
|
|
811
|
-
animation: submitting 1s infinite linear;
|
|
812
|
-
/* Spinner overlay */
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
```
|
|
816
|
-
|
|
817
|
-
#### Auto-Submit Forms
|
|
818
|
-
|
|
819
|
-
Use the `auto_submit_form_with` helper for forms that submit on change:
|
|
820
|
-
|
|
821
|
-
```erb
|
|
822
|
-
<%= auto_submit_form_with model: @settings do |form| %>
|
|
823
|
-
<%= form.select :theme, options, {},
|
|
824
|
-
class: "input input--select",
|
|
825
|
-
data: { action: "change->form#submit" } %>
|
|
826
|
-
<% end %>
|
|
827
|
-
```
|
|
828
|
-
|
|
829
|
-
### Form Custom Properties Reference
|
|
830
|
-
|
|
831
|
-
| Property | Default | Purpose |
|
|
832
|
-
|----------|---------|---------|
|
|
833
|
-
| `--input-accent-color` | `var(--color-ink)` | Checkbox/radio accent |
|
|
834
|
-
| `--input-background` | `transparent` | Input background |
|
|
835
|
-
| `--input-border-radius` | `0.5em` | Corner radius |
|
|
836
|
-
| `--input-border-color` | `var(--color-ink-medium)` | Border color |
|
|
837
|
-
| `--input-border-size` | `1px` | Border width |
|
|
838
|
-
| `--input-color` | `var(--color-ink)` | Text color |
|
|
839
|
-
| `--input-padding` | `0.5em 0.8em` | Internal padding |
|
|
840
|
-
| `--switch-color` | `var(--color-ink-medium)` | Switch track color |
|
|
841
|
-
|
|
842
|
-
---
|
|
843
|
-
|
|
844
|
-
## Layout
|
|
845
|
-
|
|
846
|
-
### Grid-Based Header
|
|
847
|
-
|
|
848
|
-
```css
|
|
849
|
-
.header {
|
|
850
|
-
display: grid;
|
|
851
|
-
grid-template-columns: var(--actions-start-size) 1fr var(--actions-end-size);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
.header--mobile-actions-stack /* Stacked on mobile */
|
|
855
|
-
```
|
|
856
|
-
|
|
857
|
-
### Main Content
|
|
858
|
-
|
|
859
|
-
```css
|
|
860
|
-
body {
|
|
861
|
-
display: grid;
|
|
862
|
-
grid-template-rows: auto 1fr auto 9em;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
#main {
|
|
866
|
-
inline-size: 100dvw;
|
|
867
|
-
max-inline-size: var(--main-width);
|
|
868
|
-
}
|
|
869
|
-
```
|
|
870
|
-
|
|
871
|
-
### Flexbox Utilities
|
|
872
|
-
|
|
873
|
-
```css
|
|
874
|
-
.flex, .flex-inline, .flex-column, .flex-wrap
|
|
875
|
-
.flex-1, .flex-item-grow, .flex-item-shrink, .flex-item-no-shrink
|
|
876
|
-
.gap, .gap-half, .gap-none
|
|
877
|
-
.justify-start, .justify-end, .justify-center, .justify-space-between
|
|
878
|
-
.align-start, .align-end, .align-center
|
|
879
|
-
```
|
|
880
|
-
|
|
881
|
-
### Sizing Utilities
|
|
882
|
-
|
|
883
|
-
```css
|
|
884
|
-
.full-width, .max-width, .half-width
|
|
885
|
-
.min-width, .min-content, .fit-content
|
|
886
|
-
.overflow-x, .overflow-y
|
|
887
|
-
.overflow-ellipsis
|
|
888
|
-
.overflow-line-clamp /* Use with --lines custom property */
|
|
889
|
-
```
|
|
890
|
-
|
|
891
|
-
---
|
|
892
|
-
|
|
893
|
-
## Dark Mode
|
|
894
|
-
|
|
895
|
-
Dark mode is supported via theme attribute and system preference:
|
|
896
|
-
|
|
897
|
-
```css
|
|
898
|
-
/* User-selected theme */
|
|
899
|
-
html[data-theme="dark"] { ... }
|
|
900
|
-
|
|
901
|
-
/* System preference fallback */
|
|
902
|
-
@media (prefers-color-scheme: dark) {
|
|
903
|
-
html:not([data-theme]) { ... }
|
|
904
|
-
}
|
|
905
|
-
```
|
|
906
|
-
|
|
907
|
-
Dark mode adjustments include:
|
|
908
|
-
- Complete OKLCH palette redefinition
|
|
909
|
-
- Enhanced layered shadows (6 layers vs 4)
|
|
910
|
-
- Adjusted contrast ratios
|
|
911
|
-
|
|
912
|
-
---
|
|
913
|
-
|
|
914
|
-
## Borders & Shadows
|
|
915
|
-
|
|
916
|
-
### Borders
|
|
917
|
-
|
|
918
|
-
```css
|
|
919
|
-
--border-color: var(--color-ink-lighter);
|
|
920
|
-
|
|
921
|
-
.border /* 1px solid */
|
|
922
|
-
.border-block /* Top and bottom */
|
|
923
|
-
.border-top, .border-bottom
|
|
924
|
-
.borderless
|
|
925
|
-
.border-radius /* Uses --border-radius, default 0.5em */
|
|
926
|
-
```
|
|
927
|
-
|
|
928
|
-
### Shadows
|
|
929
|
-
|
|
930
|
-
```css
|
|
931
|
-
--shadow:
|
|
932
|
-
0 0 0 1px oklch(var(--lch-black) / 5%),
|
|
933
|
-
0 0.2em 0.2em oklch(var(--lch-black) / 5%),
|
|
934
|
-
0 0.4em 0.4em oklch(var(--lch-black) / 5%),
|
|
935
|
-
0 0.8em 0.8em oklch(var(--lch-black) / 5%);
|
|
936
|
-
|
|
937
|
-
.shadow /* Applies the layered shadow */
|
|
938
|
-
```
|
|
939
|
-
|
|
940
|
-
---
|
|
941
|
-
|
|
942
|
-
## Animations
|
|
943
|
-
|
|
944
|
-
### Easing Functions
|
|
945
|
-
|
|
946
|
-
```css
|
|
947
|
-
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
|
948
|
-
--ease-out-overshoot: cubic-bezier(0.25, 1.75, 0.5, 1);
|
|
949
|
-
--ease-out-overshoot-subtle: cubic-bezier(0.25, 1.25, 0.5, 1);
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
### Default Transitions
|
|
953
|
-
|
|
954
|
-
```css
|
|
955
|
-
transition: 100ms ease-out;
|
|
956
|
-
transition-property: background-color, border-color, box-shadow, filter, outline;
|
|
957
|
-
```
|
|
958
|
-
|
|
959
|
-
### Available Keyframes
|
|
960
|
-
|
|
961
|
-
| Animation | Purpose |
|
|
962
|
-
|-----------|---------|
|
|
963
|
-
| `shake` | Horizontal error shake |
|
|
964
|
-
| `react` | Scale up reaction |
|
|
965
|
-
| `scale-fade-out` | Shrink and fade |
|
|
966
|
-
| `slide-up`, `slide-down` | Vertical movement |
|
|
967
|
-
| `slide-up-fade-in` | Combined slide + fade |
|
|
968
|
-
| `pulse` | Opacity pulse |
|
|
969
|
-
| `submitting` | Spinner animation |
|
|
970
|
-
| `success` | Scale on success |
|
|
971
|
-
| `wiggle` | Rotation wiggle |
|
|
972
|
-
|
|
973
|
-
### Reduced Motion
|
|
974
|
-
|
|
975
|
-
```css
|
|
976
|
-
@media (prefers-reduced-motion: reduce) {
|
|
977
|
-
animation-duration: 0.01ms !important;
|
|
978
|
-
transition-duration: 0.01ms !important;
|
|
979
|
-
}
|
|
980
|
-
```
|
|
981
|
-
|
|
982
|
-
---
|
|
983
|
-
|
|
984
|
-
## Focus States
|
|
985
|
-
|
|
986
|
-
### Focus Ring
|
|
987
|
-
|
|
988
|
-
```css
|
|
989
|
-
--focus-ring-color: var(--color-link);
|
|
990
|
-
--focus-ring-offset: 1px;
|
|
991
|
-
--focus-ring-size: 2px;
|
|
992
|
-
--focus-ring: 2px solid var(--color-link);
|
|
993
|
-
```
|
|
994
|
-
|
|
995
|
-
Applied via `:focus-visible` for keyboard-only styling on all interactive elements.
|
|
996
|
-
|
|
997
|
-
---
|
|
998
|
-
|
|
999
|
-
## Z-Index Stack
|
|
1000
|
-
|
|
1001
|
-
| Variable | Value | Purpose |
|
|
1002
|
-
|----------|-------|---------|
|
|
1003
|
-
| `--z-events-column-header` | 1 | Column headers |
|
|
1004
|
-
| `--z-events-day-header` | 3 | Day headers |
|
|
1005
|
-
| `--z-popup` | 10 | Popups/dropdowns |
|
|
1006
|
-
| `--z-nav` | 20 | Navigation |
|
|
1007
|
-
| `--z-flash` | 30 | Flash messages |
|
|
1008
|
-
| `--z-tooltip` | 40 | Tooltips |
|
|
1009
|
-
| `--z-bar` | 50 | Action bars |
|
|
1010
|
-
| `--z-tray` | 51 | Side trays |
|
|
1011
|
-
| `--z-welcome` | 52 | Welcome overlay |
|
|
1012
|
-
| `--z-nav-open` | 100 | Open navigation |
|
|
1013
|
-
|
|
1014
|
-
---
|
|
1015
|
-
|
|
1016
|
-
## Accessibility
|
|
1017
|
-
|
|
1018
|
-
### Visibility Utilities
|
|
1019
|
-
|
|
1020
|
-
```css
|
|
1021
|
-
.visually-hidden, .for-screen-reader /* Screen reader only */
|
|
1022
|
-
[hidden] /* Display none */
|
|
1023
|
-
.display-contents /* Remove wrapper */
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
### Responsive Visibility
|
|
1027
|
-
|
|
1028
|
-
```css
|
|
1029
|
-
.hide-on-touch /* Hidden on touch devices */
|
|
1030
|
-
.show-on-touch /* Visible only on touch */
|
|
1031
|
-
.show-on-native /* Native app only */
|
|
1032
|
-
.hide-in-pwa /* Hidden in PWA mode */
|
|
1033
|
-
.hide-in-browser /* Hidden in browser */
|
|
1034
|
-
```
|
|
1035
|
-
|
|
1036
|
-
---
|
|
1037
|
-
|
|
1038
|
-
## Responsive Design
|
|
1039
|
-
|
|
1040
|
-
### Breakpoints
|
|
1041
|
-
|
|
1042
|
-
- **Small**: < 640px (mobile)
|
|
1043
|
-
- **Medium**: 640px - 800px (tablet)
|
|
1044
|
-
- **Large**: > 800px (desktop)
|
|
1045
|
-
|
|
1046
|
-
### Approach
|
|
1047
|
-
|
|
1048
|
-
Mobile-first with `max-width` queries for larger screens. Use `clamp()` for fluid sizing:
|
|
1049
|
-
|
|
1050
|
-
```css
|
|
1051
|
-
--main-padding: clamp(1ch, 3vw, 3ch);
|
|
1052
|
-
font-size: clamp(0.85rem, 2vw, 1rem);
|
|
1053
|
-
```
|
|
1054
|
-
|
|
1055
|
-
### Safe Area Insets
|
|
1056
|
-
|
|
1057
|
-
Support for notched devices:
|
|
1058
|
-
|
|
1059
|
-
```css
|
|
1060
|
-
padding-inline: calc(var(--main-padding) + env(safe-area-inset-left));
|
|
1061
|
-
padding-block-start: calc(var(--block-space-half) + env(safe-area-inset-top));
|
|
1062
|
-
```
|
|
1063
|
-
|
|
1064
|
-
---
|
|
1065
|
-
|
|
1066
|
-
## Best Practices
|
|
1067
|
-
|
|
1068
|
-
### Do
|
|
1069
|
-
|
|
1070
|
-
- Use semantic color variables (`--color-ink`, `--color-link`)
|
|
1071
|
-
- Use logical properties (`block-start` instead of `top`)
|
|
1072
|
-
- Use CSS custom properties for component variants
|
|
1073
|
-
- Use `clamp()` for fluid responsive values
|
|
1074
|
-
- Test in both light and dark modes
|
|
1075
|
-
- Respect `prefers-reduced-motion`
|
|
1076
|
-
|
|
1077
|
-
### Don't
|
|
1078
|
-
|
|
1079
|
-
- Hard-code color values (use variables)
|
|
1080
|
-
- Use physical properties when logical work
|
|
1081
|
-
- Create new z-index values without adding to the stack
|
|
1082
|
-
- Skip focus states on interactive elements
|
|
1083
|
-
- Use `px` for font sizes (use `rem`)
|
|
1084
|
-
|
|
1085
|
-
### Adding New Styles
|
|
1086
|
-
|
|
1087
|
-
1. Check if an existing utility class serves the purpose
|
|
1088
|
-
2. Use CSS custom properties for configurable values
|
|
1089
|
-
3. Place styles in the appropriate layer
|
|
1090
|
-
4. Ensure dark mode compatibility
|
|
1091
|
-
5. Test with reduced motion preferences
|