@uncinq/css-components 1.16.2 → 1.16.4
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 +11 -10
- package/css/components/alert.css +5 -0
- package/css/components/panel.css +8 -6
- package/docs/_index.md +0 -84
- package/docs/buttons/_index.md +7 -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/buttons/overview.md +75 -0
- package/docs/cascade-layers.md +9 -7
- package/docs/content/_index.md +7 -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/overview.md +39 -0
- package/docs/content/surtitle.md +43 -0
- package/docs/embeds/_index.md +7 -0
- package/docs/embeds/embed.md +53 -0
- package/docs/embeds/map.md +57 -0
- package/docs/embeds/overview.md +25 -0
- package/docs/embeds/video.md +53 -0
- package/docs/forms/_index.md +7 -0
- package/docs/forms/form-check.md +77 -0
- package/docs/forms/form.md +67 -0
- package/docs/forms/overview.md +31 -0
- package/docs/navigation/_index.md +7 -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/overview.md +29 -0
- package/docs/navigation/pagination.md +77 -0
- package/docs/overlays/_index.md +7 -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/overview.md +34 -0
- package/docs/overlays/panel.md +159 -0
- package/docs/overview.md +93 -0
- package/docs/utilities/_index.md +7 -0
- package/docs/utilities/overview.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,23 @@ 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
|
+
- [Overview](docs/overview.md) — installation, import order, what the package leaves out
|
|
84
85
|
- [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
|
|
86
|
+
- [Buttons](docs/buttons/) — [`.btn`](docs/buttons/btn.md) and the seven specialised buttons
|
|
87
|
+
- [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)
|
|
88
|
+
- [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)
|
|
89
|
+
- [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)
|
|
90
|
+
- [Embeds](docs/embeds/) — [`.embed`](docs/embeds/embed.md), [`.video`](docs/embeds/video.md), [`.map`](docs/embeds/map.md)
|
|
91
|
+
- [Forms](docs/forms/) — [`.form`](docs/forms/form.md), [`.form-check`](docs/forms/form-check.md)
|
|
92
|
+
- [Utilities](docs/utilities/) — [`.scrollsnap`](docs/utilities/scrollsnap.md) and the bleed contract
|
|
92
93
|
|
|
93
94
|
## References
|
|
94
95
|
|
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
|
@@ -5,87 +5,3 @@ description: Framework-agnostic CSS component implementations, driven entirely b
|
|
|
5
5
|
weight: 5
|
|
6
6
|
icon: grid
|
|
7
7
|
---
|
|
8
|
-
|
|
9
|
-
`@uncinq/css-components` is the top of the stack: 29 components and 1 utility, written as plain CSS, reading their values from [@uncinq/component-tokens](../component-tokens/) and adding no values of their own.
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
@uncinq/design-tokens primitive + semantic values
|
|
13
|
-
@uncinq/component-tokens component-scoped values
|
|
14
|
-
@uncinq/css-base reset, native elements, layouts
|
|
15
|
-
@uncinq/css-components ← this package
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Every rule lives in `@layer components`, except the scroll-snap utility which lives in `@layer utilities`. There is no build step, no preprocessor, and no JavaScript shipped in the package. Three components do expect a script you provide, and each one documents the contract it needs.
|
|
19
|
-
|
|
20
|
-
## The components
|
|
21
|
-
|
|
22
|
-
| Page | Covers |
|
|
23
|
-
| --- | --- |
|
|
24
|
-
| [Buttons](buttons/) | `.btn` with its colour, size and style variants, plus 7 specialised buttons |
|
|
25
|
-
| [Overlays](overlays/) | `.panel`, `.modal`, `.drawer`, `.dropdown` |
|
|
26
|
-
| [Content](content/) | `.alert`, `.badge`, `.banner`, `.card`, `.items`, `.list`, `.media`, `.surtitle` |
|
|
27
|
-
| [Navigation](navigation/) | `.nav`, `.nav-title`, `.nav-accessibility`, `.breadcrumb-wrapper`, `.pagination` |
|
|
28
|
-
| [Embeds](embeds/) | `.embed`, `.video`, `.map` |
|
|
29
|
-
| [Forms](forms/) | `.form`, `.form-check` and the form layout helpers |
|
|
30
|
-
| [Utilities](utilities/) | `.scrollsnap`, turning a grid into a snap carousel |
|
|
31
|
-
|
|
32
|
-
Read [Cascade layers](cascade-layers/) first if you are wiring this into a project for the first time.
|
|
33
|
-
|
|
34
|
-
## Installation
|
|
35
|
-
|
|
36
|
-
All three packages below are required. The tokens resolve the custom properties, and css-base provides the reset and the element styles the components build on.
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npm install @uncinq/design-tokens @uncinq/component-tokens @uncinq/css-base @uncinq/css-components
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
```css
|
|
43
|
-
@layer reset, tokens, libs, base, vendors, layouts, components, pages, utilities;
|
|
44
|
-
|
|
45
|
-
@import '@uncinq/design-tokens'; /* @layer tokens */
|
|
46
|
-
@import '@uncinq/css-base'; /* @layer reset, base, layouts */
|
|
47
|
-
@import '@uncinq/component-tokens'; /* @layer tokens */
|
|
48
|
-
@import '@uncinq/css-components'; /* @layer components, utilities */
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Per component, when you only need a few:
|
|
52
|
-
|
|
53
|
-
```css
|
|
54
|
-
@import '@uncinq/css-components/css/components/button.css';
|
|
55
|
-
@import '@uncinq/css-components/css/components/alert.css';
|
|
56
|
-
@import '@uncinq/css-components/css/utilities/scrollsnap.css';
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### A CDN link is not enough
|
|
60
|
-
|
|
61
|
-
`@uncinq/design-tokens` and `@uncinq/component-tokens` can be linked straight from a CDN, because they emit nothing but custom properties. **This package cannot**, and neither can `@uncinq/css-base`.
|
|
62
|
-
|
|
63
|
-
Both ship `@media (--sm)` queries that rely on the `@custom-media` rules declared in `css-base/css/mediaqueries.css`, and no browser implements `@custom-media`. Loaded without [postcss-custom-media](https://www.npmjs.com/package/postcss-custom-media), 13 responsive blocks in this package are dropped: `.panel-inline-*`, `.panel-trigger-*` and every `.scrollsnap-*` variant.
|
|
64
|
-
|
|
65
|
-
The failure is silent. No error is raised, the page simply stays at its mobile values on every screen. A build step is required.
|
|
66
|
-
|
|
67
|
-
Note that `@uncinq/css-base` is a genuine prerequisite but is **not** declared in `peerDependencies`. Install it explicitly.
|
|
68
|
-
|
|
69
|
-
## Import order inside the package
|
|
70
|
-
|
|
71
|
-
`css/index.css` imports the components alphabetically, with two deliberate exceptions.
|
|
72
|
-
|
|
73
|
-
`components/panel.css` is imported **last** among the components. Its `.panel-inline-*` variants have to beat `.modal` and `.drawer`, and they do so on source order rather than on specificity, because most of the file is wrapped in `:where()` and carries no specificity at all.
|
|
74
|
-
|
|
75
|
-
`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
|
-
|
|
77
|
-
If you import file by file rather than using the barrel, preserve those two positions.
|
|
78
|
-
|
|
79
|
-
## What this package does not include
|
|
80
|
-
|
|
81
|
-
**No JavaScript.** `.modal`, `.drawer` and `.dropdown` expect a script. The CSS defines the classes and attributes that script must toggle, and each page documents the contract.
|
|
82
|
-
|
|
83
|
-
**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
|
-
|
|
85
|
-
**No icons.** Components that show a glyph, such as `.pagination` controls, expect the theme to supply it through `::before` or `::after` content.
|
|
86
|
-
|
|
87
|
-
## References
|
|
88
|
-
|
|
89
|
-
- [@uncinq/component-tokens](https://github.com/uncinq/component-tokens), the values these components read
|
|
90
|
-
- [@uncinq/css-base](https://github.com/uncinq/css-base), the foundation underneath
|
|
91
|
-
- [MDN: CSS cascade layers](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Cascade_layers)
|
|
@@ -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: 3
|
|
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: 6
|
|
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: 4
|
|
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: 5
|
|
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: 7
|
|
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: 8
|
|
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: 9
|
|
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: 2
|
|
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`.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Overview
|
|
4
|
+
description: The eight button files, the two kinds of specialised button, and the masked icons they share.
|
|
5
|
+
weight: 1
|
|
6
|
+
icon: book
|
|
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/). 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.
|