palace-ui 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,4 +13,10 @@ rotation, chunky offset shadows. One stylesheet, no build step, no JavaScript.
13
13
  - `SPEC.md`: the design specification, written to be followed exactly by people and AI agents alike
14
14
  - `catalog.html`: every component on one page; open it in a browser
15
15
 
16
+ ## Utilities
17
+
18
+ - `.visually-hidden`: text for screen readers only. A scrolling container
19
+ (`overflow-x: auto`) that holds it needs `position: relative`, or the hidden
20
+ text widens the page.
21
+
16
22
  MIT licensed.
package/SPEC.md CHANGED
@@ -138,6 +138,37 @@ Every page follows this structure:
138
138
  </nav>
139
139
  ```
140
140
 
141
+ A navbar menu for the signed-in user is a `<details class="navbar-menu">`: the summary looks like `.navbar-user`, and the panel's links and buttons are its items. A form in the panel (sign out) sits under a thin rule. It works without JavaScript; a script can add Escape and closing from outside.
142
+
143
+ ```html
144
+ <nav class="navbar">
145
+ <a href="/" class="navbar-brand">App Name</a>
146
+ <div class="navbar-right">
147
+ <details class="navbar-menu">
148
+ <summary><span class="visually-hidden">Account: </span>Username</summary>
149
+ <div class="navbar-menu-panel">
150
+ <p class="navbar-menu-label" aria-hidden="true">Your teams</p>
151
+ <ul aria-label="Your teams"><li><a href="/a" aria-current="true">Team A</a></li></ul>
152
+ <a href="/account">Account</a>
153
+ <form method="post" action="/logout"><button type="submit">Sign out</button></form>
154
+ </div>
155
+ </details>
156
+ </div>
157
+ </nav>
158
+ ```
159
+
160
+ Group items with `.navbar-menu-group`: a thin rule above, like the one before sign out, and an optional `.navbar-menu-label` as its heading. A setting that is switched on and off is a `button.navbar-menu-setting` with `aria-pressed`: its name sits on the left and a small square switch on the right, pink when pressed. Toggle `aria-pressed` with a script, or submit a form that renders the new state. Use it for on/off settings, not for links.
161
+
162
+ ```html
163
+ <div class="navbar-menu-group" role="group" aria-labelledby="menu-settings">
164
+ <p class="navbar-menu-label" id="menu-settings">Settings</p>
165
+ <button type="button" class="navbar-menu-setting" aria-pressed="true">Notifications</button>
166
+ <button type="button" class="navbar-menu-setting" aria-pressed="false">Sound</button>
167
+ </div>
168
+ ```
169
+
170
+ `.visually-hidden` hides text visually and keeps it for screen readers (see Visually Hidden).
171
+
141
172
  With an icon beside the brand name (`.navbar-brand` uses `inline-flex` layout with `gap: 10px`; images/SVGs are auto-sized to 24×24):
142
173
 
143
174
  ```html
@@ -281,7 +312,7 @@ Use `.card-static` instead of `.card` when the card is a container (e.g. auth fo
281
312
 
282
313
  ### Buttons
283
314
 
284
- Size modifiers compose with any color variant:
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.
285
316
 
286
317
  ```html
287
318
  <!-- Size modifiers -->
@@ -605,11 +636,27 @@ Inline keyboard key: bordered, uppercase mono, with a small pink offset shadow.
605
636
  <kbd>Ctrl</kbd> + <kbd>Z</kbd>
606
637
  ```
607
638
 
639
+ ### Visually Hidden
640
+
641
+ `.visually-hidden` keeps text for screen readers and draws nothing: a label for an icon-only column, the name of the row an "Edit" button acts on.
642
+
643
+ ```html
644
+ <button class="btn btn-sm btn-outline">Edit<span class="visually-hidden"> Ada</span></button>
645
+ ```
646
+
647
+ It is absolutely positioned where the text would have been, with no `top` or `left`, so it stays where the text is and a screen reader moving to it does not jump the page. That means it still counts toward the width of its containing block. **A scrolling container (`overflow-x: auto`) holding `.visually-hidden` text needs `position: relative`**; without it the hidden text's containing block is the page, and a wide table makes the whole page scroll sideways on a phone.
648
+
649
+ ```html
650
+ <div style="overflow-x: auto; position: relative;">
651
+ <table>…<th><span class="visually-hidden">Actions</span></th>…</table>
652
+ </div>
653
+ ```
654
+
608
655
  ## Accessibility
609
656
 
610
657
  ### Focus Visible
611
658
 
612
- All interactive elements (buttons, links, inputs) have `:focus-visible` styles with a `2px` outline in accent-2 (purple) and `2px` offset. Buttons also get a lift + shadow on focus.
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.
613
660
 
614
661
  ### Reduced Motion
615
662
 
package/catalog.html CHANGED
@@ -361,6 +361,17 @@
361
361
  <button class="btn btn-lg btn-primary">Large</button>
362
362
  </div>
363
363
 
364
+ <h3 style="margin-bottom: 12px;">Buttons and links side by side</h3>
365
+ <p style="margin-bottom: 12px;">A <code>&lt;button&gt;</code> and an <code>&lt;a class="btn"&gt;</code> of the same size are the same height and line up.</p>
366
+ <div data-source id="btn-match" style="display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; margin-bottom: 24px;">
367
+ <button class="btn btn-sm btn-outline">Button</button>
368
+ <a href="#" class="btn btn-sm btn-outline">Link</a>
369
+ <button class="btn btn-primary">Button</button>
370
+ <a href="#" class="btn btn-primary">Link</a>
371
+ <button class="btn btn-lg btn-secondary">Button</button>
372
+ <a href="#" class="btn btn-lg btn-secondary">Link</a>
373
+ </div>
374
+
364
375
  <h3 style="margin-bottom: 12px;">Disabled</h3>
365
376
  <div data-source style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 24px;">
366
377
  <button class="btn btn-primary" disabled>Disabled</button>
@@ -462,6 +473,23 @@
462
473
  </div>
463
474
  </div>
464
475
 
476
+ <h3 style="margin-bottom: 12px;">Checkboxes, radio buttons and disclosures</h3>
477
+ <p style="margin-bottom: 12px;">Tab through them: each gets the same focus ring as links and buttons.</p>
478
+ <div data-source id="focus-controls" class="card-static" style="margin-bottom: 16px;">
479
+ <div class="stack-md">
480
+ <label><input type="checkbox" checked> Show lap times</label>
481
+ <fieldset style="border: 0;">
482
+ <legend class="visually-hidden">Direction</legend>
483
+ <label><input type="radio" name="cat-dir" checked> Anticlockwise</label>
484
+ <label><input type="radio" name="cat-dir"> Clockwise</label>
485
+ </fieldset>
486
+ <details>
487
+ <summary>More options</summary>
488
+ <p>Hidden until the summary is opened.</p>
489
+ </details>
490
+ </div>
491
+ </div>
492
+
465
493
  <h3 style="margin-bottom: 12px;">Inline Form</h3>
466
494
  <div data-source style="display: flex; gap: 8px;">
467
495
  <input type="text" placeholder="Add a label" aria-label="Add a label" style="flex: 1;">
@@ -522,6 +550,20 @@
522
550
  </tbody>
523
551
  </table>
524
552
  </div>
553
+
554
+ <h3 style="margin-bottom: 12px; margin-top: 24px;">Scrolling table</h3>
555
+ <p style="margin-bottom: 12px;">A wide table scrolls inside its wrapper. The wrapper is <code>position: relative</code>, so <code>.visually-hidden</code> text in it stays inside it and the page never grows wider.</p>
556
+ <div data-source id="scroll-table" style="overflow-x: auto; position: relative;">
557
+ <table style="min-width: 640px;">
558
+ <thead>
559
+ <tr><th>Driver</th><th>Laps</th><th>Best</th><th>Last</th><th><span class="visually-hidden">Actions</span></th></tr>
560
+ </thead>
561
+ <tbody>
562
+ <tr><td>Ada</td><td>12</td><td>4.812</td><td>4.977</td><td><button class="btn btn-sm btn-outline">Edit<span class="visually-hidden"> Ada</span></button></td></tr>
563
+ <tr><td>Grace</td><td>11</td><td>4.903</td><td>5.120</td><td><button class="btn btn-sm btn-outline">Edit<span class="visually-hidden"> Grace</span></button></td></tr>
564
+ </tbody>
565
+ </table>
566
+ </div>
525
567
  </div>
526
568
 
527
569
  <!-- ════════════════════════════════════════ CODE ════════════════════════════════════════ -->
@@ -793,6 +835,29 @@
793
835
  </nav>
794
836
  </div>
795
837
 
838
+ <h3 style="margin-bottom: 12px;">With a navbar menu (open it)</h3>
839
+ <div data-source style="margin-bottom: 24px; padding-bottom: 300px;">
840
+ <nav class="navbar">
841
+ <a href="#" class="navbar-brand">App Name</a>
842
+ <div class="navbar-right">
843
+ <details class="navbar-menu">
844
+ <summary><span class="visually-hidden">Account: </span>Username</summary>
845
+ <div class="navbar-menu-panel">
846
+ <p class="navbar-menu-label" aria-hidden="true">Your teams</p>
847
+ <ul aria-label="Your teams"><li><a href="#" aria-current="true">Team A</a></li><li><a href="#">Team B</a></li></ul>
848
+ <a href="#">Account</a>
849
+ <div class="navbar-menu-group" role="group" aria-labelledby="cat-menu-settings">
850
+ <p class="navbar-menu-label" id="cat-menu-settings">Settings</p>
851
+ <button type="button" class="navbar-menu-setting" aria-pressed="true">Notifications</button>
852
+ <button type="button" class="navbar-menu-setting" aria-pressed="false">Sound</button>
853
+ </div>
854
+ <form><button type="button">Sign out</button></form>
855
+ </div>
856
+ </details>
857
+ </div>
858
+ </nav>
859
+ </div>
860
+
796
861
  <h3 style="margin-bottom: 12px;">Light (.navbar-light)</h3>
797
862
  <div data-source>
798
863
  <nav class="navbar navbar-light" style="border: 2px solid var(--p-border);">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palace-ui",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Retro-technical design system. Hard edges, vibrant gradients, chunky shadows.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/palace.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! palace-ui v0.11.0 | MIT License | fonts: SIL OFL 1.1, see fonts/ */
1
+ /*! palace-ui v0.12.0 | MIT License | fonts: SIL OFL 1.1, see fonts/ */
2
2
 
3
3
  /* ══════════════════════════════════════════
4
4
  FONTS (self-hosted; no third-party requests)
@@ -114,6 +114,22 @@
114
114
  /* The hidden attribute always hides, even on elements palace gives a display (buttons, .btn). */
115
115
  [hidden] { display: none !important; }
116
116
 
117
+ /* Text for screen readers only: not drawn, still read. It is absolutely
118
+ positioned where it would have been, so inside a scrolling container that is
119
+ not positioned it still widens the page: give that container
120
+ position: relative. */
121
+ .visually-hidden {
122
+ position: absolute;
123
+ width: 1px;
124
+ height: 1px;
125
+ margin: -1px;
126
+ padding: 0;
127
+ border: 0;
128
+ overflow: hidden;
129
+ clip-path: inset(50%);
130
+ white-space: nowrap;
131
+ }
132
+
117
133
  body {
118
134
  font-family: var(--p-font);
119
135
  color: var(--p-text);
@@ -311,12 +327,17 @@ button, .btn {
311
327
  transition: var(--p-transition);
312
328
  text-decoration: none;
313
329
  display: inline-block;
330
+ /* One line height for <button> and <a class="btn">, so both are the same
331
+ height: a button does not inherit the body's line height, a link does. */
332
+ line-height: calc(17 / 13);
333
+ text-align: center;
314
334
  }
315
335
 
316
336
  /* Size modifiers */
317
337
  .btn-sm {
318
338
  padding: 6px 14px;
319
339
  font-size: 11px;
340
+ line-height: calc(14 / 11);
320
341
  letter-spacing: 1px;
321
342
  border-width: var(--p-border-thin);
322
343
  }
@@ -324,6 +345,7 @@ button, .btn {
324
345
  .btn-lg {
325
346
  padding: 14px 28px;
326
347
  font-size: 15px;
348
+ line-height: calc(19 / 15);
327
349
  letter-spacing: 1px;
328
350
  }
329
351
 
@@ -387,6 +409,8 @@ button, .btn {
387
409
  padding: 14px 28px;
388
410
  font-family: var(--p-font);
389
411
  font-size: 15px;
412
+ line-height: calc(19 / 15);
413
+ text-align: center;
390
414
  font-weight: 700;
391
415
  text-transform: uppercase;
392
416
  letter-spacing: 1px;
@@ -608,6 +632,15 @@ textarea:focus {
608
632
  transform: var(--p-lift-sm);
609
633
  }
610
634
 
635
+ /* Checkboxes, radio buttons and disclosure summaries: the focus ring of links
636
+ and buttons, without the lift. */
637
+ input[type="checkbox"]:focus-visible,
638
+ input[type="radio"]:focus-visible,
639
+ summary:focus-visible {
640
+ outline: var(--p-border-width) solid var(--p-accent-2);
641
+ outline-offset: 2px;
642
+ }
643
+
611
644
 
612
645
  /* ══════════════════════════════════════════
613
646
  STATUS MESSAGES
@@ -720,6 +753,11 @@ textarea:focus {
720
753
  color: var(--p-accent-2);
721
754
  }
722
755
 
756
+ .navbar-light .navbar-menu > summary {
757
+ color: var(--p-accent-2);
758
+ border-color: var(--p-border);
759
+ }
760
+
723
761
  .navbar-light .btn {
724
762
  color: var(--p-text-secondary);
725
763
  border-color: var(--p-border);
@@ -731,6 +769,135 @@ textarea:focus {
731
769
  }
732
770
 
733
771
 
772
+ /* Navbar menu: a <details> whose summary looks like .navbar-user and opens a
773
+ panel in the dialog card language. Works without JavaScript; a script may
774
+ add Escape and closing from outside. Links and buttons in the panel are its
775
+ items; a form in it (e.g. Sign out) sits under a thin rule. */
776
+ .navbar-menu { position: relative; }
777
+ .navbar-menu > summary {
778
+ position: relative;
779
+ display: block;
780
+ box-sizing: border-box;
781
+ max-width: min(14rem, 40vw);
782
+ /* The label truncates; the caret after it, in the right padding, never does. */
783
+ padding: 6px 28px 6px 14px;
784
+ overflow: hidden;
785
+ text-overflow: ellipsis;
786
+ white-space: nowrap;
787
+ border: var(--p-border-thin) solid var(--p-border-dark);
788
+ font: 600 11px/1.4 var(--p-font);
789
+ letter-spacing: 1px;
790
+ text-transform: uppercase;
791
+ color: var(--p-accent-3);
792
+ list-style: none;
793
+ cursor: pointer;
794
+ }
795
+ .navbar-menu > summary::-webkit-details-marker { display: none; }
796
+ .navbar-menu > summary::after {
797
+ content: '';
798
+ position: absolute;
799
+ right: 12px;
800
+ top: 50%;
801
+ margin-top: -2px;
802
+ border: 4px solid transparent;
803
+ border-top-color: currentColor;
804
+ border-bottom: 0;
805
+ }
806
+ .navbar-menu > summary:hover,
807
+ .navbar-menu[open] > summary { color: var(--p-accent-1); border-color: var(--p-accent-1); }
808
+ .navbar-menu > summary:focus-visible { outline: 2px solid var(--p-accent-3); outline-offset: 2px; }
809
+
810
+ /* Right-aligned under the summary: a navbar menu sits at the navbar's right. */
811
+ .navbar-menu-panel {
812
+ position: absolute;
813
+ right: 0;
814
+ top: calc(100% + 6px);
815
+ z-index: 100;
816
+ min-width: 14rem;
817
+ max-width: calc(100vw - 2 * var(--p-space-md));
818
+ display: grid;
819
+ padding: var(--p-space-xs) 0;
820
+ background: var(--p-surface);
821
+ color: var(--p-text);
822
+ border: var(--p-border-width) solid var(--p-text);
823
+ box-shadow: var(--p-shadow-md) var(--p-accent-2);
824
+ }
825
+ .navbar-menu-panel ul { list-style: none; margin: 0; padding: 0; display: grid; }
826
+ .navbar-menu-label {
827
+ margin: 0;
828
+ padding: var(--p-space-sm) var(--p-space-md) var(--p-space-xs);
829
+ font-size: 11px;
830
+ font-weight: 600;
831
+ letter-spacing: 1px;
832
+ text-transform: uppercase;
833
+ color: var(--p-text-faint);
834
+ }
835
+ .navbar-menu-panel a,
836
+ .navbar-menu-panel button {
837
+ display: block;
838
+ width: 100%;
839
+ box-sizing: border-box;
840
+ padding: var(--p-space-sm) var(--p-space-md);
841
+ border: 0;
842
+ background: none;
843
+ font: 600 13px/1.4 var(--p-font);
844
+ text-transform: none;
845
+ letter-spacing: normal;
846
+ color: var(--p-text);
847
+ text-align: left;
848
+ text-decoration: none;
849
+ cursor: pointer;
850
+ /* No transition: a closed <details> does not render its content, so a
851
+ transition started there would never finish. */
852
+ transition: none;
853
+ }
854
+ .navbar-menu-panel a:hover,
855
+ .navbar-menu-panel button:hover { background: var(--p-hover-bg); transform: none; box-shadow: none; }
856
+ /* One focus style for links and buttons: the button lift would spill past the panel. */
857
+ .navbar-menu-panel a:focus-visible,
858
+ .navbar-menu-panel button:focus-visible {
859
+ outline: 2px solid var(--p-accent-2);
860
+ outline-offset: -2px;
861
+ color: var(--p-accent-2);
862
+ transform: none;
863
+ box-shadow: none;
864
+ }
865
+ /* The current item: a pink bar, beside any focus outline, never instead of it. */
866
+ .navbar-menu-panel a[aria-current],
867
+ .navbar-menu-panel a[aria-current]:focus-visible { box-shadow: inset 3px 0 0 var(--p-accent-1); }
868
+ .navbar-menu-panel form,
869
+ .navbar-menu-group {
870
+ margin: var(--p-space-xs) 0 0;
871
+ padding-top: var(--p-space-xs);
872
+ border-top: var(--p-border-thin) solid var(--p-border);
873
+ }
874
+ /* A group of items under a thin rule, with an optional .navbar-menu-label. */
875
+ .navbar-menu-group { display: grid; }
876
+ .navbar-menu-group:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
877
+ /* A setting: its name on the left, a switch on the right, on and off from
878
+ aria-pressed. The switch is square-edged like everything else. */
879
+ .navbar-menu-panel .navbar-menu-setting {
880
+ display: flex;
881
+ align-items: center;
882
+ justify-content: space-between;
883
+ gap: var(--p-space-md);
884
+ }
885
+ .navbar-menu-setting::after {
886
+ content: '';
887
+ flex: none;
888
+ width: 28px;
889
+ height: 16px;
890
+ border: var(--p-border-width) solid var(--p-text);
891
+ background: var(--p-surface) linear-gradient(var(--p-text), var(--p-text)) no-repeat left 2px center / 8px 8px;
892
+ }
893
+ .navbar-menu-setting[aria-pressed="true"]::after {
894
+ background: var(--p-accent-1) linear-gradient(var(--p-text), var(--p-text)) no-repeat right 2px center / 8px 8px;
895
+ }
896
+ @media (forced-colors: active) {
897
+ .navbar-menu-setting::after { forced-color-adjust: none; border-color: CanvasText; background-color: Canvas; background-image: linear-gradient(CanvasText, CanvasText); }
898
+ .navbar-menu-setting[aria-pressed="true"]::after { background-color: Highlight; }
899
+ }
900
+
734
901
  /* ══════════════════════════════════════════
735
902
  TABLES
736
903
  ══════════════════════════════════════════ */