palace-ui 0.12.2 → 0.14.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.
Files changed (4) hide show
  1. package/SPEC.md +182 -54
  2. package/catalog.html +886 -664
  3. package/package.json +8 -2
  4. package/palace.css +152 -45
package/SPEC.md CHANGED
@@ -75,25 +75,31 @@ Or if consuming via npm with a build tool:
75
75
  | Token | Value | Usage |
76
76
  |---|---|---|
77
77
  | `--p-space-xs` | `4px` | Tight gaps |
78
- | `--p-space-sm` | `8px` | Button gaps, small margins |
79
- | `--p-space-md` | `16px` | Card margins, standard padding |
78
+ | `--p-space-sm` | `8px` | Button gaps, label to control |
79
+ | `--p-space-md` | `16px` | Space between cards and fields, standard padding |
80
80
  | `--p-space-lg` | `24px` | Card padding, section spacing |
81
81
  | `--p-space-xl` | `32px` | Intermediate section gaps |
82
82
  | `--p-space-2xl` | `48px` | Hero padding, major section gaps |
83
83
 
84
84
  ### Spacing Rules
85
85
 
86
- Three mechanisms for spacing, each with a clear use case:
86
+ Spacing comes from the container, never from the component inside it.
87
87
 
88
- 1. **`margin-bottom`** — spacing between sibling elements. All vertical inter-element spacing uses `margin-bottom` only (single-direction margins). Elements push the next sibling down; never pull themselves away with `margin-top`. This keeps spacing predictable, portable, and avoids margin-collapsing surprises.
88
+ 1. **`space-*` between children.** A container spaces its direct children vertically with the owl selector (`> * + *` gets `margin-top`). Use it for any column: paragraphs, fields, cards, sections.
89
89
 
90
- 2. **`gap`** — spacing inside flex/grid containers. Preferred when the parent is `display: flex` or `display: grid`. No margin needed on children.
90
+ 2. **`gap-*` in rows and grids.** A flex or grid container sets its `gap`. Its children get no margin.
91
91
 
92
- 3. **`.stack-*`** — spacing between children of a non-flex/grid container. Apply to the parent; children get uniform vertical spacing via the owl selector (`> * + *`). Available sizes: `stack-xs` (4px), `stack-sm` (8px), `stack-md` (16px), `stack-lg` (24px), `stack-xl` (32px), `stack-2xl` (48px).
92
+ 3. **Margin only for a single exception.** One element that needs more or less space than its container gives it gets its own margin, with a comment saying why.
93
93
 
94
- **Never** use `padding` to create distance between sibling elements — padding is for internal space within an element.
94
+ 4. **Padding is the inside of a box**, never the space between things.
95
95
 
96
- **Exceptions:** dividers (`.divider`, `.divider-danger`, `.gradient-stripe`) use top+bottom margin because they are structural separators by nature. Tight optical adjustments inside components (e.g. `.stat-label` 2px nudge) are exempt.
96
+ 5. **Components set no outer margin.** A component styles its inside only: `.card`, `.card-static`, `label`, `.stat-row`, `.page-header` and dialog parts have no margin around them. An outer margin would add to a `gap` or fight a `space-*`. Components that space their own children (`.page-header`, `.code-reveal`, `dialog`, list items in a container) do it with `:where()` rules, so a `space-*` utility on them always wins.
97
+
98
+ 6. **Missing values become tokens.** When a value is missing, add it to palace as a token instead of writing a raw number in an app. A raw value is only for a genuine one-off, with a comment saying why.
99
+
100
+ Sizes for both: `xs` (4px), `sm` (8px), `md` (16px), `lg` (24px), `xl` (32px), `2xl` (48px), the `--p-space-*` scale.
101
+
102
+ **Exceptions:** dividers (`.divider`, `.divider-danger`, `.gradient-stripe`) use top and bottom margin because they are separators by nature. `margin: auto` centres containers and dialogs. The `.stat-label` 2px optical nudge and the rules between the navbar menu's groups are part of those components.
97
103
 
98
104
  ### Layout
99
105
 
@@ -103,6 +109,14 @@ Three mechanisms for spacing, each with a clear use case:
103
109
  | `--p-container-narrow` | `420px` |
104
110
  | `--p-navbar-height` | `52px` |
105
111
 
112
+ ### Controls
113
+
114
+ | Token | Value | Usage |
115
+ |---|---|---|
116
+ | `--p-control-height` | `44px` | Text inputs, selects, number input, buttons |
117
+ | `--p-control-height-sm` | `32px` | `.btn-sm`, a button inside `.input-action` |
118
+ | `--p-control-height-lg` | `52px` | `.btn-lg`, `.cta` |
119
+
106
120
  ## Component Catalog
107
121
 
108
122
  ### Page Shell
@@ -198,7 +212,7 @@ Light variant for pages that need a white navbar:
198
212
 
199
213
  ### Page Header
200
214
 
201
- Standard spacing for non-hero pages (after navbar). Provides `48px` top padding and consistent gaps between h1, subtitle, and content.
215
+ Standard spacing for non-hero pages (after navbar). Provides `48px` top padding and `8px` between its children.
202
216
 
203
217
  ```html
204
218
  <div class="container page-header">
@@ -218,18 +232,30 @@ Combines with any container class:
218
232
  </div>
219
233
  ```
220
234
 
221
- Spacing: `48px` top padding, `8px` below h1, `32px` below `.subtitle`. Do **not** use on landing/hero pages (those use custom hero spacing).
235
+ Spacing: `48px` top padding and `8px` between its children; it has no outer margin. To give the content more room than the heading, group the heading and subtitle and put a `space-*` class on the header:
236
+
237
+ ```html
238
+ <div class="container page-header space-xl">
239
+ <div class="space-sm">
240
+ <h1>Page Title</h1>
241
+ <p class="subtitle">Helpful subtitle</p>
242
+ </div>
243
+ <!-- content -->
244
+ </div>
245
+ ```
246
+
247
+ Do **not** use on landing/hero pages (those use custom hero spacing).
222
248
 
223
249
  ### Hero Section (Landing Pages)
224
250
 
225
251
  ```html
226
- <div class="container" style="padding-top: 80px; padding-bottom: 48px;">
252
+ <div class="container space-lg" style="padding-top: 80px; padding-bottom: 48px;">
227
253
  <p class="brand">App Name</p>
228
254
  <h1 class="h1-hero">Main Headline Here</h1>
229
255
  <p class="tagline">
230
256
  A sentence or two explaining what this does. Keep it short.
231
257
  </p>
232
- <div style="display: flex; gap: 12px; flex-wrap: wrap; margin-top: 32px;">
258
+ <div class="gap-sm" style="display: flex; flex-wrap: wrap;">
233
259
  <a href="/signup" class="cta cta-primary">Sign Up</a>
234
260
  <a href="/login" class="cta cta-secondary">Log In</a>
235
261
  </div>
@@ -239,8 +265,8 @@ Spacing: `48px` top padding, `8px` below h1, `32px` below `.subtitle`. Do **not*
239
265
  When the app has a visual mark/icon, use `.brand-lockup` instead of a standalone `.brand`:
240
266
 
241
267
  ```html
242
- <div class="container" style="padding-top: 80px; padding-bottom: 48px;">
243
- <div class="brand-lockup" style="margin-bottom: 32px;">
268
+ <div class="container space-xl" style="padding-top: 80px; padding-bottom: 48px;">
269
+ <div class="brand-lockup">
244
270
  <img src="/icons/app-icon.svg" alt="">
245
271
  <div>
246
272
  <p class="brand" style="font-size: 17px; letter-spacing: 3px;">App Name</p>
@@ -270,10 +296,10 @@ Use in hero sections and landing pages where the app has a visual mark. The `.br
270
296
 
271
297
  ### Card List (3-Color Rotation)
272
298
 
273
- When you have a list of cards, the accent color auto-rotates via `nth-child`:
299
+ When you have a list of cards, the accent color auto-rotates via `nth-child`. Cards have no margin; the list spaces them with `space-md` (or `gap-md` in a grid):
274
300
 
275
301
  ```html
276
- <div class="container">
302
+ <div class="container space-md">
277
303
  <div class="card">
278
304
  <h2>First Card</h2>
279
305
  <p>Description goes here. Color will be accent-1 (pink).</p>
@@ -302,9 +328,11 @@ For explicit accent control (e.g. server-rendered cards), use the `--accent` cus
302
328
  For cards used as form containers or static content that should not lift on hover:
303
329
 
304
330
  ```html
305
- <div class="card-static">
306
- <label for="name">Name</label>
307
- <input type="text" id="name" placeholder="Enter your name">
331
+ <div class="card-static space-md">
332
+ <div class="field">
333
+ <label for="name">Name</label>
334
+ <input type="text" id="name" placeholder="Enter your name">
335
+ </div>
308
336
  </div>
309
337
  ```
310
338
 
@@ -312,7 +340,7 @@ Use `.card-static` instead of `.card` when the card is a container (e.g. auth fo
312
340
 
313
341
  ### Buttons
314
342
 
315
- Size modifiers compose with any color variant. `.btn` works on `<button>` and on `<a>` alike: each size sets its own line height, so a button and a link button of the same size are the same height (41px default, 28px `.btn-sm`, 51px `.btn-lg` and `.cta`) and line up side by side.
343
+ Size modifiers compose with any color variant. `.btn` works on `<button>` and on `<a>` alike: a button and a link button of the same size are the same height (44px default, 32px `.btn-sm`, 52px `.btn-lg` and `.cta`, from the control height tokens) and line up side by side. The height is a minimum: a button grows when its label wraps.
316
344
 
317
345
  ```html
318
346
  <!-- Size modifiers -->
@@ -345,27 +373,86 @@ Size modifiers compose with any color variant. `.btn` works on `<button>` and on
345
373
 
346
374
  ### Forms
347
375
 
376
+ A `.field` holds a label, its control and an optional hint or error, with 8px between them. Labels have no margin of their own; always put a label and its control in a `.field`. Space a column of fields with `space-md`.
377
+
348
378
  ```html
349
- <div class="card">
350
- <label for="name">Your Name</label>
351
- <input type="text" id="name" placeholder="Enter your name" style="margin-bottom: 16px;">
352
- <button class="btn-primary" style="width: 100%;">Submit</button>
353
- </div>
379
+ <form class="card-static space-md">
380
+ <div class="field">
381
+ <label for="name">Your Name</label>
382
+ <input type="text" id="name" placeholder="Enter your name">
383
+ </div>
384
+ <div class="field">
385
+ <label for="email">Email</label>
386
+ <input type="email" id="email" aria-invalid="true" aria-describedby="email-error">
387
+ <p class="field-error" id="email-error">Enter an email address with a domain, like you@example.com.</p>
388
+ </div>
389
+ <div class="field">
390
+ <label for="pass">Password</label>
391
+ <input type="password" id="pass" aria-describedby="pass-hint">
392
+ <p class="field-hint" id="pass-hint">At least 8 characters.</p>
393
+ </div>
394
+ <button class="btn btn-primary btn-block">Submit</button>
395
+ </form>
354
396
  ```
355
397
 
398
+ `.field-hint` is muted small text; `.field-error` is small red text. Connect either to the control with `aria-describedby`. `aria-invalid="true"` on an input, select or textarea gives it a red border, and a red shadow on focus.
399
+
400
+ All controls share one height. Text inputs, selects and the number input are exactly `--p-control-height` (44px) tall; buttons have it as a minimum and grow when a label wraps. A textarea starts at the control height. A field and a button beside it line up by construction.
401
+
356
402
  A `<select>` is styled like the text inputs, with a drawn chevron:
357
403
 
358
404
  ```html
359
- <label for="direction">Direction</label>
360
- <select id="direction"><option>Anticlockwise</option><option>Clockwise</option></select>
405
+ <div class="field">
406
+ <label for="direction">Direction</label>
407
+ <select id="direction"><option>Anticlockwise</option><option>Clockwise</option></select>
408
+ </div>
409
+ ```
410
+
411
+ A `.control-row` puts inputs, fields and buttons on one line, aligned to the bottom, 8px apart. Inputs, selects, textareas, `.input-action`s and fields grow and share the width, starting from `--control-row-min` (12rem); buttons keep their natural width, may wrap their label, and the row wraps when it is narrow.
412
+
413
+ The row aligns its items to the bottom of each `.field`, so a field in a row holds only its label and control. Put the row's hint or error below the row, not inside the `.field`, or the button no longer lines up with the control. Space them with `space-sm` on a wrapper.
414
+
415
+ ```html
416
+ <div class="control-row">
417
+ <input type="text" placeholder="Label" aria-label="Label">
418
+ <button class="btn btn-primary">Create</button>
419
+ </div>
420
+
421
+ <div class="control-row">
422
+ <div class="field">
423
+ <label for="driver">Driver</label>
424
+ <input type="text" id="driver">
425
+ </div>
426
+ <div class="field">
427
+ <label for="car">Car</label>
428
+ <select id="car"><option>Red</option><option>Blue</option></select>
429
+ </div>
430
+ <button class="btn btn-primary">Add Driver</button>
431
+ </div>
432
+
433
+ <div class="space-sm">
434
+ <div class="control-row">
435
+ <div class="field">
436
+ <label for="code">Transponder</label>
437
+ <input type="text" id="code" aria-invalid="true" aria-describedby="code-error">
438
+ </div>
439
+ <button class="btn btn-primary">Assign</button>
440
+ </div>
441
+ <p class="field-error" id="code-error">That transponder is already assigned.</p>
442
+ </div>
361
443
  ```
362
444
 
363
- Inline form (input + button side by side):
445
+ Form grids and column layouts are not part of palace: build them with `gap-*` and `space-*`.
446
+
447
+ A field with a button inside, at its right end (show/hide a password, paste, copy). The input reserves the button's width plus its 8px inset from the edge (`--input-action-width` is the button's width, default `4.75rem`); the button is centred in the input's box and lifts with the input on focus, so they stay aligned. The button is the small control height. Use `.btn-sm`, a real `<button type="button">`, and for a toggle let the visible text and the accessible name change together ("Show" / `aria-label="Show password"` and "Hide" / "Hide password"). Pick one way to say the state: a changing name, or a fixed name with `aria-pressed` — never both.
364
448
 
365
449
  ```html
366
- <div style="display: flex; gap: 8px;">
367
- <input type="text" placeholder="Label" style="flex: 1;">
368
- <button class="btn-primary">Create</button>
450
+ <div class="field">
451
+ <label for="pw">Password</label>
452
+ <div class="input-action">
453
+ <input type="password" id="pw" autocomplete="off">
454
+ <button type="button" class="btn btn-sm btn-outline" aria-label="Show password">Show</button>
455
+ </div>
369
456
  </div>
370
457
  ```
371
458
 
@@ -390,6 +477,8 @@ Inline form (input + button side by side):
390
477
 
391
478
  Use `role="alert"` for error and warning messages (assertive — interrupts the user). Use `role="status"` for success and info messages (polite — announced at the next opportunity). Omit the role for neutral/static messages that don't need to be announced.
392
479
 
480
+ Status boxes carry palace's hard shadow (`--p-shadow-lg` in `--p-text`); leave room for it (4px right and below) inside containers that clip.
481
+
393
482
  Show/hide with JavaScript:
394
483
  ```js
395
484
  function showStatus(el, msg, type) {
@@ -429,14 +518,14 @@ Standard code block:
429
518
  };</pre>
430
519
  ```
431
520
 
432
- Dark reveal panel (for secrets, API keys, terminal output):
521
+ Dark reveal panel (for secrets, API keys, terminal output). It puts 8px between its children:
433
522
  ```html
434
523
  <div class="code-reveal">
435
- <p class="eyebrow" style="color: var(--p-accent-3); margin-bottom: 10px;">
524
+ <p class="eyebrow" style="color: var(--p-accent-3);">
436
525
  Copy this — you won't see it again
437
526
  </p>
438
527
  <code>sk_live_abc123def456</code>
439
- <div style="display: flex; gap: 8px; margin-top: 12px;">
528
+ <div class="gap-sm" style="display: flex;">
440
529
  <button class="btn-secondary">Copy</button>
441
530
  </div>
442
531
  </div>
@@ -471,21 +560,30 @@ Dark reveal panel (for secrets, API keys, terminal output):
471
560
  </div>
472
561
  ```
473
562
 
474
- ### Stack Spacing
563
+ ### Space and Gap
475
564
 
476
- Uniform vertical spacing between direct children. Apply to the parent element.
565
+ `space-*` puts the same vertical space between the direct children of a container. Apply it to the parent.
477
566
 
478
567
  ```html
479
- <div class="stack-md">
568
+ <div class="space-md">
480
569
  <p>First item</p>
481
- <p>16px gap</p>
482
- <p>16px gap</p>
570
+ <p>16px above</p>
571
+ <p>16px above</p>
483
572
  </div>
484
573
  ```
485
574
 
486
- Available: `stack-xs` (4px) · `stack-sm` (8px) · `stack-md` (16px) · `stack-lg` (24px) · `stack-xl` (32px) · `stack-2xl` (48px)
575
+ `gap-*` sets the gap of a flex or grid container, for rows and grids.
487
576
 
488
- For flex/grid parents, prefer `gap` instead. `.stack-*` is for flow-layout contexts where children are block-level elements stacked vertically.
577
+ ```html
578
+ <div class="gap-sm" style="display: flex; flex-wrap: wrap;">
579
+ <span class="tag">One</span>
580
+ <span class="tag">Two</span>
581
+ </div>
582
+ ```
583
+
584
+ Available: `xs` (4px) · `sm` (8px) · `md` (16px) · `lg` (24px) · `xl` (32px) · `2xl` (48px), as `space-md`, `gap-md` and so on.
585
+
586
+ `.stack-*` is a deprecated alias of `space-*` and will be removed in a later release.
489
587
 
490
588
  ### Dividers
491
589
 
@@ -501,7 +599,7 @@ For flex/grid parents, prefer `gap` instead. `.stack-*` is for flow-layout conte
501
599
 
502
600
  ```html
503
601
  <hr class="divider-danger">
504
- <div class="danger-zone">
602
+ <div class="danger-zone space-sm">
505
603
  <h2>Danger Zone</h2>
506
604
  <p>This will permanently delete your account. This cannot be undone.</p>
507
605
  <button class="btn btn-danger btn-block">Delete My Account</button>
@@ -614,9 +712,22 @@ Lists inside `.container` or `.container-narrow` are automatically styled with p
614
712
 
615
713
  ### Dialog
616
714
 
617
- A native `<dialog>` styled as a card: border, purple offset shadow, overlay backdrop. Actions go in a `<menu>`; its buttons share the width equally.
715
+ A native `<dialog>` styled as a card: border, purple offset shadow, overlay backdrop. It spaces its own children — its direct children, or children of a direct `<form>` (`<form method="dialog">`): 4px below the heading, 8px after a paragraph, 24px after the subtitle and before the actions. Actions go in a `<menu>`, one `<li>` per button; the buttons share the width equally. A modal dialog stays centred in any container.
618
716
 
619
717
  ```html
718
+ <dialog>
719
+ <form method="dialog">
720
+ <h2>Title</h2>
721
+ <p class="subtitle">Supporting line</p>
722
+ <p>Body text.</p>
723
+ <menu>
724
+ <li><button class="btn" value="cancel">Cancel</button></li>
725
+ <li><button class="btn btn-primary" value="confirm">Confirm</button></li>
726
+ </menu>
727
+ </form>
728
+ </dialog>
729
+
730
+ <!-- Without a form, the same rhythm on its direct children -->
620
731
  <dialog>
621
732
  <h2>Title</h2>
622
733
  <p class="subtitle">Supporting line</p>
@@ -656,7 +767,7 @@ It is absolutely positioned where the text would have been, with no `top` or `le
656
767
 
657
768
  ### Focus Visible
658
769
 
659
- All interactive elements (buttons, links, inputs, checkboxes, radio buttons, `<summary>`) have `:focus-visible` styles with a `2px` outline in accent-2 (purple) and `2px` offset. Buttons also get a lift + shadow on focus. Text inputs show focus with a purple border and shadow instead. The navbar menu's summary keeps its own cyan ring.
770
+ All interactive elements (buttons, links, inputs, checkboxes, radio buttons, `<summary>`) have `:focus-visible` styles with a `2px` outline in accent-2 (purple) and `2px` offset. Buttons also get a lift + shadow on focus. Text inputs show focus with a purple border and shadow instead; an input with `aria-invalid="true"` shows a red border and shadow. The navbar menu's summary keeps its own cyan ring.
660
771
 
661
772
  ### Reduced Motion
662
773
 
@@ -684,14 +795,16 @@ Use `container-narrow page-header` (420px). No navbar. Centered with top padding
684
795
 
685
796
  ```html
686
797
  <body>
687
- <div class="container-narrow page-header">
688
- <p class="brand">App Name</p>
689
- <h1>Page Title</h1>
690
- <p class="subtitle">Helpful subtitle</p>
691
- <div class="card">
692
- <!-- form contents -->
798
+ <div class="container-narrow page-header space-lg">
799
+ <div class="space-sm">
800
+ <p class="brand">App Name</p>
801
+ <h1>Page Title</h1>
802
+ <p class="subtitle">Helpful subtitle</p>
693
803
  </div>
694
- <p style="margin-top: 20px; text-align: center; font-size: 13px; color: var(--p-text-muted);">
804
+ <form class="card-static space-md">
805
+ <!-- fields -->
806
+ </form>
807
+ <p style="text-align: center; font-size: 13px; color: var(--p-text-muted);">
695
808
  Already in? <a href="/login">Log in</a>
696
809
  </p>
697
810
  </div>
@@ -705,9 +818,11 @@ Navbar + container with `page-header`. Use `divider` between major sections.
705
818
  ```html
706
819
  <body>
707
820
  <nav class="navbar">...</nav>
708
- <div class="container page-header">
709
- <h1>Dashboard</h1>
710
- <p class="subtitle">Manage your stuff</p>
821
+ <div class="container page-header space-xl">
822
+ <div class="space-sm">
823
+ <h1>Dashboard</h1>
824
+ <p class="subtitle">Manage your stuff</p>
825
+ </div>
711
826
  <!-- content sections -->
712
827
  <hr class="divider">
713
828
  <!-- more sections -->
@@ -796,3 +911,16 @@ Palace CSS handles h1, container, and card adjustments automatically. For page-s
796
911
  .cta-row { flex-direction: column; }
797
912
  }
798
913
  ```
914
+
915
+ ## Migrating to 0.14
916
+
917
+ 0.14 moves spacing from components to containers. Check every page:
918
+
919
+ - Rename `stack-*` to `space-*`. `.stack-*` is deprecated: it still works for now, as an alias of `space-*`, and will be removed in a later major version.
920
+ - `.card` and `.card-static` have no margin. Wrap a list of cards in `space-md`, or `gap-md` in a flex or grid container.
921
+ - Labels have no margin. Put each label and its control in a `.field`.
922
+ - `.page-header`, `.stat-row`, `.code-reveal` and dialog parts set no outer margin. Give the space after them from the parent with `space-*` or `gap-*`.
923
+ - A dialog, `.page-header` and `.code-reveal` now space only their own children (a dialog: its direct children, or those of a direct `<form>`), with `:where()` rules, so a `space-*` on them wins. Content nested deeper gets no space from them: give its wrapper a `space-*`.
924
+ - Inputs, selects and the number input are exactly 44px tall. Buttons are at least 44px (32px `.btn-sm`, 52px `.btn-lg` and `.cta`) and grow when a label wraps. Check rows of fields and buttons against them; a `.control-row` lines them up.
925
+ - In a `.control-row`, put a field's hint or error below the row, not inside the `.field`.
926
+ - Replace a margin that spaced siblings with `space-*` or `gap-*` on their parent.