Skip to main content

max / goingson

Sync the design docs with adoption 1 Five commits of the makeover adoption renamed primitives and neither doc followed. Both were prescriptive about classes that no longer exist, so a reader following them would have written markup the stylesheet does not style. Corrected across both files: .btn -> .button with double-hyphen variants, .form-input/.form-select/.form-textarea -> .field with the kind on the element, .tab.active -> .tab.chosen, .progress-bar-container/.progress-bar -> .progress/.progress-fill, .tag -> .badge. The naming section taught single-hyphen modifiers (.btn-primary) and carried an explicit "exception: .btn is established" clause. Both are now false, so the rule reads straight: double hyphen for variants, no abbreviations. Two errors found while checking rather than assuming: .undo-btn is documented as a toast sub-part and has never existed in either the CSS or the markup (the undo toast uses a plain .button), and .type-job appeared in a card example though .type-* has no rule anywhere.
Author: Max Johnson <me@maxj.phd> · 2026-07-30 00:14 UTC
Signed with PGP, not checked
Commit: 7010eeeb5e1427ab8a805e65bb2fb49c761aa385
Parent: eb3dd11
2 files changed, +80 insertions, -55 deletions
@@ -37,11 +37,12 @@
37 37
38 38 Each primitive below lists its **canonical class** (use this, only this). Modifier classes follow `--modifier` or `state-*` patterns. If you find yourself wanting a new modifier, add it here first.
39 39
40 - ### Button: `.btn`
41 - Variants: `.btn-primary`, `.btn-secondary`, `.btn-danger`, `.btn-icon`, `.btn-text`, `.btn-link`.
42 - Sizes: `.btn-sm` (default size is medium).
43 - State: `.btn-loading`.
44 - **Never** style a `<button>` without `.btn`. **Never** inline a hex color on a button.
40 + ### Button: `.button`
41 + Variants: `.button--primary`, `.button--secondary`, `.button--danger`, `.button--icon`, `.button--text`, `.button--link`.
42 + Sizes: `.button--sm` (default size is medium).
43 + State: `.button--loading`.
44 + **Never** style a `<button>` without `.button`. **Never** inline a hex color on a button.
45 + Fill and edge come from the generated `.button` in `css/layout.css`; do not restate them here.
45 46
46 47 ### Card: `.card`
47 48 Sub-parts: `.card-header`, `.card-title`, `.card-description`, `.card-meta`, `.card-badge`.
@@ -49,19 +50,23 @@
49 50 Used by: projects-render, contacts-render, dashboard tiles.
50 51
51 52 ### Form field: `.form-group`
52 - Sub-parts: `.form-label`, `.form-input | .form-select | .form-textarea`, `.form-actions`, `.form-row`.
53 + Sub-parts: `.form-label`, `.field`, `.form-actions`, `.form-row`.
54 + The kind rides on the element, not on a modifier: `input.field`, `select.field`, `textarea.field`. Modifiers: `.field--ghost`, `.field--compact`. Invalid state is `[aria-invalid="true"]`, not a class, so the visual and the accessible state read one fact.
53 55 Canonical render helper (**to be added**): `GoingsOn.ui.renderFormField({ kind, label, value, error, help })`. Until it exists, hand-rolled `.form-group` blocks are tolerated; once it lands they are not.
54 56
55 - ### Badge / Tag: `.badge`, `.tag`
56 - Color variant: `[data-color="green|yellow|red|cyan|purple|muted"]`.
57 - Status variant on `.tag`: `.status-active | .status-onhold | .status-archived | .status-inactive | .status-completed`.
57 + ### Badge: `.badge`
58 + There is no `.tag`. It was a second name for the same thing and collapsed into `.badge`.
59 + Color variant: `[data-color="green|yellow|red|cyan|purple|blue|muted"]`.
60 + Status variant: `.status-active | .status-onhold | .status-archived | .status-inactive | .status-completed`, plus `.badge-shared`.
61 + Sizes and intent: `.badge--xs`, `.badge--filled`.
62 + A plain badge is flat: no fill, no edge, text at `--content-muted`, all from the generated `.badge`. An edge on a label says it can be pressed, and no badge in GO is interactive. A badge carrying `[data-color]` or a status class is a pill and declares its own fill and border.
58 63
59 64 ### Modal: `.modal-overlay` (single global)
60 65 Open via `GoingsOn.ui.openModal(title, html, opts)`. Sub-parts: `.modal-container` (+ `.modal-large`), `.modal-header`, `.modal-title`, `.modal-content`, `.modal-close`. Visibility via `.hidden` / `.closing`. **There is only one modal overlay in the DOM** (`#modal-overlay` in `index.html`); never create another.
61 66
62 67 ### Toast: `.toast`
63 68 Variants: `.toast-info`, `.toast-success`, `.toast-error`, `.toast-undo`.
64 - Undo sub-parts: `.undo-message`, `.undo-btn`, `.undo-countdown`.
69 + Undo sub-parts: `.undo-message`, `.undo-countdown`, and a plain `.button.button--sm.button--primary` for the action.
65 70 Show via `GoingsOn.ui.showToast(msg, type, opts)` or `GoingsOn.ui.showUndoToast(...)`.
66 71 **Rule (to be enforced):** positioning, shadow, and color belong on these classes in CSS. `showToast` MUST NOT inject `style.cssText`. Today's helper violates this; fix in remediation.
67 72
@@ -69,7 +74,7 @@
69 74 Render via `GoingsOn.ui.showConfirmDialog(title, message, opts)` or `GoingsOn.ui.confirmDelete(name, action)`. Uses the global modal. **Never** call `window.confirm()` (one offender remains in `contacts.js`, fix in remediation).
70 75
71 76 ### Empty state: `.empty-state`
72 - Canonical: `<div class="empty-state"><div class="empty-state-icon">…</div><p class="empty-state-text">…</p><button class="btn btn-primary">…</button></div>`.
77 + Canonical: `<div class="empty-state"><div class="empty-state-icon">…</div><p class="empty-state-text">…</p><button class="button button--primary">…</button></div>`.
73 78 Render via `GoingsOn.ui.renderEmptyState(message, buttonLabel?, onClick?)`.
74 79 The non-canonical classes `.empty-dashboard-list`, `.kanban-empty`, `.virtual-scroller-empty` are **deprecated**; consolidate to `.empty-state` with size modifiers in remediation.
75 80
@@ -82,13 +87,14 @@
82 87 Open via `GoingsOn.ui.showContextMenu(x, y, items)`.
83 88
84 89 ### Tab / pill nav: `.tab-navigation` / `.pill-nav`
85 - Active state: `.tab.active` / `.pill.active`. Used in shell (`index.html`) only. Feature modules should not introduce new tab styles.
90 + Chosen state: `.tab.chosen` (scoped to `.tab-navigation`) / `.pill.active`. The two names differ because only `.tab` takes the generated selector rules. Used in shell (`index.html`) only. Feature modules should not introduce new tab styles.
86 91
87 92 ### Filter bar: `.filter-bar`
88 93 Children: `.filter-select`, `.filter-checkbox`. Used in tasks and emails filter rows.
89 94
90 - ### Progress bar: `.progress-bar-container` + `.progress-bar`
91 - Used in tasks (subtask completion) and reviews.
95 + ### Progress bar: `.progress` (trough) + `.progress-fill`
96 + Sizes: `.progress--slim`, `.progress--mini`, `.progress--focus`. Tone on the fill: `[data-tone="info|success|warning|danger"]`.
97 + Used in tasks (subtask completion), milestones, and reviews.
92 98
93 99 ### Row primitives
94 100
@@ -168,7 +174,7 @@
168 174 Any flow with more than two sequential modal steps shows a "Step N of M" indicator in the modal header. Applies to OAuth, encryption setup, plugin import wizards.
169 175
170 176 ### Action bars cap at 5 visible
171 - A horizontal action bar has at most 5 visible actions; the rest live in an overflow `Actions ▾` menu. Primary actions get `.btn-primary`; destructive actions go in the overflow.
177 + A horizontal action bar has at most 5 visible actions; the rest live in an overflow `Actions ▾` menu. Primary actions get `.button--primary`; destructive actions go in the overflow.
172 178
173 179 ### Justified touch branches
174 180 Modules with `isTouchDevice` branches must include a top-of-file comment naming what the branch does and why CSS-only isn't sufficient.
M docs/styleguide.md +59 -40
@@ -213,21 +213,21 @@
213 213
214 214 ```html
215 215 <!-- Primary button (blue background, white text) -->
216 - <button class="btn btn-primary">Action</button>
216 + <button class="button button--primary">Action</button>
217 217
218 218 <!-- Secondary button (light background) -->
219 - <button class="btn btn-secondary">Cancel</button>
219 + <button class="button button--secondary">Cancel</button>
220 220
221 221 <!-- Small button -->
222 - <button class="btn btn-sm">Small</button>
222 + <button class="button button--sm">Small</button>
223 223 ```
224 224
225 - **States (default `.btn`):**
225 + **States (default `.button`):**
226 226 - **Default**: Flat (border only, no shadow)
227 227 - **Hover**: Background changes to `--bg-secondary`
228 228 - **Active**: Background changes to `--bg-tertiary`
229 229
230 - **States (`.btn-primary`, `.btn-danger`):**
230 + **States (`.button--primary`, `.button--danger`):**
231 231 - **Default**: 4px offset shadow
232 232 - **Hover**: Lifts up (-2px, -2px), shadow increases
233 233 - **Active**: Pushes down (1px, 1px), shadow shrinks
@@ -241,8 +241,8 @@
241 241 </div>
242 242 <p class="card-description">Description text</p>
243 243 <div class="card-meta">
244 - <span class="tag type-job">Job</span>
245 - <span class="tag status-active">Active</span>
244 + <span class="badge">Job</span>
245 + <span class="badge status-active">Active</span>
246 246 </div>
247 247 </div>
248 248 ```
@@ -252,9 +252,20 @@
252 252 - Hover: lifts up (-2px), shadow grows, bg shifts to `--bg-secondary`
253 253 - Touch devices: no hover transform
254 254
255 - ### Badges & Tags
255 + ### Badges
256 256
257 - **Using data attributes (preferred):**
257 + There is no `.tag`. It was a second name for the same thing and collapsed into
258 + `.badge`.
259 +
260 + A plain badge is flat: no fill, no edge, text at `--content-muted`. An edge on a
261 + label says it can be pressed, and no badge in GO is interactive.
262 +
263 + ```html
264 + <span class="badge">Label</span>
265 + ```
266 +
267 + **Colour makes it a pill.** A badge carrying `[data-color]` or a status class
268 + declares its own fill and border:
258 269
259 270 ```html
260 271 <span class="badge" data-color="green">Success</span>
@@ -262,39 +273,46 @@
262 273 <span class="badge" data-color="red">Error</span>
263 274 <span class="badge" data-color="cyan">Info</span>
264 275 <span class="badge" data-color="purple">Special</span>
265 - <span class="badge" data-color="muted">Default</span>
276 + <span class="badge" data-color="blue">Default</span>
277 + <span class="badge" data-color="muted">Muted</span>
266 278 ```
267 279
268 - **Legacy classes:**
280 + **Status classes:**
269 281
270 282 ```html
271 - <span class="tag type-job">Job</span>
272 - <span class="tag type-sideproject">Side Project</span>
273 - <span class="tag status-active">Active</span>
274 - <span class="tag status-completed">Completed</span>
283 + <span class="badge status-active">Active</span>
284 + <span class="badge status-completed">Completed</span>
285 + <span class="badge badge-shared">Shared</span>
275 286 ```
276 287
288 + **Size and intent:** `.badge--xs` for a smaller pill, `.badge--filled` for a
289 + solid accent fill with no border.
290 +
277 291 ### Form Inputs
278 292
279 293 ```html
280 294 <div class="form-group">
281 295 <label class="form-label">Label</label>
282 - <input type="text" class="form-input" placeholder="Enter text...">
296 + <input type="text" class="field" placeholder="Enter text...">
283 297 </div>
284 298
285 299 <div class="form-group">
286 300 <label class="form-label">Select</label>
287 - <select class="form-select">
301 + <select class="field">
288 302 <option>Option 1</option>
289 303 </select>
290 304 </div>
291 305
292 306 <div class="form-group">
293 307 <label class="form-label">Textarea</label>
294 - <textarea class="form-textarea"></textarea>
308 + <textarea class="field"></textarea>
295 309 </div>
296 310 ```
297 311
312 + The kind rides on the element, not on a modifier class. `select.field` and
313 + `textarea.field` carry the kind-specific bits, so an element selector cannot be
314 + forgotten at a call site.
315 +
298 316 **Focus state**: Blue ring (2px) around the input
299 317
300 318 ### Modals
@@ -368,14 +386,14 @@
368 386
369 387 ### Hover Lift Effect
370 388
371 - Reserved for clickable cards (`.card`, `.dashboard-item`, `.kanban-card`, `.saved-view-item`), `.btn-primary`, `.btn-danger`, modals, dropdowns, mobile nav:
389 + Reserved for clickable cards (`.card`, `.dashboard-item`, `.kanban-card`, `.saved-view-item`), `.button--primary`, `.button--danger`, modals, dropdowns, mobile nav:
372 390
373 391 ```css
374 392 .card:hover {
375 393 transform: translate(-2px, -2px);
376 394 box-shadow: 6px 6px 0 var(--border-color);
377 395 }
378 - .btn-primary:active {
396 + .button--primary:active {
379 397 transform: translate(1px, 1px);
380 398 box-shadow: 1px 1px 0 var(--border-color);
381 399 }
@@ -392,8 +410,8 @@
392 410 All interactive elements have visible focus indicators:
393 411
394 412 ```css
395 - .btn:focus-visible,
396 - .form-input:focus-visible {
413 + .button:focus-visible,
414 + .field:focus-visible {
397 415 outline: 3px solid var(--accent-blue);
398 416 outline-offset: 2px;
399 417 }
@@ -451,9 +469,9 @@
451 469 ### Pattern: `.block-element`
452 470
453 471 ```
454 - .component -> Block (card, modal, btn, form)
472 + .component -> Block (card, modal, button, field)
455 473 .component-part -> Element within block (card-header, modal-title)
456 - .component-modifier -> Variant (btn-primary, btn-sm)
474 + .component--variant -> Variant (button--primary, button--sm)
457 475 ```
458 476
459 477 ### Examples
@@ -462,7 +480,7 @@
462 480 /* Block */
463 481 .card { }
464 482 .modal { }
465 - .btn { }
483 + .button { }
466 484
467 485 /* Elements (single hyphen) */
468 486 .card-header { }
@@ -471,15 +489,15 @@
471 489 .modal-overlay { }
472 490 .modal-content { }
473 491
474 - /* Modifiers (single hyphen) */
475 - .btn-primary { }
476 - .btn-secondary { }
477 - .btn-sm { }
492 + /* Modifiers (double hyphen) */
493 + .button--primary { }
494 + .button--secondary { }
495 + .button--sm { }
478 496
479 497 /* State modifiers (class combination) */
480 - .tab.active { }
481 - .btn:disabled { }
482 - .form-input:focus { }
498 + .tab.chosen { }
499 + .button:disabled { }
500 + .field:focus { }
483 501 ```
484 502
485 503 ### Data Attributes for Dynamic Styling
@@ -506,20 +524,21 @@
506 524
507 525 | Pattern | Usage | Example |
508 526 |---------|-------|---------|
509 - | `.block` | Component root | `.card`, `.modal`, `.btn` |
510 - | `.block-element` | Child element | `.card-header`, `.btn-icon` |
511 - | `.block-modifier` | Variant | `.btn-primary`, `.card-compact` |
512 - | `.block.state` | State class | `.tab.active`, `.item.selected` |
527 + | `.block` | Component root | `.card`, `.modal`, `.button` |
528 + | `.block-element` | Child element | `.card-header`, `.card-title` |
529 + | `.block--variant` | Variant | `.button--primary`, `.card--shell` |
530 + | `.block.state` | State class | `.tab.chosen`, `.item.selected` |
513 531 | `.utility` | Single purpose | `.shadow-lg`, `.hover-lift` |
514 532 | `[data-attr]` | Dynamic variants | `[data-color="red"]` |
515 533
516 534 ### Naming Rules
517 535
518 536 1. **kebab-case only** - No camelCase or underscores
519 - 2. **Single hyphen** - `.card-header` not `.card__header` (simplified BEM)
520 - 3. **Descriptive names** - `.form-input` not `.fi`
521 - 4. **Component prefix** - `.modal-title` not `.title` (within modal context)
522 - 5. **No abbreviations** - `.button` not `.btn` (exception: `.btn` is established)
537 + 2. **Single hyphen for elements** - `.card-header` not `.card__header` (simplified BEM)
538 + 3. **Double hyphen for variants** - `.button--primary`, `.field--compact`
539 + 4. **Descriptive names** - `.field` not `.fi`
540 + 5. **Component prefix** - `.modal-title` not `.title` (within modal context)
541 + 6. **No abbreviations** - `.button` not `.btn`
523 542
524 543 ---
525 544