| 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 |
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 |
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 |
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 |
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 |
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 |
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 |
480 |
|
/* Block */
|
| 463 |
481 |
|
.card { }
|
| 464 |
482 |
|
.modal { }
|
| 465 |
|
- |
.btn { }
|
|
483 |
+ |
.button { }
|
| 466 |
484 |
|
|
| 467 |
485 |
|
/* Elements (single hyphen) */
|
| 468 |
486 |
|
.card-header { }
|
| 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 |
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 |
|
|