@uncinq/css-components 1.16.1 → 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 +17 -15
- package/css/components/alert.css +5 -0
- package/css/components/panel.css +8 -6
- package/docs/_index.md +20 -16
- 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 +9 -7
- 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
|
@@ -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
|
---
|
|
@@ -16,25 +16,27 @@ This package writes to two layers.
|
|
|
16
16
|
The full order, including layers owned by the sibling packages and by the project itself, is the **consuming project's** responsibility. Declare it once, at the very top of your entry stylesheet, before any `@import`:
|
|
17
17
|
|
|
18
18
|
```css
|
|
19
|
-
@layer reset, tokens, libs,
|
|
19
|
+
@layer reset, tokens, libs, base, vendors, layouts, components, pages, utilities;
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
CSS fixes a layer's position the first time its name is seen, and later re-declarations do not reorder it. If that line comes after an import, it is already too late. See [the css-base page](../../css-base/cascade-layers/) for the full explanation, including why `libs` and `vendors` are a pair.
|
|
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
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Content
|
|
4
|
+
description: Alerts, badges, banners, cards, the items grid, labelled lists, media objects and surtitles.
|
|
5
|
+
weight: 4
|
|
6
|
+
icon: card-text
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Eight components for laying out page content.
|
|
10
|
+
|
|
11
|
+
| Page | Class | Purpose |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| [Alert](alert/) | `.alert` | An inline notification block |
|
|
14
|
+
| [Badge](badge/) | `.badge` | A small inline label |
|
|
15
|
+
| [Banner](banner/) | `.banner` | A centred modifier on `.alert` |
|
|
16
|
+
| [Card](card/) | `.card` | A self-contained content card |
|
|
17
|
+
| [Items](items/) | `.items` | The responsive grid that holds cards |
|
|
18
|
+
| [List](list/) | `.list` | A compact labelled list |
|
|
19
|
+
| [Media](media/) | `.media` | The structural base for images and video |
|
|
20
|
+
| [Surtitle](surtitle/) | `.surtitle` | An eyebrow label above a heading |
|
|
21
|
+
|
|
22
|
+
## Three of them are meant to nest
|
|
23
|
+
|
|
24
|
+
`.items` holds `.card`, a card holds a `.media`, and a `.surtitle` sits above the card's title. Each one reassigns the next one's tokens from its own namespace, so a media block or a surtitle adapts to its context without a variant class. The [card page](card/) covers what it hands down.
|
|
25
|
+
|
|
26
|
+
{{< alert-block state="info" >}}
|
|
27
|
+
Several source comments describe `.card` as a backwards-compatible alias for `.item` and recommend using `.item` directly. That is misleading for anyone consuming this package on its own: **`.item` is not defined here**. It lives in the Hugolify design system theme. Within `@uncinq/css-components`, `.card` is a complete implementation and the class to use.
|
|
28
|
+
{{< /alert-block >}}
|
|
29
|
+
|
|
30
|
+
## Colour variants follow one of two patterns
|
|
31
|
+
|
|
32
|
+
`.alert` and `.badge` both ship the same ten intent names, and they resolve them differently.
|
|
33
|
+
|
|
34
|
+
| | Background | Text |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `.alert-*` | `--color-*-muted` | `--color-*-strong` |
|
|
37
|
+
| `.badge-*` | `--color-*` | `--color-text-on-*` |
|
|
38
|
+
|
|
39
|
+
An alert is a tinted block meant to hold a paragraph, so it uses the muted pair. A badge is a solid chip meant to be read at a glance, so it uses the full colour with its guaranteed-contrast text token. Neither ever hardcodes a colour, which is what makes a rebrand a token change rather than a search and replace.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Alert
|
|
4
|
+
description: An inline notification block, with ten colour variants and an automatic horizontal layout when it leads with an icon.
|
|
5
|
+
weight: 1
|
|
6
|
+
icon: exclamation-triangle
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
An inline notification block.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<div class="alert alert-info">
|
|
13
|
+
<p class="alert-heading">Heads up</p>
|
|
14
|
+
<p>Your changes were saved.</p>
|
|
15
|
+
</div>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Variants
|
|
19
|
+
|
|
20
|
+
`.alert-primary`, `.alert-secondary`, `.alert-brand`, `.alert-neutral`, `.alert-dark`, `.alert-light`, `.alert-success`, `.alert-danger`, `.alert-warning`, `.alert-info`.
|
|
21
|
+
|
|
22
|
+
Each sets background, border and text colour from the matching `-muted` and `-strong` semantic tokens, so the pairing stays legible if the underlying colour changes.
|
|
23
|
+
|
|
24
|
+
```css
|
|
25
|
+
.alert-success {
|
|
26
|
+
--alert-color-background: var(--color-success-muted);
|
|
27
|
+
--alert-color-border: var(--color-success-muted);
|
|
28
|
+
--alert-color-text: var(--color-success-strong);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`.alert-primary` and `.alert-brand` resolve identically, as do `.alert-secondary` and `.alert-neutral`. `.alert-dark` is the one exception to the muted pattern: it takes `--color-text-on-dark` rather than `--color-dark-strong`.
|
|
33
|
+
|
|
34
|
+
The base `.alert` declares no colours of its own, so an alert with no variant class shows whatever `--alert-color-*` resolves to in your token set.
|
|
35
|
+
|
|
36
|
+
## Links inside an alert
|
|
37
|
+
|
|
38
|
+
`--color-link` is reassigned to the alert's own text colour:
|
|
39
|
+
|
|
40
|
+
```css
|
|
41
|
+
.alert { --color-link: var(--alert-color-text); }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Without that, a link inside a tinted block would fall back to the page link colour, which is chosen for contrast against the page background rather than against a muted tint.
|
|
45
|
+
|
|
46
|
+
## Leading with an icon
|
|
47
|
+
|
|
48
|
+
An alert whose first child is an `.icon`, and which has no `.alert-heading`, switches to a horizontal layout on its own:
|
|
49
|
+
|
|
50
|
+
```css
|
|
51
|
+
&:has(.icon:first-child):not(:has(.alert-heading)) {
|
|
52
|
+
flex-direction: row;
|
|
53
|
+
align-items: flex-start;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<div class="alert alert-warning">
|
|
59
|
+
<span class="icon icon-triangle-alert"></span>
|
|
60
|
+
<p>This action cannot be undone.</p>
|
|
61
|
+
</div>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
With a heading present the alert stays stacked, because the icon then belongs beside the heading rather than beside the whole block. `.alert-heading` is itself a flex row with `--spacing-xs` between its children, so an icon inside the heading lines up with the text.
|
|
65
|
+
|
|
66
|
+
## `.container` inside an alert
|
|
67
|
+
|
|
68
|
+
`.container` inherits the same flex layout as the alert itself:
|
|
69
|
+
|
|
70
|
+
```css
|
|
71
|
+
&,
|
|
72
|
+
.container {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: var(--alert-direction, column);
|
|
75
|
+
gap: var(--alert-gap);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
That is what lets a full-bleed alert keep its content aligned with the rest of the page: the alert spans the viewport, the container inside it caps the measure, and both lay their children out the same way.
|
|
80
|
+
|
|
81
|
+
## Hiding an alert
|
|
82
|
+
|
|
83
|
+
`.alert` declares `display: flex`, which would otherwise beat the user agent rule for `[hidden]`. The file restores it:
|
|
84
|
+
|
|
85
|
+
```css
|
|
86
|
+
.alert[hidden] { display: none; }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
So a dismissible alert can be hidden with the attribute alone, with no state class to manage:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
alert.hidden = true;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Margins
|
|
96
|
+
|
|
97
|
+
`.alert *` sets `margin-block: 0` on every descendant, so paragraphs inside an alert are spaced by the flex `gap` rather than by their own margins. Vertical rhythm around the alert itself comes from `--alert-margin`.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Badge
|
|
4
|
+
description: A small inline label, with ten solid colour variants and hover styles reserved for links.
|
|
5
|
+
weight: 2
|
|
6
|
+
icon: tag
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A small inline label.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<span class="badge badge-success">Published</span>
|
|
13
|
+
<a class="badge" href="/tag/css">css</a>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
It is `inline-flex`, so it sits in a line of text and can hold an icon beside its label.
|
|
17
|
+
|
|
18
|
+
## Variants
|
|
19
|
+
|
|
20
|
+
The names mirror the [alert](../alert/) set: `.badge-primary`, `.badge-secondary`, `.badge-brand`, `.badge-neutral`, `.badge-dark`, `.badge-light`, `.badge-success`, `.badge-danger`, `.badge-warning`, `.badge-info`.
|
|
21
|
+
|
|
22
|
+
The colours do not. A badge is a **solid** chip, not a tinted block:
|
|
23
|
+
|
|
24
|
+
```css
|
|
25
|
+
.badge-success {
|
|
26
|
+
--badge-color-background: var(--color-success);
|
|
27
|
+
--badge-color-background-hover: var(--color-success-strong);
|
|
28
|
+
--badge-color-text: var(--color-text-on-success);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Full colour for the background, and the matching `--color-text-on-*` token for the text, which is the pair that guarantees contrast when the underlying colour is overridden. Compare with `.alert-*`, which uses `-muted` and `-strong` because it has to remain readable behind a paragraph.
|
|
33
|
+
|
|
34
|
+
Note that a variant sets no border colour. `--badge-color-border` keeps whatever the base token resolves to, so a bordered badge needs that token set explicitly.
|
|
35
|
+
|
|
36
|
+
## Hover is reserved for links
|
|
37
|
+
|
|
38
|
+
```css
|
|
39
|
+
@media (hover: hover) and (pointer: fine) {
|
|
40
|
+
a.badge:hover { ... }
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Two guards, doing two different jobs. The element selector means a static `<span class="badge">` never looks interactive, which matters because badges are used for both states and actions. The media query means a tap on a touch device does not leave the badge stuck in its hover colour.
|
|
45
|
+
|
|
46
|
+
Both hover values fall back to the resting one, so a variant that defines no hover colour simply does not change on hover rather than resolving to nothing.
|
|
47
|
+
|
|
48
|
+
## Text decoration
|
|
49
|
+
|
|
50
|
+
The base rule sets `text-decoration: none`, so `a.badge` never carries the page's link underline. That is the one thing the class overrides on a link; everything else it leaves to the variant.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Banner
|
|
4
|
+
description: A centred modifier on .alert, for site-wide announcements.
|
|
5
|
+
weight: 3
|
|
6
|
+
icon: megaphone
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A thin variation on the [alert](../alert/), centred rather than start-aligned.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<div class="alert banner">
|
|
13
|
+
<p>Site-wide announcement.</p>
|
|
14
|
+
</div>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**It is a modifier, so use both classes.** `.banner` on its own has no background, no border and no padding.
|
|
18
|
+
|
|
19
|
+
## The whole component
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
.banner {
|
|
23
|
+
--alert-align: center;
|
|
24
|
+
|
|
25
|
+
p { margin-block: 0; }
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
That is the entire file. `--alert-align` feeds both `align-items` and `text-align` in the alert rule, so one token centres the block on both axes at once. The paragraph reset flattens the margins that would otherwise show up when the banner holds a single line.
|
|
30
|
+
|
|
31
|
+
## Variants come from the alert
|
|
32
|
+
|
|
33
|
+
Every `.alert-*` colour works here, because the variant only sets `--alert-color-*` tokens and the banner only sets alignment:
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<div class="alert alert-warning banner">
|
|
37
|
+
<p>Scheduled maintenance on Sunday.</p>
|
|
38
|
+
</div>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Full-bleed announcements
|
|
42
|
+
|
|
43
|
+
A banner is usually the one alert that spans the viewport. Put a `.container` inside it rather than constraining the banner itself: the alert passes its flex layout down to `.container`, so the announcement stays aligned with the rest of the page while the tint runs edge to edge. See [the alert page](../alert/#container-inside-an-alert).
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Card
|
|
4
|
+
description: A self-contained content card, the tokens it hands down to its media, and the whole-card link pattern.
|
|
5
|
+
weight: 4
|
|
6
|
+
icon: card-heading
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A self-contained content card.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<article class="card">
|
|
13
|
+
<div class="content">
|
|
14
|
+
<p class="surtitle">Category</p>
|
|
15
|
+
<p class="title">Card title</p>
|
|
16
|
+
<p class="description">Short description.</p>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="media">
|
|
19
|
+
<picture>...</picture>
|
|
20
|
+
</div>
|
|
21
|
+
</article>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| Child | Role |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `.content` | The text block, grows to fill the card |
|
|
27
|
+
| `.title` | The heading line, styled from `--card-title-*` |
|
|
28
|
+
| `.description` | The body text, at `--card-font-size` |
|
|
29
|
+
| `.media` | The image or video, see [Media](../media/) |
|
|
30
|
+
| `.surtitle` | An optional eyebrow, see [Surtitle](../surtitle/) |
|
|
31
|
+
|
|
32
|
+
{{< alert-block state="info" >}}
|
|
33
|
+
The source comment in `card.css` describes `.card` as a backwards-compatible alias for `.item` and recommends using `.item` directly. That is misleading for anyone consuming this package on its own: **`.item` is not defined here**. It lives in the Hugolify design system theme. Within `@uncinq/css-components`, `.card` is a complete implementation and the class to use.
|
|
34
|
+
{{< /alert-block >}}
|
|
35
|
+
|
|
36
|
+
## Put the media last in the markup
|
|
37
|
+
|
|
38
|
+
The media block sits first **visually**, through `--card-media-order: -1`, regardless of where it appears in the source. Put it after the content so that screen readers and search engines reach the text first.
|
|
39
|
+
|
|
40
|
+
Set `--card-media-order` to anything else, or to `initial`, to put the image below the text on a given card.
|
|
41
|
+
|
|
42
|
+
## What the card hands down
|
|
43
|
+
|
|
44
|
+
Three reassignments at the top of the rule:
|
|
45
|
+
|
|
46
|
+
```css
|
|
47
|
+
--media-color-background: var(--card-media-color-background);
|
|
48
|
+
--media-ratio: var(--card-media-ratio);
|
|
49
|
+
--surtitle-margin-block-end: calc(var(--card-gap) * -1);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
A [`.media`](../media/) inside a card therefore takes the card's ratio and background without any variant class, and a [`.surtitle`](../surtitle/) is pulled back by exactly one gap so that it reads as part of the title rather than as a separate line.
|
|
53
|
+
|
|
54
|
+
`.media-icon` gets a longer handover of its own, mapping eight `--card-media-icon-*` tokens onto the media namespace, which is what lets an icon card look nothing like a photo card while using the same two classes.
|
|
55
|
+
|
|
56
|
+
## The whole-card link
|
|
57
|
+
|
|
58
|
+
Wrapping the content in `a.content` makes the entire card clickable:
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<article class="card">
|
|
62
|
+
<a class="content" href="/article">
|
|
63
|
+
<p class="title">Card title</p>
|
|
64
|
+
<p class="description">Short description.</p>
|
|
65
|
+
</a>
|
|
66
|
+
<div class="media">...</div>
|
|
67
|
+
</article>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The link lays a `::before` overlay across the card, and its own focus ring is suppressed so the focus outline can be drawn on the card instead:
|
|
71
|
+
|
|
72
|
+
```css
|
|
73
|
+
a.content::before { content: ''; inset: 0; position: absolute; }
|
|
74
|
+
a.content:focus-visible { outline: none; }
|
|
75
|
+
|
|
76
|
+
.card:has(a.content:focus-visible) {
|
|
77
|
+
outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-color-outline);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Without the `:has()` rule the ring would outline an invisible box, which is the usual failure of this pattern.
|
|
82
|
+
|
|
83
|
+
Two things to know before using it. **Nested interactive elements become unreachable**, since the overlay covers them: a card with both a whole-card link and a button inside needs a different structure. And the overlay resolves against the nearest positioned ancestor, so the card must establish one; give it `position: relative` if your layout does not already.
|
|
84
|
+
|
|
85
|
+
## Sizing
|
|
86
|
+
|
|
87
|
+
`--card-max-width` and `--card-width` are unset by default, so a card fills its grid track. Prefer `ch` units when you do set them, so the width tracks the text measure rather than a fixed pixel count.
|
|
88
|
+
|
|
89
|
+
`overflow: hidden` on the card is what lets `--card-border-radius` clip a full-bleed media block at the corners.
|
|
90
|
+
|
|
91
|
+
## States
|
|
92
|
+
|
|
93
|
+
Hover swaps the shadow, `--card-shadow` taking `--card-shadow-hover`, behind `@media (hover: hover) and (pointer: fine)`. The transition is behind `prefers-reduced-motion`. There is no hover colour change, deliberately: a card that changes background on hover competes with the link inside it.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Items
|
|
4
|
+
description: A responsive grid whose column count is intrinsic rather than breakpoint-driven.
|
|
5
|
+
weight: 5
|
|
6
|
+
icon: grid-3x2-gap
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A responsive grid, and the usual container for [cards](../card/).
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<div class="items">
|
|
13
|
+
<article class="card">...</article>
|
|
14
|
+
<article class="card">...</article>
|
|
15
|
+
<article class="card">...</article>
|
|
16
|
+
</div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The children are yours to provide. Within this package that means `.card`; `.item` is defined by the Hugolify design system theme, not here.
|
|
20
|
+
|
|
21
|
+
## The column count is intrinsic
|
|
22
|
+
|
|
23
|
+
```css
|
|
24
|
+
grid-template-columns: repeat(
|
|
25
|
+
var(--items-cols, auto-fill),
|
|
26
|
+
minmax(min(var(--items-min-width, var(--max-width-item)), 100%), 1fr)
|
|
27
|
+
);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Items wrap once they no longer fit at `--items-min-width`, **with no media query involved**. The grid answers to the width of its container rather than to the width of the viewport, so the same markup works in a page, in a sidebar and inside another grid.
|
|
31
|
+
|
|
32
|
+
The inner `min(..., 100%)` is what keeps a single item from overflowing a container narrower than `--items-min-width`. Without it, a 30ch minimum in a 20ch column would blow out the layout.
|
|
33
|
+
|
|
34
|
+
Two ways to change the result:
|
|
35
|
+
|
|
36
|
+
| Token | Effect |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `--items-min-width` | The width below which items wrap. Defaults to `--max-width-item` |
|
|
39
|
+
| `--items-cols` | A fixed column count, replacing `auto-fill` |
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
.related { --items-cols: 2; }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Gaps
|
|
46
|
+
|
|
47
|
+
`--items-gap` sets both axes. `--items-column-gap` and `--items-row-gap` override one at a time, each falling back to `--items-gap`.
|
|
48
|
+
|
|
49
|
+
## It is a container query context
|
|
50
|
+
|
|
51
|
+
```css
|
|
52
|
+
container-type: inline-size;
|
|
53
|
+
container-name: items;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
So children can query the grid rather than the viewport:
|
|
57
|
+
|
|
58
|
+
```css
|
|
59
|
+
@container items (min-width: 60ch) {
|
|
60
|
+
.card { --card-media-order: initial; }
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
That is the companion to the intrinsic columns: the grid decides how many tracks fit, and the children adapt to the space they actually got.
|
|
65
|
+
|
|
66
|
+
Be aware that `container-type: inline-size` makes the grid its own containment context, which affects how absolutely positioned descendants resolve.
|
|
67
|
+
|
|
68
|
+
## Turning it into a carousel
|
|
69
|
+
|
|
70
|
+
Add a [`.scrollsnap-*`](../../utilities/scrollsnap/) class to the same element. The utility replaces the grid template with a single scrolling row below its breakpoint and hands everything back above it.
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<div class="items scrollsnap-md">...</div>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The carousel reads the same `--items-min-width`, so item width stays consistent between the two modes.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: List
|
|
4
|
+
description: A compact labelled list, with the label as a sibling of the list rather than inside it.
|
|
5
|
+
weight: 6
|
|
6
|
+
icon: list-ul
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A compact labelled list.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<div class="list">
|
|
13
|
+
<p>Optional label</p>
|
|
14
|
+
<ul>
|
|
15
|
+
<li>First</li>
|
|
16
|
+
<li>Second</li>
|
|
17
|
+
</ul>
|
|
18
|
+
</div>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**The wrapper is a `div`, and the label is a sibling of the list, not inside it.** That is the one structural thing to get right. Either `ul` or `ol` works.
|
|
22
|
+
|
|
23
|
+
## What it changes
|
|
24
|
+
|
|
25
|
+
Three small things, and nothing else:
|
|
26
|
+
|
|
27
|
+
| Selector | Token | Effect |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `p` | `--list-label-font-weight`, `--list-label-margin-block` | The label's weight and the space under it |
|
|
30
|
+
| `li` | `--list-item-font-size`, `--list-item-gap` | Item size, and the space between items as a block margin |
|
|
31
|
+
| `.list, ul, ol` | — | `margin-block-end: 0`, so the block ends flush |
|
|
32
|
+
|
|
33
|
+
Item spacing is a **margin**, not a `gap`, because the list is not a flex container. A `gap` here would do nothing.
|
|
34
|
+
|
|
35
|
+
## Markers come from css-base
|
|
36
|
+
|
|
37
|
+
The dash marker is not declared in this file. Bullets, numbering and the marker colour all come from the native list styling in [css-base](../../../css-base/base/), which is also why the component works on `ol` without a variant.
|
|
38
|
+
|
|
39
|
+
## Where it fits
|
|
40
|
+
|
|
41
|
+
It is the plain-text counterpart to [`.nav`](../../navigation/nav/), which resets the markers and lays its items out with flexbox because they are links. Use `.list` for content, `.nav` for navigation.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
isIndex: false
|
|
3
|
+
title: Media
|
|
4
|
+
description: The structural base for images and video, holding the aspect ratio, plus the logo and icon variants.
|
|
5
|
+
weight: 7
|
|
6
|
+
icon: image
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
The structural base for media blocks. It holds an aspect ratio and crops whatever is inside it.
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<div class="media">
|
|
13
|
+
<picture>
|
|
14
|
+
<img src="..." alt="...">
|
|
15
|
+
</picture>
|
|
16
|
+
</div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Class | Use |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `.media` | Base, for a picture or a video |
|
|
22
|
+
| `.media-logo` | Logo, contained rather than cropped |
|
|
23
|
+
| `.media-icon` | Icon, no ratio and no background |
|
|
24
|
+
|
|
25
|
+
`.media-logo` and `.media-icon` are **modifiers**: use them alongside `.media`.
|
|
26
|
+
|
|
27
|
+
## What the base does
|
|
28
|
+
|
|
29
|
+
```css
|
|
30
|
+
.media {
|
|
31
|
+
aspect-ratio: var(--media-ratio);
|
|
32
|
+
background-color: var(--media-color-background);
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
and then stretches `img`, `picture` and `video` to fill it with `object-fit: cover`. The background shows through while an image loads, which is what keeps a grid of cards from flashing white.
|
|
38
|
+
|
|
39
|
+
Note that the base declares **no border and no radius**. A bordered media block is a card's doing, not the component's: see below.
|
|
40
|
+
|
|
41
|
+
## `.media-logo`
|
|
42
|
+
|
|
43
|
+
Swaps the crop for a contain:
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
.media-logo {
|
|
47
|
+
picture { display: flex; align-items: center; justify-content: center; }
|
|
48
|
+
img { object-fit: contain; max-height: var(--media-logo-max-height); max-width: var(--media-logo-max-width); }
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
A logo must never be cropped, and it must not fill the box either, hence the two maxima and the centring. Everything else, ratio and background, still comes from `.media`.
|
|
53
|
+
|
|
54
|
+
## `.media-icon`
|
|
55
|
+
|
|
56
|
+
Removes the two things that make a media block a media block:
|
|
57
|
+
|
|
58
|
+
```css
|
|
59
|
+
.media-icon {
|
|
60
|
+
--media-ratio: none;
|
|
61
|
+
--media-color-background: transparent;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
So it takes the size of its content. Inside a [card](../card/) it gets far more than that: the card maps eight `--card-media-icon-*` tokens onto the media namespace, including `--icon-size` and `--icon-color`, which is what gives icon cards their own look without a third class.
|
|
66
|
+
|
|
67
|
+
## Context over variants
|
|
68
|
+
|
|
69
|
+
A media block inside a card reads the card's tokens rather than its own:
|
|
70
|
+
|
|
71
|
+
```css
|
|
72
|
+
.card {
|
|
73
|
+
--media-color-background: var(--card-media-color-background);
|
|
74
|
+
--media-ratio: var(--card-media-ratio);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
and the card adds the border, radius and margins on `.media` directly. So the same `<div class="media">` is a full-bleed card header in one place and a standalone framed image in another, with no class changing.
|
|
79
|
+
|
|
80
|
+
That is the pattern to copy when you need a media block to look different in a new context: set the `--media-*` tokens on the context, not a variant on the block.
|
|
81
|
+
|
|
82
|
+
## Video inside a media block
|
|
83
|
+
|
|
84
|
+
```css
|
|
85
|
+
.media .video { height: 100%; }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
A [`.video`](../../embeds/video/) wrapper is stretched to fill the media box, so a video and an image are interchangeable inside a card.
|