@uncinq/css-components 1.16.2 → 1.16.3
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 +10 -10
- package/css/components/alert.css +5 -0
- package/css/components/panel.css +8 -6
- package/docs/_index.md +13 -11
- package/docs/buttons/_index.md +75 -0
- package/docs/buttons/btn-close.md +58 -0
- package/docs/buttons/btn-filter.md +36 -0
- package/docs/buttons/btn-menu.md +39 -0
- package/docs/buttons/btn-search.md +40 -0
- package/docs/buttons/btn-share.md +33 -0
- package/docs/buttons/btn-toc.md +23 -0
- package/docs/buttons/btn-toggle-video.md +58 -0
- package/docs/buttons/btn.md +95 -0
- package/docs/cascade-layers.md +8 -6
- package/docs/content/_index.md +39 -0
- package/docs/content/alert.md +97 -0
- package/docs/content/badge.md +50 -0
- package/docs/content/banner.md +43 -0
- package/docs/content/card.md +93 -0
- package/docs/content/items.md +76 -0
- package/docs/content/list.md +41 -0
- package/docs/content/media.md +88 -0
- package/docs/content/surtitle.md +43 -0
- package/docs/embeds/_index.md +25 -0
- package/docs/embeds/embed.md +53 -0
- package/docs/embeds/map.md +57 -0
- package/docs/embeds/video.md +53 -0
- package/docs/forms/_index.md +31 -0
- package/docs/forms/form-check.md +77 -0
- package/docs/forms/form.md +67 -0
- package/docs/navigation/_index.md +29 -0
- package/docs/navigation/breadcrumb.md +59 -0
- package/docs/navigation/nav-accessibility.md +67 -0
- package/docs/navigation/nav-title.md +43 -0
- package/docs/navigation/nav.md +65 -0
- package/docs/navigation/pagination.md +77 -0
- package/docs/overlays/_index.md +34 -0
- package/docs/overlays/drawer.md +58 -0
- package/docs/overlays/dropdown.md +87 -0
- package/docs/overlays/modal.md +75 -0
- package/docs/overlays/panel.md +159 -0
- package/docs/utilities/_index.md +21 -0
- package/docs/{utilities.md → utilities/scrollsnap.md} +26 -9
- package/package.json +1 -1
- package/docs/buttons.md +0 -103
- package/docs/content.md +0 -145
- package/docs/embeds.md +0 -66
- package/docs/forms.md +0 -80
- package/docs/navigation.md +0 -93
- package/docs/overlays.md +0 -133
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ The failure is silent. No error is raised, the page simply stays at its mobile v
|
|
|
63
63
|
|
|
64
64
|
## Two import-order rules
|
|
65
65
|
|
|
66
|
-
`components/panel.css`
|
|
66
|
+
`components/panel.css` must come **after** `modal.css` and `drawer.css`: its `.panel-inline-*` variants beat them on source order, because most of the file is `:where()`-wrapped and carries no specificity. Alphabetical order already gives that in `css/index.css`.
|
|
67
67
|
|
|
68
68
|
`utilities/scrollsnap.css` comes after everything, in `@layer utilities`, because it has to win over the grid a component declares for itself.
|
|
69
69
|
|
|
@@ -73,22 +73,22 @@ If you import file by file rather than using the barrel, preserve both positions
|
|
|
73
73
|
|
|
74
74
|
- **No JavaScript.** `modal`, `drawer` and `dropdown` expect a script; each documents the classes and attributes it must toggle.
|
|
75
75
|
- **No `.item`.** This package ships `.card`, a complete implementation. `.item` lives in the Hugolify design system theme.
|
|
76
|
-
- **No
|
|
76
|
+
- **No `.icon` glyphs.** Buttons and `pagination` mask their own from icon tokens, but an `.icon` element you place inside an `alert` or a video toggle is the theme's to render.
|
|
77
77
|
|
|
78
78
|
## Documentation
|
|
79
79
|
|
|
80
80
|
Full documentation: **[socle.uncinq.dev/docs/css-components/](https://socle.uncinq.dev/docs/css-components/)**
|
|
81
81
|
|
|
82
|
-
It is also versioned with the code in [`docs/`](docs/), and ships inside the npm package, so it is readable offline and from `node_modules`:
|
|
82
|
+
It is also versioned with the code in [`docs/`](docs/), one page per component, and ships inside the npm package, so it is readable offline and from `node_modules`:
|
|
83
83
|
|
|
84
84
|
- [Cascade layers](docs/cascade-layers.md) — the two layers, and the `:where()` convention
|
|
85
|
-
- [Buttons](docs/buttons.md)
|
|
86
|
-
- [Overlays](docs/overlays
|
|
87
|
-
- [Content](docs/content.md)
|
|
88
|
-
- [Navigation](docs/navigation.md)
|
|
89
|
-
- [Embeds](docs/embeds.md)
|
|
90
|
-
- [Forms](docs/forms.md)
|
|
91
|
-
- [Utilities](docs/utilities
|
|
85
|
+
- [Buttons](docs/buttons/) — [`.btn`](docs/buttons/btn.md) and the seven specialised buttons
|
|
86
|
+
- [Overlays](docs/overlays/) — [the panel skeleton](docs/overlays/panel.md), [`.modal`](docs/overlays/modal.md), [`.drawer`](docs/overlays/drawer.md), [`.dropdown`](docs/overlays/dropdown.md)
|
|
87
|
+
- [Content](docs/content/) — [`.alert`](docs/content/alert.md), [`.badge`](docs/content/badge.md), [`.banner`](docs/content/banner.md), [`.card`](docs/content/card.md), [`.items`](docs/content/items.md), [`.list`](docs/content/list.md), [`.media`](docs/content/media.md), [`.surtitle`](docs/content/surtitle.md)
|
|
88
|
+
- [Navigation](docs/navigation/) — [`.nav`](docs/navigation/nav.md), [`.nav-title`](docs/navigation/nav-title.md), [skip links](docs/navigation/nav-accessibility.md), [breadcrumb](docs/navigation/breadcrumb.md), [pagination](docs/navigation/pagination.md)
|
|
89
|
+
- [Embeds](docs/embeds/) — [`.embed`](docs/embeds/embed.md), [`.video`](docs/embeds/video.md), [`.map`](docs/embeds/map.md)
|
|
90
|
+
- [Forms](docs/forms/) — [`.form`](docs/forms/form.md), [`.form-check`](docs/forms/form-check.md)
|
|
91
|
+
- [Utilities](docs/utilities/) — [`.scrollsnap`](docs/utilities/scrollsnap.md) and the bleed contract
|
|
92
92
|
|
|
93
93
|
## References
|
|
94
94
|
|
package/css/components/alert.css
CHANGED
package/css/components/panel.css
CHANGED
|
@@ -9,17 +9,19 @@
|
|
|
9
9
|
* The tag picks the strategy, not the class. Either variant can be:
|
|
10
10
|
* - <dialog> opened with showModal() — [open], native ::backdrop, top layer,
|
|
11
11
|
* and the browser handles Escape, the focus trap and inertness;
|
|
12
|
-
* - any element carrying popover="auto", opened by
|
|
13
|
-
*
|
|
12
|
+
* - any element carrying popover="auto", opened with showPopover() by the
|
|
13
|
+
* script you provide — same top layer, same ::backdrop, but no role of its
|
|
14
14
|
* own. Use this when the panel becomes a static region of the page above a
|
|
15
15
|
* breakpoint (see .panel-inline-*): a <dialog> is exposed as role="dialog"
|
|
16
|
-
* even when it is only styled as a sidebar, whereas
|
|
16
|
+
* even when it is only styled as a sidebar, whereas a script can set that
|
|
17
17
|
* role solely while the panel is open.
|
|
18
18
|
*
|
|
19
19
|
* Most selectors here are :where()-wrapped so they carry no specificity: the
|
|
20
20
|
* variants and the host components always win without escalating. The
|
|
21
21
|
* .panel-inline-* variants are the exception — they must beat .modal and
|
|
22
|
-
* .drawer, which they do on source order, so this file
|
|
22
|
+
* .drawer, which they do on source order, so this file must be imported
|
|
23
|
+
* AFTER modal.css and drawer.css. Alphabetical order already gives that in
|
|
24
|
+
* css/index.css; keep it if you import file by file.
|
|
23
25
|
*
|
|
24
26
|
* Structure reads --panel-* tokens; each variant aliases them from its own
|
|
25
27
|
* namespace, so --modal-* and --drawer-* stay the public API.
|
|
@@ -158,8 +160,8 @@
|
|
|
158
160
|
* than patching every host component.
|
|
159
161
|
*
|
|
160
162
|
* The panel must NOT be a <dialog> when one of these is used — see the
|
|
161
|
-
* strategy note at the top of this file.
|
|
162
|
-
*
|
|
163
|
+
* strategy note at the top of this file. Your script must also close an
|
|
164
|
+
* open panel carrying one of these classes when the viewport width
|
|
163
165
|
* changes, since CSS alone cannot release the backdrop, the scroll lock
|
|
164
166
|
* and the inert background. */
|
|
165
167
|
|
package/docs/_index.md
CHANGED
|
@@ -19,15 +19,17 @@ Every rule lives in `@layer components`, except the scroll-snap utility which li
|
|
|
19
19
|
|
|
20
20
|
## The components
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
One page per component, grouped into six sections.
|
|
23
|
+
|
|
24
|
+
| Section | Components |
|
|
23
25
|
| --- | --- |
|
|
24
|
-
| [Buttons](buttons/) | `.btn`
|
|
25
|
-
| [Overlays](overlays/) |
|
|
26
|
-
| [Content](content/) | `.alert
|
|
27
|
-
| [Navigation](navigation/) | `.nav
|
|
28
|
-
| [Embeds](embeds/) | `.embed
|
|
29
|
-
| [Forms](forms/) | `.form
|
|
30
|
-
| [Utilities](utilities/) | `.scrollsnap
|
|
26
|
+
| [Buttons](buttons/) | [`.btn`](buttons/btn/), [`.btn-close`](buttons/btn-close/), [`.btn-menu`](buttons/btn-menu/), [`.btn-search`](buttons/btn-search/), [`.btn-filter`](buttons/btn-filter/), [`.btn-share`](buttons/btn-share/), [`.btn-toc`](buttons/btn-toc/), [`.btn-toggle-video`](buttons/btn-toggle-video/) |
|
|
27
|
+
| [Overlays](overlays/) | [panel](overlays/panel/), [`.modal`](overlays/modal/), [`.drawer`](overlays/drawer/), [`.dropdown`](overlays/dropdown/) |
|
|
28
|
+
| [Content](content/) | [`.alert`](content/alert/), [`.badge`](content/badge/), [`.banner`](content/banner/), [`.card`](content/card/), [`.items`](content/items/), [`.list`](content/list/), [`.media`](content/media/), [`.surtitle`](content/surtitle/) |
|
|
29
|
+
| [Navigation](navigation/) | [`.nav`](navigation/nav/), [`.nav-title`](navigation/nav-title/), [`.nav-accessibility`](navigation/nav-accessibility/), [`.breadcrumb-wrapper`](navigation/breadcrumb/), [`.pagination`](navigation/pagination/) |
|
|
30
|
+
| [Embeds](embeds/) | [`.embed`](embeds/embed/), [`.video`](embeds/video/), [`.map`](embeds/map/) |
|
|
31
|
+
| [Forms](forms/) | [`.form`](forms/form/), [`.form-check`](forms/form-check/) |
|
|
32
|
+
| [Utilities](utilities/) | [`.scrollsnap`](utilities/scrollsnap/) |
|
|
31
33
|
|
|
32
34
|
Read [Cascade layers](cascade-layers/) first if you are wiring this into a project for the first time.
|
|
33
35
|
|
|
@@ -70,11 +72,11 @@ Note that `@uncinq/css-base` is a genuine prerequisite but is **not** declared i
|
|
|
70
72
|
|
|
71
73
|
`css/index.css` imports the components alphabetically, with two deliberate exceptions.
|
|
72
74
|
|
|
73
|
-
`components/
|
|
75
|
+
`components/button.css` comes **before** the seven `btn-*` files rather than after them, because five of those are modifiers on `.btn` and read better in that order.
|
|
74
76
|
|
|
75
77
|
`utilities/scrollsnap.css` comes after everything, in `@layer utilities`, because it has to win over the grid a component declares for itself in `@layer components`.
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
One constraint is easy to break when importing file by file: `panel.css` must come **after** `modal.css` and `drawer.css`. Alphabetical order already gives that, which is why nothing in the barrel looks unusual, but the `.panel-inline-*` variants beat the two host components on source order rather than on specificity. See [Cascade layers](cascade-layers/).
|
|
78
80
|
|
|
79
81
|
## What this package does not include
|
|
80
82
|
|
|
@@ -82,7 +84,7 @@ If you import file by file rather than using the barrel, preserve those two posi
|
|
|
82
84
|
|
|
83
85
|
**No `.item`.** Despite what a couple of source comments still suggest, `.item` is not defined here. It lives in the Hugolify design system theme. This package ships `.card`, a complete implementation in its own right, and `.items`, a grid whose children you provide. See [Content](content/).
|
|
84
86
|
|
|
85
|
-
**No icons
|
|
87
|
+
**No icons for `.icon`.** Components that draw a glyph, the [buttons](buttons/) and [`.pagination`](navigation/pagination/), mask it from an icon token. An `.icon` element you place inside an [alert](content/alert/) or a [video toggle](buttons/btn-toggle-video/) is the theme's to render.
|
|
86
88
|
|
|
87
89
|
## References
|
|
88
90
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Buttons
|
|
4
|
+
description: The .btn base class with its colour, size and style variants, and the seven specialised buttons built on it.
|
|
5
|
+
weight: 2
|
|
6
|
+
icon: hand-index
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Eight files: one base class, and seven single-purpose buttons.
|
|
10
|
+
|
|
11
|
+
| Page | Class | Needs `.btn` |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| [Button](btn/) | `.btn` | — |
|
|
14
|
+
| [Close button](btn-close/) | `.btn-close` | no |
|
|
15
|
+
| [Menu button](btn-menu/) | `.btn-menu` | no |
|
|
16
|
+
| [Search button](btn-search/) | `.btn-search` | yes |
|
|
17
|
+
| [Filter button](btn-filter/) | `.btn-filter` | yes |
|
|
18
|
+
| [Share button](btn-share/) | `.btn-share` | yes |
|
|
19
|
+
| [Table of contents button](btn-toc/) | `.btn-toc` | yes |
|
|
20
|
+
| [Video toggle](btn-toggle-video/) | `.btn-toggle-video` | yes |
|
|
21
|
+
|
|
22
|
+
## Two kinds of specialised button
|
|
23
|
+
|
|
24
|
+
Read the third column before using one, because the two kinds behave differently when the base class is missing.
|
|
25
|
+
|
|
26
|
+
`.btn-close` and `.btn-menu` **redeclare everything they need**. Each `--btn-close-*` and `--btn-menu-*` token falls back to the matching `--btn-*` one, so they look consistent with `.btn` without depending on it. Use them on their own.
|
|
27
|
+
|
|
28
|
+
The other five are **modifiers**. They declare nothing but `--btn-color-*` reassignments and an icon, so omitting `.btn` leaves them almost entirely unstyled.
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<!-- self-contained -->
|
|
32
|
+
<button class="btn-close" aria-label="Close"></button>
|
|
33
|
+
|
|
34
|
+
<!-- modifier on .btn -->
|
|
35
|
+
<button class="btn btn-search" aria-label="Search"></button>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## The icons are masks, not images
|
|
39
|
+
|
|
40
|
+
Six of the seven ship their glyph, drawn on `::before` from the icon tokens in [@uncinq/design-tokens](../../design-tokens/reference/#icon):
|
|
41
|
+
|
|
42
|
+
```css
|
|
43
|
+
&::before {
|
|
44
|
+
background-color: currentColor;
|
|
45
|
+
mask: var(--icon-search) center / var(--btn-search-icon-size, var(--icon-size)) no-repeat;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Because it is a mask over a background colour rather than a background image, the glyph takes `currentColor` and follows the button's text colour through every state, including hover and disabled. Size it globally with `--icon-size`, or per button with `--btn-close-icon-size` and its siblings.
|
|
50
|
+
|
|
51
|
+
`.btn` publishes `--icon-size: var(--btn-font-size)`, so a modifier button scales with its size class for free. A bare `.btn-close` or `.btn-menu` has no such declaration above it and takes whatever `--icon-size` is in scope.
|
|
52
|
+
|
|
53
|
+
[`.btn-toggle-video`](btn-toggle-video/) is the exception: it ships no icon and expects two child elements instead.
|
|
54
|
+
|
|
55
|
+
## None of them names itself
|
|
56
|
+
|
|
57
|
+
When a button has no visible text, give it an accessible name with `aria-label`, or a `.visually-hidden` span from [css-base](../../css-base/base/#accessibility-helpers). No button in this package provides one.
|
|
58
|
+
|
|
59
|
+
## Focus and disabled states
|
|
60
|
+
|
|
61
|
+
`.btn` declares both:
|
|
62
|
+
|
|
63
|
+
```css
|
|
64
|
+
&:focus-visible {
|
|
65
|
+
outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-color-outline);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&:disabled,
|
|
69
|
+
&[aria-disabled='true'] {
|
|
70
|
+
cursor: not-allowed;
|
|
71
|
+
opacity: var(--opacity-disabled);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The five modifiers inherit that through the base class. **`.btn-close` and `.btn-menu` declare neither**, so they fall back to the browser's own focus ring rather than the design system's, and a disabled one looks no different from an enabled one. Add `.btn` alongside them when either matters.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Close button
|
|
4
|
+
description: The self-contained close control used inside panels, alerts and dismissible blocks.
|
|
5
|
+
weight: 2
|
|
6
|
+
icon: x-lg
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A close control. It is **self-contained**: `.btn` is not required.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<button class="btn-close" aria-label="Close"></button>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Its main use is the header of a [panel](../../overlays/panel/), where it usually carries the `.js-dialog-close` hook as well:
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<div class="header">
|
|
19
|
+
<p class="title" id="contactLabel">Contact</p>
|
|
20
|
+
<button class="btn-close js-dialog-close" aria-label="Close"></button>
|
|
21
|
+
</div>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Why it stands alone
|
|
25
|
+
|
|
26
|
+
Every property is declared twice over, with `--btn-close-*` falling back to the matching `--btn-*` token:
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
background-color: var(--btn-close-color-background, var(--btn-control-color-background));
|
|
30
|
+
border-radius: var(--btn-close-border-radius, var(--btn-border-radius));
|
|
31
|
+
font-size: var(--btn-close-font-size, var(--btn-font-size));
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
So it looks like the rest of the button family without inheriting from it, and a single `--btn-close-*` override retargets it alone. Its default palette is `--btn-control-*`, the same quiet colours as [`.btn-control`](../btn/#style-variants), which is what keeps it from competing with the panel's real actions.
|
|
35
|
+
|
|
36
|
+
## The icon
|
|
37
|
+
|
|
38
|
+
Drawn on `::before` as a mask from `--icon-close`, sized by `--btn-close-icon-size` falling back to `--icon-size`. Since nothing above a bare `.btn-close` publishes `--icon-size`, set one explicitly if the ambient value is not what you want:
|
|
39
|
+
|
|
40
|
+
```css
|
|
41
|
+
.btn-close {
|
|
42
|
+
--btn-close-icon-size: var(--font-size-lg);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## What it does not declare
|
|
47
|
+
|
|
48
|
+
No `:focus-visible` rule and no disabled styling. The focus ring is therefore the browser's own rather than the one from the `--focus-*` tokens, and `disabled` changes nothing visually.
|
|
49
|
+
|
|
50
|
+
Add `.btn` alongside it when either matters:
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<button class="btn btn-close" aria-label="Close"></button>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Accessibility
|
|
57
|
+
|
|
58
|
+
The button has no text content, so `aria-label` is not optional. A close button with no accessible name is announced as "button" and nothing more.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Filter button
|
|
4
|
+
description: The filters toggle, a modifier on .btn carrying the filter icon.
|
|
5
|
+
weight: 5
|
|
6
|
+
icon: funnel
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
The control that opens a filters panel. **It requires `.btn`.**
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<button class="btn btn-filter panel-trigger-md js-dialog-toggle"
|
|
13
|
+
data-target="#filters"
|
|
14
|
+
aria-label="Filters"></button>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Like [`.btn-search`](../btn-search/), it declares only a `--btn-color-*` set pointed at the control palette, with `--btn-filter-color-*` in front of it as the override hook, and an icon masked from `--icon-filter`. Everything else comes from `.btn`.
|
|
18
|
+
|
|
19
|
+
## It usually needs a `.panel-trigger-*`
|
|
20
|
+
|
|
21
|
+
A filters panel is the textbook case for [`.panel-inline-*`](../../overlays/panel/#inline-variants): a drawer on mobile, a sidebar column on desktop. Once the panel is a column there is nothing left to open, so the button has to go with it.
|
|
22
|
+
|
|
23
|
+
The two classes are separate and must be paired by hand:
|
|
24
|
+
|
|
25
|
+
| Panel carries | Button carries |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `.panel-inline-sm` | `.panel-trigger-sm` |
|
|
28
|
+
| `.panel-inline-md` | `.panel-trigger-md` |
|
|
29
|
+
| `.panel-inline-lg` | `.panel-trigger-lg` |
|
|
30
|
+
| `.panel-inline-xl` | `.panel-trigger-xl` |
|
|
31
|
+
|
|
32
|
+
A mismatch leaves a button on screen that opens nothing. The reasoning behind the two-class design is on the [panel page](../../overlays/panel/#hiding-the-trigger).
|
|
33
|
+
|
|
34
|
+
## Placement
|
|
35
|
+
|
|
36
|
+
The filter button usually lives in the page heading rather than beside the panel it opens, which is worth remembering when writing layout rules around it. It is an ordinary inline-flex button and takes whatever the surrounding layout gives it.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Menu button
|
|
4
|
+
description: The self-contained menu toggle, typically the burger in a site header.
|
|
5
|
+
weight: 3
|
|
6
|
+
icon: list
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A menu toggle, typically the burger control in a header. Like [`.btn-close`](../btn-close/), it is **self-contained**: `.btn` is not required.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<button class="btn-menu js-dialog-toggle" data-target="#menu" aria-label="Menu"></button>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It is almost always the trigger for a [drawer](../../overlays/drawer/), which is why it usually carries `.js-dialog-toggle` and a `data-target` pointing at the panel's `id`.
|
|
16
|
+
|
|
17
|
+
## Same construction as the close button
|
|
18
|
+
|
|
19
|
+
Every property falls back from `--btn-menu-*` to the matching `--btn-*` token, and the default palette is `--btn-control-*`. See [`.btn-close`](../btn-close/#why-it-stands-alone) for the full explanation, which applies here unchanged.
|
|
20
|
+
|
|
21
|
+
The icon comes from `--icon-menu`, masked on `::before` and sized by `--btn-menu-icon-size` falling back to `--icon-size`.
|
|
22
|
+
|
|
23
|
+
## What it does not declare
|
|
24
|
+
|
|
25
|
+
No `:focus-visible` rule and no disabled styling, exactly as with `.btn-close`. Add `.btn` alongside it when you need either.
|
|
26
|
+
|
|
27
|
+
## Hiding it once the menu is inline
|
|
28
|
+
|
|
29
|
+
A burger that opens a panel which has become a static region of the page is a button that does nothing. Pair the panel's `.panel-inline-*` class with the matching `.panel-trigger-*` on this button:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<button class="btn-menu panel-trigger-md js-dialog-toggle" data-target="#menu" aria-label="Menu"></button>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
See [Hiding the trigger](../../overlays/panel/#hiding-the-trigger).
|
|
36
|
+
|
|
37
|
+
## Accessibility
|
|
38
|
+
|
|
39
|
+
`aria-label` is required, since the button has no text. Keep `aria-expanded` in sync if your script toggles a menu in place rather than opening a panel in the top layer.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Search button
|
|
4
|
+
description: The search toggle, a modifier on .btn carrying the search icon.
|
|
5
|
+
weight: 4
|
|
6
|
+
icon: search
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A search control. **It requires `.btn`**: on its own it is nothing but a colour set and an icon.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<button class="btn btn-search js-dialog-toggle" data-target="#search" aria-label="Search"></button>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What it declares
|
|
16
|
+
|
|
17
|
+
Two things, and nothing else.
|
|
18
|
+
|
|
19
|
+
The `--btn-color-*` set is retargeted at the control palette, with a per-button escape hatch in front of it:
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
--btn-color-background: var(--btn-search-color-background, var(--btn-control-color-background));
|
|
23
|
+
--btn-color-text: var(--btn-search-color-text, var(--btn-control-color-text));
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Override `--btn-search-color-*` to restyle every search button, or `--btn-color-*` directly on one instance.
|
|
27
|
+
|
|
28
|
+
And the glyph, masked from `--icon-search` on `::before`, sized by `--btn-search-icon-size` falling back to `--icon-size`, which `.btn` sets to its own font size.
|
|
29
|
+
|
|
30
|
+
That is the whole file. Geometry, focus, hover, disabled and the transition all come from the base class, which is why omitting it leaves an unstyled button with an icon.
|
|
31
|
+
|
|
32
|
+
## Adding a label
|
|
33
|
+
|
|
34
|
+
The icon sits on `::before`, so any text content lands beside it with `--btn-gap` between the two. No extra class is needed:
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<button class="btn btn-search">Search</button>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Without visible text, `aria-label` is required.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Share button
|
|
4
|
+
description: The share control, a modifier on .btn carrying the share icon.
|
|
5
|
+
weight: 6
|
|
6
|
+
icon: share
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A share control. **It requires `.btn`.**
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<button class="btn btn-share" aria-label="Share">Share</button>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Construction is identical to [`.btn-search`](../btn-search/): a `--btn-color-*` set pointed at the control palette, with `--btn-share-color-*` as the per-variant override, and the glyph masked from `--icon-share`.
|
|
16
|
+
|
|
17
|
+
## It ships no behaviour
|
|
18
|
+
|
|
19
|
+
The class is presentation only. Whether the button calls `navigator.share()`, copies a URL to the clipboard or opens a [dropdown](../../overlays/dropdown/) of destinations is entirely up to your script.
|
|
20
|
+
|
|
21
|
+
If it opens a menu, the dropdown contract applies: the trigger needs `.dropdown-toggle` and `.js-dropdown-toggle`, and its next element sibling must be the `.dropdown-menu`.
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<div class="dropdown">
|
|
25
|
+
<button class="btn btn-share dropdown-toggle js-dropdown-toggle" aria-expanded="false">Share</button>
|
|
26
|
+
<ul class="dropdown-menu">
|
|
27
|
+
<li><a href="...">Bluesky</a></li>
|
|
28
|
+
<li><a href="...">LinkedIn</a></li>
|
|
29
|
+
</ul>
|
|
30
|
+
</div>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Note that `.dropdown-toggle` draws its own chevron on `::after` while `.btn-share` draws the share glyph on `::before`, so the combination gives you both, one on each side of the label.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Table of contents button
|
|
4
|
+
description: The table of contents toggle, a modifier on .btn carrying the toc icon.
|
|
5
|
+
weight: 7
|
|
6
|
+
icon: list-nested
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
The control that opens a table of contents. **It requires `.btn`.**
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<button class="btn btn-toc panel-trigger-lg js-dialog-toggle"
|
|
13
|
+
data-target="#toc"
|
|
14
|
+
aria-label="Table of contents"></button>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Construction is identical to [`.btn-search`](../btn-search/): a `--btn-color-*` set pointed at the control palette, with `--btn-toc-color-*` as the per-variant override, and the glyph masked from `--icon-toc`.
|
|
18
|
+
|
|
19
|
+
## It behaves like the filter button
|
|
20
|
+
|
|
21
|
+
A table of contents follows the same responsive pattern as a filters panel: a drawer on small screens, a sticky column on large ones. Pair the panel's [`.panel-inline-*`](../../overlays/panel/#inline-variants) with the matching `.panel-trigger-*` on this button, or you keep a button that opens nothing at desktop width.
|
|
22
|
+
|
|
23
|
+
Because a table of contents is typically a [`.nav`](../../navigation/nav/) inside the panel's `.content`, the panel's colour handover matters here: going inline hands `--nav-color-text` back to the page, so the links stop using the panel's text colour and take the site's link colour instead.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Video toggle
|
|
4
|
+
description: The play and pause control overlaid on an autoplaying video, driven by the .is-playing state class.
|
|
5
|
+
weight: 8
|
|
6
|
+
icon: play-circle
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A play and pause control overlaid on a video. **It requires `.btn`**, and it is the one specialised button that ships no icon of its own.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<div class="video">
|
|
13
|
+
<video autoplay muted loop playsinline></video>
|
|
14
|
+
<button class="btn btn-toggle-video is-playing" aria-label="Pause">
|
|
15
|
+
<span class="icon-pause"></span>
|
|
16
|
+
<span class="icon-play"></span>
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Why it exists
|
|
22
|
+
|
|
23
|
+
A video that autoplays without native controls must be pausable to meet WCAG. This button is that pause control. If your video carries `controls`, you do not need it.
|
|
24
|
+
|
|
25
|
+
It stays fully transparent, background and border alike, so the video shows through:
|
|
26
|
+
|
|
27
|
+
```css
|
|
28
|
+
--btn-color-background: transparent;
|
|
29
|
+
--btn-color-border: transparent;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The [`.video`](../../embeds/video/) wrapper is what positions it, pinned to the bottom inline-end corner. Used outside that wrapper it is an ordinary transparent button in the flow.
|
|
33
|
+
|
|
34
|
+
## Two children, one visible
|
|
35
|
+
|
|
36
|
+
The whole file beyond the colours is this:
|
|
37
|
+
|
|
38
|
+
```css
|
|
39
|
+
&:not(.is-playing) .icon-pause { display: none; }
|
|
40
|
+
&.is-playing .icon-play { display: none; }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
So the button expects **two** child elements and shows exactly one. The glyphs themselves are not provided here: `.icon-pause` and `.icon-play` are whatever your theme's icon system renders.
|
|
44
|
+
|
|
45
|
+
## What your script owns
|
|
46
|
+
|
|
47
|
+
Two things, and getting either wrong breaks the control.
|
|
48
|
+
|
|
49
|
+
**Toggling `.is-playing`** in step with the video's actual state, including when playback stops for a reason the script did not initiate.
|
|
50
|
+
|
|
51
|
+
**Keeping the accessible name in sync.** The control means *pause* while playing and *play* while paused, so a fixed `aria-label` is wrong half the time.
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
button.classList.toggle('is-playing', !video.paused);
|
|
55
|
+
button.setAttribute('aria-label', video.paused ? 'Play' : 'Pause');
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Note that `.is-playing` describes the **video**, not the button, which is why the class stays on while the video runs rather than flipping on click.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Button
|
|
4
|
+
description: The .btn base class, its ten colour variants, five sizes and three style variants.
|
|
5
|
+
weight: 1
|
|
6
|
+
icon: hand-index-thumb
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
`.btn` is the base class. It works on `<button>`, `<a>` and `<input type="submit">`, and reads `--btn-*` from [@uncinq/component-tokens](../../../component-tokens/reference/).
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<button class="btn">Send</button>
|
|
13
|
+
<a class="btn btn-secondary" href="/contact">Contact</a>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Variants compose freely: `class="btn btn-danger btn-sm btn-ghost"`.
|
|
17
|
+
|
|
18
|
+
## Colour variants
|
|
19
|
+
|
|
20
|
+
| Class | Intent |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `.btn-primary` | Primary action |
|
|
23
|
+
| `.btn-secondary` | Secondary action |
|
|
24
|
+
| `.btn-brand` | Brand-coloured |
|
|
25
|
+
| `.btn-neutral` | Neutral emphasis |
|
|
26
|
+
| `.btn-dark` | Dark surface |
|
|
27
|
+
| `.btn-light` | Light surface |
|
|
28
|
+
| `.btn-success` | Confirmation |
|
|
29
|
+
| `.btn-danger` | Destructive action |
|
|
30
|
+
| `.btn-warning` | Warning |
|
|
31
|
+
| `.btn-info` | Informational |
|
|
32
|
+
|
|
33
|
+
Each sets four properties together: background, border, their hover values, and the text colour.
|
|
34
|
+
|
|
35
|
+
```css
|
|
36
|
+
.btn-danger {
|
|
37
|
+
--btn-color-background: var(--color-danger);
|
|
38
|
+
--btn-color-background-hover: var(--color-danger-strong);
|
|
39
|
+
--btn-color-border: var(--color-danger);
|
|
40
|
+
--btn-color-border-hover: var(--color-danger-strong);
|
|
41
|
+
--btn-color-text: var(--color-text-on-danger);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The text colour always comes from the matching `--color-text-on-*` semantic token, so contrast survives a change to the underlying colour. That pairing is the reason to rebrand by redefining `--color-brand` rather than `--btn-color-background`.
|
|
46
|
+
|
|
47
|
+
`.btn-primary` and `.btn-brand` resolve to exactly the same values, as do `.btn-secondary` and `.btn-neutral`. The intent names exist so a page can say *primary action* without deciding which colour that is.
|
|
48
|
+
|
|
49
|
+
## Sizes
|
|
50
|
+
|
|
51
|
+
| Class | Use |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `.btn-xs` | Dense UI, inline controls |
|
|
54
|
+
| `.btn-sm` | Compact |
|
|
55
|
+
| `.btn-md` | Default, can be omitted |
|
|
56
|
+
| `.btn-lg` | Prominent |
|
|
57
|
+
| `.btn-xl` | Hero call to action |
|
|
58
|
+
|
|
59
|
+
A size class sets `--btn-font-size` along with the block and inline padding, all three from the same rung of the scale. Since `.btn` publishes `--icon-size: var(--btn-font-size)`, any icon inside scales with it.
|
|
60
|
+
|
|
61
|
+
## Style variants
|
|
62
|
+
|
|
63
|
+
| Class | Effect |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `.btn-ghost` | Fully transparent, background and border alike, text in `--color-text` |
|
|
66
|
+
| `.btn-link` | Renders as an underlined link |
|
|
67
|
+
| `.btn-control` | Form control styling, for buttons sitting next to inputs |
|
|
68
|
+
|
|
69
|
+
`.btn-control` maps the `--btn-color-*` set onto the `--btn-control-*` tokens, which is the same palette the [close](../btn-close/) and [menu](../btn-menu/) buttons use by default. It is what makes a button read as part of a form rather than as a call to action.
|
|
70
|
+
|
|
71
|
+
### How `.btn-link` gets its underline
|
|
72
|
+
|
|
73
|
+
Every button already carries one. The base class declares `text-decoration-line: var(--btn-text-decoration-line)`, which the tokens set to `underline`, and paints it with `--btn-color-text-decoration`, which defaults to `transparent`.
|
|
74
|
+
|
|
75
|
+
`.btn-link` only changes the colour:
|
|
76
|
+
|
|
77
|
+
```css
|
|
78
|
+
.btn-link {
|
|
79
|
+
--btn-color-text-decoration: initial; /* revealed, takes currentColor */
|
|
80
|
+
|
|
81
|
+
@media (hover: hover) and (pointer: fine) {
|
|
82
|
+
&:hover {
|
|
83
|
+
--btn-color-text-decoration: transparent; /* hidden again */
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The underline is therefore always in the layout and never shifts the text, which is why hovering one does not nudge the label. If you write your own link-like variant, change the decoration colour rather than the decoration line, for the same reason.
|
|
90
|
+
|
|
91
|
+
## States
|
|
92
|
+
|
|
93
|
+
`:focus-visible` draws the outline from the `--focus-*` tokens. `:disabled` and `[aria-disabled='true']` are treated alike, both getting `cursor: not-allowed` and `--opacity-disabled`.
|
|
94
|
+
|
|
95
|
+
Hover is guarded by `@media (hover: hover) and (pointer: fine)`, so a touch device never leaves a button stuck in its hover colours after a tap. The transition is guarded by `prefers-reduced-motion`.
|
package/docs/cascade-layers.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
isIndex: false
|
|
3
3
|
title: Cascade layers
|
|
4
|
-
description: The two layers this package owns, why panel.css
|
|
4
|
+
description: The two layers this package owns, why panel.css follows modal and drawer, and how to override a component cleanly.
|
|
5
5
|
weight: 1
|
|
6
6
|
icon: stack
|
|
7
7
|
---
|
|
@@ -23,18 +23,20 @@ CSS fixes a layer's position the first time its name is seen, and later re-decla
|
|
|
23
23
|
|
|
24
24
|
## Why `utilities` is last
|
|
25
25
|
|
|
26
|
-
`.scrollsnap` changes the `grid-template-columns` that a component such as `.items` declares for itself in `@layer components`. A later layer beats an earlier one regardless of specificity, so putting it in `utilities` is what lets a single class opt an existing component into carousel behaviour without rewriting the component.
|
|
26
|
+
[`.scrollsnap`](../utilities/scrollsnap/) changes the `grid-template-columns` that a component such as [`.items`](../content/items/) declares for itself in `@layer components`. A later layer beats an earlier one regardless of specificity, so putting it in `utilities` is what lets a single class opt an existing component into carousel behaviour without rewriting the component.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
It is also what makes `revert-layer` work in the responsive variants: handing a property back to the layer below only means anything when there is a layer below holding the component's own value.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
This is the reason it is a utility rather than a component rule: it is a modifier applied to something else, not a thing of its own.
|
|
31
|
+
|
|
32
|
+
## Why `panel.css` follows `modal.css` and `drawer.css`
|
|
31
33
|
|
|
32
34
|
Inside `@layer components`, source order breaks ties between rules of equal specificity. Two facts combine here:
|
|
33
35
|
|
|
34
|
-
1. Most of `panel.css` is wrapped in `:where()`, so it carries **zero specificity**. That is deliberate: it lets `.modal`, `.drawer` and your own overrides win without escalating.
|
|
36
|
+
1. Most of [`panel.css`](../overlays/panel/) is wrapped in `:where()`, so it carries **zero specificity**. That is deliberate: it lets `.modal`, `.drawer` and your own overrides win without escalating.
|
|
35
37
|
2. The `.panel-inline-*` variants must beat `.modal` and `.drawer`, and they are plain class selectors.
|
|
36
38
|
|
|
37
|
-
Because those variants have no specificity advantage over the host components, they rely on arriving later in the file.
|
|
39
|
+
Because those variants have no specificity advantage over the host components, they rely on arriving later in the file. The alphabetical import order in `css/index.css` already puts `panel.css` after both, so nothing there looks out of the ordinary, but the constraint is real.
|
|
38
40
|
|
|
39
41
|
If you import files individually, keep `panel.css` after `modal.css` and `drawer.css`.
|
|
40
42
|
|