css-is-awesome 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +343 -0
- package/CHANGELOG.md +1036 -0
- package/CLAUDE.md +9 -0
- package/CONTRACT.md +580 -0
- package/GEMINI.md +9 -0
- package/LICENSE +21 -0
- package/LICENSE-third-party +88 -0
- package/MIGRATION.md +312 -0
- package/README.md +380 -0
- package/ROADMAP.md +714 -0
- package/THEMING.md +290 -0
- package/THREE-TIERS.md +158 -0
- package/VERSIONING.md +200 -0
- package/_index.scss +34 -0
- package/api.scss +16 -0
- package/bin/README.md +57 -0
- package/bin/cia.cjs +114 -0
- package/bin/migrate-bootstrap.cjs +427 -0
- package/bin/migrate-tailwind.cjs +832 -0
- package/css-is-awesome.instructions.md +533 -0
- package/dist/css-is-awesome.core.css +598 -0
- package/dist/css-is-awesome.core.min.css +1 -0
- package/dist/css-is-awesome.css +3733 -0
- package/dist/css-is-awesome.min.css +1 -0
- package/dist/css-is-awesome.utilities.css +2445 -0
- package/dist/css-is-awesome.utilities.min.css +1 -0
- package/dist/tokens.css +508 -0
- package/dist/tokens.d.ts +266 -0
- package/dist/tokens.min.css +1 -0
- package/figma-tokens/README.md +57 -0
- package/figma-tokens/primitives-brand.scss +121 -0
- package/figma-tokens/semantic-dark.scss +61 -0
- package/figma-tokens/semantic-light.scss +61 -0
- package/figma-tokens/tokens.json +144 -0
- package/llm.txt +183 -0
- package/mcp/server.cjs +1495 -0
- package/package.json +199 -0
- package/public/icons/README.md +278 -0
- package/public/icons/arrow-right.svg +4 -0
- package/public/icons/check.svg +4 -0
- package/public/icons/chevron-down.svg +4 -0
- package/public/icons/close.svg +4 -0
- package/public/icons/core/arrow-down.svg +13 -0
- package/public/icons/core/arrow-left.svg +13 -0
- package/public/icons/core/arrow-right.svg +13 -0
- package/public/icons/core/arrow-up.svg +13 -0
- package/public/icons/core/bell.svg +13 -0
- package/public/icons/core/calendar.svg +15 -0
- package/public/icons/core/check.svg +12 -0
- package/public/icons/core/chevron-down.svg +12 -0
- package/public/icons/core/chevron-left.svg +12 -0
- package/public/icons/core/chevron-right.svg +12 -0
- package/public/icons/core/chevron-up.svg +12 -0
- package/public/icons/core/clock.svg +13 -0
- package/public/icons/core/close.svg +13 -0
- package/public/icons/core/copy.svg +13 -0
- package/public/icons/core/download.svg +14 -0
- package/public/icons/core/edit.svg +13 -0
- package/public/icons/core/error.svg +14 -0
- package/public/icons/core/external-link.svg +14 -0
- package/public/icons/core/eye-off.svg +15 -0
- package/public/icons/core/eye.svg +13 -0
- package/public/icons/core/filter.svg +12 -0
- package/public/icons/core/heart.svg +12 -0
- package/public/icons/core/help.svg +14 -0
- package/public/icons/core/home.svg +13 -0
- package/public/icons/core/info.svg +14 -0
- package/public/icons/core/loading.svg +12 -0
- package/public/icons/core/lock.svg +13 -0
- package/public/icons/core/mail.svg +13 -0
- package/public/icons/core/menu.svg +14 -0
- package/public/icons/core/message.svg +12 -0
- package/public/icons/core/minus.svg +12 -0
- package/public/icons/core/more-horizontal.svg +14 -0
- package/public/icons/core/pause.svg +13 -0
- package/public/icons/core/play.svg +12 -0
- package/public/icons/core/plus.svg +13 -0
- package/public/icons/core/refresh.svg +15 -0
- package/public/icons/core/save.svg +14 -0
- package/public/icons/core/search.svg +13 -0
- package/public/icons/core/settings.svg +13 -0
- package/public/icons/core/share.svg +14 -0
- package/public/icons/core/sort.svg +15 -0
- package/public/icons/core/star.svg +12 -0
- package/public/icons/core/success.svg +13 -0
- package/public/icons/core/trash.svg +16 -0
- package/public/icons/core/unlock.svg +13 -0
- package/public/icons/core/upload.svg +14 -0
- package/public/icons/core/user.svg +13 -0
- package/public/icons/core/users.svg +15 -0
- package/public/icons/core/warning.svg +14 -0
- package/public/icons/download.svg +4 -0
- package/public/icons/edit.svg +4 -0
- package/public/icons/menu.svg +4 -0
- package/public/icons/search.svg +4 -0
- package/public/theme.css +4179 -0
- package/public/themes/README.md +102 -0
- package/public/themes/boilerplate/theme.css +154 -0
- package/public/themes/boilerplate-dark/theme.css +171 -0
- package/public/themes/boilerplate-light/theme.css +171 -0
- package/public/themes/cupertino/theme.css +165 -0
- package/public/themes/cupertino-dark/theme.css +200 -0
- package/public/themes/cupertino-light/theme.css +227 -0
- package/public/themes/glass/theme.css +190 -0
- package/public/themes/glass-dark/theme.css +201 -0
- package/public/themes/glass-light/theme.css +229 -0
- package/public/themes/graphite/theme.css +155 -0
- package/public/themes/graphite-dark/theme.css +223 -0
- package/public/themes/graphite-light/theme.css +191 -0
- package/public/themes/press/theme.css +189 -0
- package/public/themes/press-dark/theme.css +199 -0
- package/public/themes/press-light/theme.css +235 -0
- package/public/themes/prism/theme.css +159 -0
- package/public/themes/prism-dark/theme.css +185 -0
- package/public/themes/prism-light/theme.css +188 -0
- package/public/themes/sketchbook/theme.css +152 -0
- package/public/themes/sketchbook-dark/theme.css +194 -0
- package/public/themes/sketchbook-light/theme.css +188 -0
- package/public/themes/terminal/theme.css +159 -0
- package/public/themes/terminal-dark/theme.css +229 -0
- package/public/themes/terminal-light/theme.css +152 -0
- package/scripts/README.md +70 -0
- package/scripts/icon-contract.json +67 -0
- package/scripts/icon-validator.js +205 -0
- package/scripts/prepare-dist.mjs +65 -0
- package/scripts/theme-a11y.js +415 -0
- package/scripts/theme-contract.json +171 -0
- package/scripts/theme-validator.js +674 -0
- package/scss/README.md +55 -0
- package/scss/_animations-utilities.scss +107 -0
- package/scss/_animations.scss +248 -0
- package/scss/_app-styles.scss +15 -0
- package/scss/_generator.scss +174 -0
- package/scss/_icons.scss +475 -0
- package/scss/_index.scss +17 -0
- package/scss/_layout.scss +597 -0
- package/scss/_mixins.scss +1036 -0
- package/scss/_system.scss +291 -0
- package/scss/_utilities.scss +377 -0
- package/scss/api.scss +61 -0
- package/scss/components/_accordion.scss +72 -0
- package/scss/components/_buttons.scss +171 -0
- package/scss/components/_copy-button.scss +69 -0
- package/scss/components/_data.scss +190 -0
- package/scss/components/_feedback.scss +197 -0
- package/scss/components/_forms.scss +272 -0
- package/scss/components/_index.scss +17 -0
- package/scss/components/_navigation.scss +186 -0
- package/scss/components/_overlay.scss +259 -0
- package/scss/components/_stepper.scss +159 -0
- package/scss/components/_tabs.scss +72 -0
- package/scss/core.scss +54 -0
- package/scss/examples/_theming.scss +107 -0
- package/scss/examples/_usage.scss +317 -0
- package/scss/main.scss +64 -0
- package/scss/recipes/README.md +110 -0
- package/scss/recipes/_bare-tags.scss +232 -0
- package/scss/recipes/_recipe-template.md +119 -0
- package/scss/recipes/combobox.md +509 -0
- package/scss/recipes/dialog.md +265 -0
- package/scss/recipes/print-to-pdf.md +263 -0
- package/scss/theme/README.md +67 -0
- package/scss/theme/_brand.scss +18 -0
- package/scss/theme/_colors-dark.scss +68 -0
- package/scss/theme/_colors-light.scss +70 -0
- package/scss/theme/_components.scss +94 -0
- package/scss/theme/_icons.scss +104 -0
- package/scss/theme/_index.scss +62 -0
- package/scss/theme/_shadows.scss +65 -0
- package/scss/theme/_shape.scss +13 -0
- package/scss/theme/_typography.scss +9 -0
- package/scss/theme/registry.json +447 -0
- package/scss/themes/boilerplate-dark.scss +212 -0
- package/scss/themes/boilerplate-light.scss +209 -0
- package/scss/themes/boilerplate.scss +181 -0
- package/scss/themes/cupertino-dark.scss +238 -0
- package/scss/themes/cupertino-light.scss +266 -0
- package/scss/themes/cupertino.scss +194 -0
- package/scss/themes/glass-dark.scss +238 -0
- package/scss/themes/glass-light.scss +266 -0
- package/scss/themes/glass.scss +220 -0
- package/scss/themes/graphite-dark.scss +260 -0
- package/scss/themes/graphite-light.scss +228 -0
- package/scss/themes/graphite.scss +182 -0
- package/scss/themes/press-dark.scss +237 -0
- package/scss/themes/press-light.scss +273 -0
- package/scss/themes/press.scss +241 -0
- package/scss/themes/prism-dark.scss +217 -0
- package/scss/themes/prism-light.scss +220 -0
- package/scss/themes/prism.scss +190 -0
- package/scss/themes/sketchbook-dark.scss +231 -0
- package/scss/themes/sketchbook-light.scss +231 -0
- package/scss/themes/sketchbook.scss +178 -0
- package/scss/themes/terminal-dark.scss +267 -0
- package/scss/themes/terminal-light.scss +175 -0
- package/scss/themes/terminal.scss +185 -0
- package/scss/tokens.scss +23 -0
- package/scss/utilities-only.scss +4 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dialog
|
|
3
|
+
description: Accessible modal dialog built on the native HTML <dialog> element.
|
|
4
|
+
category: overlay
|
|
5
|
+
complexity: medium
|
|
6
|
+
cia-version: ">=1.0.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Use this when
|
|
10
|
+
|
|
11
|
+
You need a modal — confirm, info, form, lightbox — that traps focus while open, returns focus on close, dismisses on Esc, and hides the background from screen readers. Use this recipe if you can target browsers with native `<dialog>` support (Baseline since 2022 across Chrome/Firefox/Safari/Edge). If you need older browser support, reach for a focus-trap library + `[role="dialog"]` div.
|
|
12
|
+
|
|
13
|
+
## Structure (raw HTML)
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<dialog data-cia-recipe="dialog" aria-labelledby="my-dialog-title">
|
|
17
|
+
<header data-slot="header">
|
|
18
|
+
<h2 id="my-dialog-title">Dialog title</h2>
|
|
19
|
+
<button data-slot="close" aria-label="Close" formmethod="dialog">×</button>
|
|
20
|
+
</header>
|
|
21
|
+
<main data-slot="body">
|
|
22
|
+
Dialog body content goes here.
|
|
23
|
+
</main>
|
|
24
|
+
<footer data-slot="footer">
|
|
25
|
+
<button data-slot="cancel" formmethod="dialog">Cancel</button>
|
|
26
|
+
<button data-slot="confirm" autofocus>Confirm</button>
|
|
27
|
+
</footer>
|
|
28
|
+
</dialog>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Notes on the markup:
|
|
32
|
+
- `<dialog>` element is the source of truth — its `.showModal()` method does focus trap + Esc handling + `aria-modal` + backdrop, all natively
|
|
33
|
+
- `aria-labelledby` points to the title element so screen readers announce the dialog by name on open
|
|
34
|
+
- `formmethod="dialog"` on the cancel/close buttons closes the dialog without a JS handler when the dialog is inside a `<form>`
|
|
35
|
+
- `autofocus` on the primary action sets initial focus when the dialog opens
|
|
36
|
+
|
|
37
|
+
## Styling (cia mixins)
|
|
38
|
+
|
|
39
|
+
```scss
|
|
40
|
+
// MyDialog.module.scss — component stylesheet, so import the zero-emit barrel.
|
|
41
|
+
@use 'css-is-awesome/api' as cia;
|
|
42
|
+
|
|
43
|
+
.my-dialog {
|
|
44
|
+
@include cia.modal;
|
|
45
|
+
|
|
46
|
+
[data-slot="header"] { @include cia.toolbar; }
|
|
47
|
+
[data-slot="body"] { @include cia.stack($gap: 4); }
|
|
48
|
+
[data-slot="footer"] { @include cia.toolbar; }
|
|
49
|
+
|
|
50
|
+
[data-slot="close"] { @include cia.btn(ghost); margin-inline-start: auto; }
|
|
51
|
+
[data-slot="cancel"] { @include cia.btn(ghost); }
|
|
52
|
+
[data-slot="confirm"] { @include cia.btn(primary); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Backdrop is exposed by the browser via ::backdrop */
|
|
56
|
+
.my-dialog::backdrop {
|
|
57
|
+
background: rgba(0, 0, 0, 0.5);
|
|
58
|
+
backdrop-filter: blur(2px);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Interactivity
|
|
63
|
+
|
|
64
|
+
Native behavior of `<dialog>.showModal()`:
|
|
65
|
+
- Renders above all other content in the top-layer
|
|
66
|
+
- Adds `[open]` and `aria-modal="true"`
|
|
67
|
+
- Traps Tab focus inside the dialog
|
|
68
|
+
- Dismisses on Esc (calls `.close()` with no `returnValue`)
|
|
69
|
+
- Disables interaction with the page behind it
|
|
70
|
+
- Exposes `::backdrop` for backdrop styling
|
|
71
|
+
|
|
72
|
+
To open: `dialogEl.showModal()`. To close: `dialogEl.close(optionalReturnValue)`. The return value is readable on the `close` event for "Cancel vs Confirm" wiring.
|
|
73
|
+
|
|
74
|
+
Consumer responsibilities:
|
|
75
|
+
- Show / close from your component logic (button clicks, route changes, etc.)
|
|
76
|
+
- Optionally listen for the `close` event to read `returnValue`
|
|
77
|
+
- If using `<form method="dialog">`, submit buttons close with `returnValue = button.value`
|
|
78
|
+
|
|
79
|
+
No JS shim required for any of the above — every behavior is native.
|
|
80
|
+
|
|
81
|
+
## A11y checklist
|
|
82
|
+
|
|
83
|
+
- [ ] `aria-labelledby` points to the title element ([WAI-ARIA Authoring Practices: Dialog (Modal)](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/))
|
|
84
|
+
- [ ] Initial focus on the primary action via `autofocus`, OR moved programmatically to the first focusable element on open
|
|
85
|
+
- [ ] Background content has `inert` attribute when dialog is open (automatic with `.showModal()` — the top-layer hides everything from accessibility tree)
|
|
86
|
+
- [ ] Esc closes the dialog (native to `<dialog>.showModal()`)
|
|
87
|
+
- [ ] Close button has accessible name (`aria-label="Close"`)
|
|
88
|
+
- [ ] Focus returns to the trigger element on close (browser handles this automatically when `.showModal()` was the open mechanism)
|
|
89
|
+
- [ ] If dialog contains a `<form>`, use `method="dialog"` so submission closes the dialog naturally (avoids hand-wired close handlers)
|
|
90
|
+
- [ ] Color contrast on `::backdrop` does not interfere with the dialog's own contrast budget ([WCAG 2.2 SC 1.4.11 Non-text Contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html))
|
|
91
|
+
|
|
92
|
+
## Framework examples
|
|
93
|
+
|
|
94
|
+
### React
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
"use client";
|
|
98
|
+
import { useRef } from "react";
|
|
99
|
+
import styles from "./MyDialog.module.scss";
|
|
100
|
+
|
|
101
|
+
export default function MyDialog() {
|
|
102
|
+
const ref = useRef<HTMLDialogElement>(null);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
<button onClick={() => ref.current?.showModal()}>Open dialog</button>
|
|
107
|
+
|
|
108
|
+
<dialog
|
|
109
|
+
ref={ref}
|
|
110
|
+
className={styles.myDialog}
|
|
111
|
+
aria-labelledby="my-dialog-title"
|
|
112
|
+
onClose={(e) => {
|
|
113
|
+
// e.currentTarget.returnValue is the close value
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
<header data-slot="header">
|
|
117
|
+
<h2 id="my-dialog-title">Delete record?</h2>
|
|
118
|
+
<button data-slot="close" formMethod="dialog" aria-label="Close">×</button>
|
|
119
|
+
</header>
|
|
120
|
+
<main data-slot="body">This action cannot be undone.</main>
|
|
121
|
+
<footer data-slot="footer">
|
|
122
|
+
<form method="dialog" style={{ display: "contents" }}>
|
|
123
|
+
<button data-slot="cancel" value="cancel">Cancel</button>
|
|
124
|
+
<button data-slot="confirm" value="confirm" autoFocus>Delete</button>
|
|
125
|
+
</form>
|
|
126
|
+
</footer>
|
|
127
|
+
</dialog>
|
|
128
|
+
</>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Vue
|
|
134
|
+
|
|
135
|
+
```vue
|
|
136
|
+
<script setup>
|
|
137
|
+
import { ref } from "vue";
|
|
138
|
+
const dialogRef = ref(null);
|
|
139
|
+
const open = () => dialogRef.value?.showModal();
|
|
140
|
+
</script>
|
|
141
|
+
|
|
142
|
+
<template>
|
|
143
|
+
<button @click="open">Open dialog</button>
|
|
144
|
+
|
|
145
|
+
<dialog
|
|
146
|
+
ref="dialogRef"
|
|
147
|
+
class="my-dialog"
|
|
148
|
+
aria-labelledby="my-dialog-title"
|
|
149
|
+
>
|
|
150
|
+
<header data-slot="header">
|
|
151
|
+
<h2 id="my-dialog-title">Delete record?</h2>
|
|
152
|
+
<button data-slot="close" formmethod="dialog" aria-label="Close">×</button>
|
|
153
|
+
</header>
|
|
154
|
+
<main data-slot="body">This action cannot be undone.</main>
|
|
155
|
+
<footer data-slot="footer">
|
|
156
|
+
<form method="dialog" style="display: contents">
|
|
157
|
+
<button data-slot="cancel" value="cancel">Cancel</button>
|
|
158
|
+
<button data-slot="confirm" value="confirm" autofocus>Delete</button>
|
|
159
|
+
</form>
|
|
160
|
+
</footer>
|
|
161
|
+
</dialog>
|
|
162
|
+
</template>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Svelte
|
|
166
|
+
|
|
167
|
+
```svelte
|
|
168
|
+
<script>
|
|
169
|
+
let dialogEl;
|
|
170
|
+
const open = () => dialogEl?.showModal();
|
|
171
|
+
</script>
|
|
172
|
+
|
|
173
|
+
<button on:click={open}>Open dialog</button>
|
|
174
|
+
|
|
175
|
+
<dialog
|
|
176
|
+
bind:this={dialogEl}
|
|
177
|
+
class="my-dialog"
|
|
178
|
+
aria-labelledby="my-dialog-title"
|
|
179
|
+
>
|
|
180
|
+
<header data-slot="header">
|
|
181
|
+
<h2 id="my-dialog-title">Delete record?</h2>
|
|
182
|
+
<button data-slot="close" formmethod="dialog" aria-label="Close">×</button>
|
|
183
|
+
</header>
|
|
184
|
+
<main data-slot="body">This action cannot be undone.</main>
|
|
185
|
+
<footer data-slot="footer">
|
|
186
|
+
<form method="dialog" style="display: contents">
|
|
187
|
+
<button data-slot="cancel" value="cancel">Cancel</button>
|
|
188
|
+
<button data-slot="confirm" value="confirm" autofocus>Delete</button>
|
|
189
|
+
</form>
|
|
190
|
+
</footer>
|
|
191
|
+
</dialog>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Vanilla (Web Component)
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
class MyDialog extends HTMLElement {
|
|
198
|
+
connectedCallback() {
|
|
199
|
+
this.innerHTML = `
|
|
200
|
+
<dialog aria-labelledby="my-dialog-title">
|
|
201
|
+
<header data-slot="header">
|
|
202
|
+
<h2 id="my-dialog-title">${this.getAttribute("title") ?? "Dialog"}</h2>
|
|
203
|
+
<button data-slot="close" formmethod="dialog" aria-label="Close">×</button>
|
|
204
|
+
</header>
|
|
205
|
+
<main data-slot="body"><slot></slot></main>
|
|
206
|
+
<footer data-slot="footer">
|
|
207
|
+
<form method="dialog" style="display: contents">
|
|
208
|
+
<button data-slot="cancel" value="cancel">Cancel</button>
|
|
209
|
+
<button data-slot="confirm" value="confirm" autofocus>OK</button>
|
|
210
|
+
</form>
|
|
211
|
+
</footer>
|
|
212
|
+
</dialog>
|
|
213
|
+
`;
|
|
214
|
+
this._dialog = this.querySelector("dialog");
|
|
215
|
+
}
|
|
216
|
+
open() { this._dialog?.showModal(); }
|
|
217
|
+
close(returnValue) { this._dialog?.close(returnValue); }
|
|
218
|
+
}
|
|
219
|
+
customElements.define("my-dialog", MyDialog);
|
|
220
|
+
|
|
221
|
+
// usage in HTML:
|
|
222
|
+
// <my-dialog title="Delete record?">This action cannot be undone.</my-dialog>
|
|
223
|
+
// <script>document.querySelector('my-dialog').open();</script>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Variants
|
|
227
|
+
|
|
228
|
+
### Confirm / cancel only (no header dismiss)
|
|
229
|
+
|
|
230
|
+
Drop the `[data-slot="close"]` button from the header. Esc still closes; the cancel button in the footer is the primary dismiss.
|
|
231
|
+
|
|
232
|
+
### Drawer (slide-in from edge)
|
|
233
|
+
|
|
234
|
+
Override `.my-dialog` with `inset-inline-end: 0; margin-inline: auto 0; block-size: 100dvh; max-block-size: none;` to anchor right edge, full height. Add a slide animation:
|
|
235
|
+
|
|
236
|
+
```scss
|
|
237
|
+
// Duration + easing come from the theme, so the drawer inherits each theme's
|
|
238
|
+
// feel (Terminal snappy, Glass floaty) with no code change.
|
|
239
|
+
.my-dialog[open] {
|
|
240
|
+
animation: slide-in var(--duration-normal, 240ms) var(--ease, ease) both;
|
|
241
|
+
|
|
242
|
+
@media (prefers-reduced-motion: reduce) {
|
|
243
|
+
animation-duration: 0.01ms;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
@keyframes slide-in {
|
|
247
|
+
from { transform: translateX(100%); }
|
|
248
|
+
to { transform: translateX(0); }
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
cia's built-in keyframes are small entrance nudges, so a full-width drawer needs its own `@keyframes`. If an 8px slide is enough, skip the custom keyframe and use `@include cia.animate(slide-left);` — it reads the same duration/easing tokens and handles `prefers-reduced-motion` for you.
|
|
253
|
+
|
|
254
|
+
## Pitfalls
|
|
255
|
+
|
|
256
|
+
- **Form inside dialog with another method:** Setting `<form method="get">` etc. inside a `<dialog>` breaks the native close-on-submit. Either nest a separate `<form method="dialog">` around the close buttons OR handle submission explicitly with `event.preventDefault()` + `dialogEl.close()`.
|
|
257
|
+
- **Stacking dialogs:** `.showModal()` puts the dialog in the top-layer. Nesting multiple modal dialogs works but each pushes the previous one further from focus; consider whether your UX really needs nested modals.
|
|
258
|
+
- **iOS Safari quirks:** Older iOS Safari (< 17) had `::backdrop` rendering bugs and inconsistent scroll-lock. If you target older Safari, test the backdrop and consider a polyfill.
|
|
259
|
+
- **`<dialog>` inside `display: contents` ancestors:** Some CSS layout edge cases can affect the top-layer rendering. If positioning looks off, check that ancestor `transform`, `filter`, or `perspective` isn't establishing a containing block.
|
|
260
|
+
|
|
261
|
+
## Related recipes
|
|
262
|
+
|
|
263
|
+
- [`bare-tags`](./_bare-tags.scss) — base bare `<dialog>` styling that applies if you skip a custom class name
|
|
264
|
+
- (planned v1.1) `command-palette.md` — Cmd+K palette built on `<dialog>` + combobox pattern
|
|
265
|
+
- (planned v1.1) `toast.md` — non-modal transient notifications (`[popover]` based)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: print-to-pdf
|
|
3
|
+
description: Pixel-faithful PDF export from any page using only a @media print stylesheet — no library, no server.
|
|
4
|
+
category: layout
|
|
5
|
+
complexity: simple
|
|
6
|
+
cia-version: ">=1.0.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Use this when
|
|
10
|
+
|
|
11
|
+
You want users to save a page as a faithful PDF — résumés, invoices, receipts, reports, tickets, order confirmations — and you'd rather not pay for a PDF service (DocRaptor, Prince, PDFShift) or pull in a rendering library. A browser already has a layout engine and a PDF writer wired together; you just describe the page *on paper* with `@media print`, and the user saves it from the print dialog. **The page itself is the PDF source — there's no second template to keep in sync.** If your "PDF" is really a fixed artifact unrelated to the page (a generated certificate, a pre-printed form to fill), this isn't it — use a real PDF library. If you need PDFs generated with no human present (emailing invoices, batch export), the *same stylesheet* still drives it — see the automation note under Interactivity.
|
|
12
|
+
|
|
13
|
+
## Structure (raw HTML)
|
|
14
|
+
|
|
15
|
+
There's almost nothing to add — the page **is** the structure. The only print-specific markup is marking the on-screen chrome that shouldn't appear on paper. Put `data-cia-recipe` on the document root so tooling can find it.
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<body data-cia-recipe="print-to-pdf">
|
|
19
|
+
<nav class="site-nav">…</nav> <!-- hidden on paper -->
|
|
20
|
+
|
|
21
|
+
<main class="doc" data-slot="document">
|
|
22
|
+
<h1>Quarterly Report</h1>
|
|
23
|
+
<p>…</p>
|
|
24
|
+
<a href="https://example.com/details">Full details</a>
|
|
25
|
+
</main>
|
|
26
|
+
|
|
27
|
+
<footer class="site-footer">…</footer> <!-- hidden on paper -->
|
|
28
|
+
|
|
29
|
+
<!-- Optional signpost. Ctrl+P works with or without it. -->
|
|
30
|
+
<button type="button" class="no-print" onclick="window.print()">Save as PDF</button>
|
|
31
|
+
</body>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Notes on the markup:
|
|
35
|
+
|
|
36
|
+
- **No button is required.** `@media print` applies to *every* print path — `Ctrl/Cmd+P`, File → Print, the dialog's built-in "Save as PDF" destination. The button is a discoverability signpost only; it says "this page was designed to become a PDF."
|
|
37
|
+
- Mark every piece of site chrome — `nav`, `footer`, the button itself — so the print layer can hide it. A single shared class (`no-print`) plus your structural elements is enough.
|
|
38
|
+
- The `<a>` keeps its real `href`. On paper a clickable link is dead, so we print the URL after it (see Pitfalls).
|
|
39
|
+
|
|
40
|
+
## Styling (cia mixins)
|
|
41
|
+
|
|
42
|
+
cia owns the `@media print` layer through four mixins. `print-base` ships the page-level defaults **on**; the rest are per-element.
|
|
43
|
+
|
|
44
|
+
This recipe is the one case that spans **both** halves of cia's two-import model, so the code below is split accordingly. Both files import the same zero-emit authoring barrel, `css-is-awesome/api` — what differs is *where* the rules land.
|
|
45
|
+
|
|
46
|
+
**1. In your GLOBAL stylesheet** — `print-base` emits its own `:root` block plus `@page`, so it must sit at the top level of a global/root stylesheet. Never put it inside a component module: a top-level `:root` is a hard build error under Next.js CSS Modules pure mode.
|
|
47
|
+
|
|
48
|
+
```scss
|
|
49
|
+
// app/globals.scss (or your single root stylesheet) — included ONCE.
|
|
50
|
+
@use 'css-is-awesome/api' as cia;
|
|
51
|
+
|
|
52
|
+
// Page-level defaults — at the ROOT, never inside a selector (it emits @page).
|
|
53
|
+
// Sets the @page box and freezes animations so nothing prints invisible.
|
|
54
|
+
// Defaults are on; toggle via args.
|
|
55
|
+
@include cia.print-base; // size: letter, margin: 0.5in, freeze on
|
|
56
|
+
// @include cia.print-base($size: A4, $margin: 0.75in); // override the paper
|
|
57
|
+
// @include cia.print-base($freeze-animations: false); // opt out of the freeze
|
|
58
|
+
|
|
59
|
+
// Make every light-dark() token resolve to its paper-friendly light value,
|
|
60
|
+
// so a dark theme doesn't print as white-on-white. (See Pitfalls.)
|
|
61
|
+
:root {
|
|
62
|
+
@include cia.print { color-scheme: light; }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Hide site chrome on paper — the "hide the nav" case. Site chrome is global,
|
|
66
|
+
// so these usually live here too; move them into the component that owns the
|
|
67
|
+
// element if you'd rather keep the rule next to its markup.
|
|
68
|
+
.site-nav,
|
|
69
|
+
.site-footer,
|
|
70
|
+
.no-print {
|
|
71
|
+
@include cia.print-hidden;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**2. In each COMPONENT stylesheet** — everything else is per-element and emits nothing until you call a mixin, so it is safe in a `.module.scss`.
|
|
76
|
+
|
|
77
|
+
```scss
|
|
78
|
+
// Doc.module.scss — component stylesheet, so import the zero-emit barrel.
|
|
79
|
+
@use 'css-is-awesome/api' as cia;
|
|
80
|
+
|
|
81
|
+
// Co-locate per-element print overrides RIGHT NEXT TO the screen rule
|
|
82
|
+
// they change, so the reason is visible where you read the original.
|
|
83
|
+
.doc {
|
|
84
|
+
background: cia.color(surface-default);
|
|
85
|
+
color: cia.color(text-primary);
|
|
86
|
+
|
|
87
|
+
@include cia.print {
|
|
88
|
+
padding-block: 0; // strip screen chrome that wastes the sheet
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Show the minimum to make it work. The `color-scheme: light` flip is the cia-native fix for light-on-dark themes: instead of recoloring element by element, you tell the print sheet to use each token's light side once. Class names are consumer-chosen (`my-doc`, `.doc`) — never `cia-*`.
|
|
94
|
+
|
|
95
|
+
### The variable filter system
|
|
96
|
+
|
|
97
|
+
`print-base` emits three custom properties that are the control plane for the whole print layer — flip them and the output changes without rewriting a single rule:
|
|
98
|
+
|
|
99
|
+
| Variable | Value | What it does |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| `--is-print` | `0` on screen, `1` on paper | A readable print-state switch. Use it in `calc()`, `opacity`, or `@container style(--is-print: 1)` to drive custom print-only effects. |
|
|
102
|
+
| `--print-hide` | `none` | The `display` applied to `print-hidden` elements on paper. |
|
|
103
|
+
| `--print-show` | `revert` | The `display` applied to `print-only` elements on paper. |
|
|
104
|
+
|
|
105
|
+
Because the *value* is a variable, a per-element exception needs no new rule — just re-aim the variable on that element:
|
|
106
|
+
|
|
107
|
+
```scss
|
|
108
|
+
.legal-footer { @include cia.print-hidden; --print-hide: revert; } // keep this one ON paper
|
|
109
|
+
.receipt-grid { @include cia.print-only; --print-show: grid; } // print-only block as a grid
|
|
110
|
+
|
|
111
|
+
.cover { @include cia.print { opacity: var(--is-print); } } // fades in only on paper
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
That is the whole reason this recipe needs no JavaScript and no headless-browser service: the media query plus a few variables **are** the engine.
|
|
115
|
+
|
|
116
|
+
### Why these mixins use `!important`
|
|
117
|
+
|
|
118
|
+
You will see `!important` in the compiled output. cia avoids it everywhere else; the print layer is a deliberate exception.
|
|
119
|
+
|
|
120
|
+
`@media` contributes **no specificity**. `print-hidden` is included inside *your* selector, so its rule carries exactly your selector's specificity — and a later declaration at equal specificity wins, in print too:
|
|
121
|
+
|
|
122
|
+
```scss
|
|
123
|
+
.site-nav {
|
|
124
|
+
@include cia.print-hidden; // @media print { display: none }
|
|
125
|
+
display: flex; // equal specificity, later — would win without !important
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The competing rule is usually not even yours: a utility class, or a component library cia cannot see. Without `!important`, "hidden on paper" silently isn't — and it only shows up in a print preview.
|
|
130
|
+
|
|
131
|
+
**`@layer` does not solve this.** Layered CSS always loses to unlayered CSS, so a layered print rule would lose to any consumer stylesheet that isn't layered — which is most of them. (`!important` also inverts layer order, so combining them misleads.) cia ships unlayered by design; see `.agent/decisions/decided/04-at-layer-decision.md`.
|
|
132
|
+
|
|
133
|
+
The scope is small and the escape hatch is open: 8 declarations, all inside `@media print`, and every value stays variable-driven. Override `--print-hide` / `--print-show` to change *what* happens; you never have to fight the rule to win.
|
|
134
|
+
|
|
135
|
+
## Interactivity
|
|
136
|
+
|
|
137
|
+
**Zero JS.** The browser runs no script and needs no server — it reads your `@media print` rules and renders. The user presses `Ctrl/Cmd+P` (or File → Print) and picks the built-in "Save as PDF" destination. That is the entire mechanism.
|
|
138
|
+
|
|
139
|
+
The only optional code is a **discoverability signpost** — a one-line button so users notice the page is built to be saved:
|
|
140
|
+
|
|
141
|
+
```html
|
|
142
|
+
<button type="button" class="no-print" onclick="window.print()">Save as PDF</button>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
It's sugar over `window.print()` and nothing depends on it — `Ctrl+P` does the same job. Hide it on paper with `@include cia.print-hidden`. The framework examples below show this one button in each stack.
|
|
146
|
+
|
|
147
|
+
## A11y checklist
|
|
148
|
+
|
|
149
|
+
- [ ] Print output preserves DOM/reading order — the visual sheet follows source order ([WCAG 2.2 SC 1.3.2 Meaningful Sequence](https://www.w3.org/WAI/WCAG22/Understanding/meaningful-sequence.html))
|
|
150
|
+
- [ ] Hiding chrome with `print-hidden` removes only navigation/controls, never content the document needs to make sense ([WCAG 2.2 SC 1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html))
|
|
151
|
+
- [ ] Ink-on-paper contrast meets the minimum — the `color-scheme: light` flip keeps dark text on a light sheet ([WCAG 2.2 SC 1.4.3 Contrast (Minimum)](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html))
|
|
152
|
+
- [ ] Link destinations survive on paper — print the `href` inline so a printed link is still followable ([WCAG 2.2 SC 2.4.4 Link Purpose (In Context)](https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html))
|
|
153
|
+
- [ ] Animations are frozen and `opacity` forced to `1` in print so no content snapshots invisible (`print-base` does this by default)
|
|
154
|
+
- [ ] The optional trigger is a real `<button type="button">` with a clear accessible name, hidden on paper ([WCAG 2.2 SC 4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html))
|
|
155
|
+
|
|
156
|
+
## Framework examples
|
|
157
|
+
|
|
158
|
+
All four are the same **optional** signpost button — the only framework-specific code in this recipe, and every one is a thin wrapper over `window.print()`. Hide it on paper with `@include cia.print-hidden` (or the `no-print` class).
|
|
159
|
+
|
|
160
|
+
### React
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
export function PrintButton({
|
|
164
|
+
children = "Save as PDF",
|
|
165
|
+
className,
|
|
166
|
+
}: { children?: React.ReactNode; className?: string }) {
|
|
167
|
+
return (
|
|
168
|
+
<button type="button" className={className} onClick={() => window.print()}>
|
|
169
|
+
{children}
|
|
170
|
+
</button>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Vue
|
|
176
|
+
|
|
177
|
+
```vue
|
|
178
|
+
<template>
|
|
179
|
+
<button type="button" @click="() => window.print()">
|
|
180
|
+
<slot>Save as PDF</slot>
|
|
181
|
+
</button>
|
|
182
|
+
</template>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Svelte
|
|
186
|
+
|
|
187
|
+
```svelte
|
|
188
|
+
<button type="button" on:click={() => window.print()}>
|
|
189
|
+
<slot>Save as PDF</slot>
|
|
190
|
+
</button>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Vanilla (Web Component)
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
class PrintButton extends HTMLElement {
|
|
197
|
+
connectedCallback() {
|
|
198
|
+
const btn = document.createElement("button");
|
|
199
|
+
btn.type = "button";
|
|
200
|
+
btn.textContent = this.textContent.trim() || "Save as PDF";
|
|
201
|
+
this.textContent = "";
|
|
202
|
+
btn.addEventListener("click", () => window.print());
|
|
203
|
+
this.appendChild(btn);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
customElements.define("print-button", PrintButton);
|
|
207
|
+
|
|
208
|
+
// Usage: <print-button>Save as PDF</print-button>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Variants
|
|
212
|
+
|
|
213
|
+
### International paper (A4) / landscape
|
|
214
|
+
|
|
215
|
+
Pass the size through `print-base` (in the global stylesheet, at the root), and set orientation on `@page`:
|
|
216
|
+
|
|
217
|
+
```scss
|
|
218
|
+
// app/globals.scss — top level, not inside a selector, not in a component module
|
|
219
|
+
@include cia.print-base($size: A4, $margin: 0.75in);
|
|
220
|
+
|
|
221
|
+
@include cia.print {
|
|
222
|
+
@page { size: A4 landscape; }
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Force a page break before a section
|
|
227
|
+
|
|
228
|
+
Start a new sheet at a major boundary (a new invoice, a new chapter):
|
|
229
|
+
|
|
230
|
+
```scss
|
|
231
|
+
.section-start {
|
|
232
|
+
@include cia.print { break-before: page; }
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Print-only content (URL footer, "printed on" stamp)
|
|
237
|
+
|
|
238
|
+
Content that should appear *only* on paper, hidden on screen:
|
|
239
|
+
|
|
240
|
+
```scss
|
|
241
|
+
.print-footer { @include cia.print-only; }
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Pitfalls
|
|
245
|
+
|
|
246
|
+
These are the bugs that *will* bite — each one cost real debugging time. This list is the value.
|
|
247
|
+
|
|
248
|
+
- **Animations snapshot invisible.** Entrance fades and scroll reveals often start at `opacity: 0`; a PDF captured mid-animation prints **blank**. `print-base` collapses animations to zero duration and pins them to their final frame, so the fade lands visible. It does *not* force `opacity: 1` / `transform: none` — that would also flatten deliberate translucency and rotation. Elements that weren't animating keep their own styling. Keep the freeze on unless you have a specific reason not to.
|
|
249
|
+
- **A blank trailing page.** A few invisible pixels of trailing `margin`/`padding`/`border` on the last element spill an empty final sheet. Zero them: `@include cia.print { .doc > :last-child { margin-block-end: 0; border-block-end: none; } }`. Also watch a full-height scroll/perspective wrapper (`height: 100vh`, `overflow`, `perspective`) — in print set `overflow: visible` and let content flow, or it clips paged output.
|
|
250
|
+
- **Light-on-dark text becomes white-on-white.** Anything styled light text on a dark surface vanishes on a white sheet. The cia fix is the `color-scheme: light` flip in `print-base`'s block above — it lands every `light-dark()` token on its light value. For a one-off, override the single element with `@include cia.print { color: cia.color(text-secondary); }`.
|
|
251
|
+
- **Background colors are off by default.** Browsers strip background colors and images when printing to save ink. If your design depends on them, tell users to tick "Background graphics" (Chrome) / "Print backgrounds" (Firefox/Safari) in the print dialog — there's no CSS that forces it on.
|
|
252
|
+
- **Links lose their destination.** A clickable link is dead on paper — print the URL after it:
|
|
253
|
+
```scss
|
|
254
|
+
@include cia.print {
|
|
255
|
+
a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; word-break: break-all; }
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
- **Page breaks split things awkwardly.** Keep headings with their content and don't split atomic blocks: `@include cia.print { h2, h3 { break-after: avoid; } li, .card { break-inside: avoid; } .doc { orphans: 3; widows: 3; } }`.
|
|
259
|
+
- **`px` gets fuzzy in print.** Use `pt` / `in` for print typography and spacing — `px` is a screen unit and scales unpredictably across print zoom.
|
|
260
|
+
|
|
261
|
+
## Related recipes
|
|
262
|
+
|
|
263
|
+
- [`dialog`](./dialog.md) — a print-only summary often lives inside a confirmation dialog before export
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# scss/theme
|
|
2
|
+
|
|
3
|
+
The canonical **token source** for css-is-awesome. These Sass partials are
|
|
4
|
+
read at compile time by `_generator.scss` and the library mixins; they emit
|
|
5
|
+
the baseline CSS custom properties on `:root` that the whole system reads
|
|
6
|
+
through `var(--token, fallback)`. Edit a value here and rebuild — every
|
|
7
|
+
component picks it up. This is library-author territory, not consumer
|
|
8
|
+
territory.
|
|
9
|
+
|
|
10
|
+
## scss/theme/ vs public/themes/
|
|
11
|
+
|
|
12
|
+
Easy to confuse. They sit at opposite ends of the pipeline:
|
|
13
|
+
|
|
14
|
+
| Layer | `scss/theme/` (this folder) | `public/themes/<name>/theme.css` |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| When it runs | **Compile time** (Sass → CSS) | **Runtime** (browser, no build) |
|
|
17
|
+
| Format | `_*.scss` partials, Sass maps | Plain CSS custom properties |
|
|
18
|
+
| Audience | Library authors / contributors | Consumers swapping looks |
|
|
19
|
+
| What it produces | The default `:root { … }` block | A `[data-theme="<name>"] { … }` override block |
|
|
20
|
+
| Swap mechanism | Recompile the package | Drop in a new file, set `<html data-theme="…">` |
|
|
21
|
+
| Source of truth for | Defaults, scales, contracts | One specific palette / mood |
|
|
22
|
+
|
|
23
|
+
If you want to author a new theme, you almost certainly want
|
|
24
|
+
[`../../CONTRIBUTING-THEMES.md`](../../CONTRIBUTING-THEMES.md), not this
|
|
25
|
+
folder.
|
|
26
|
+
|
|
27
|
+
## Files
|
|
28
|
+
|
|
29
|
+
| File | Owns |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `_index.scss` | Barrel. `@use`s every partial in cascade order and re-exports the maps with the `$theme-*` names the rest of the system expects. Also defines the spacing scale (`$space`, `$gap`, `$padding`, `$margin`) so spacing lives next to its sibling tokens. |
|
|
32
|
+
| `_brand.scss` | `$brand` — primary / secondary / accent identity colors (and their hover/active variants). `_colors-light.scss` intentionally references these for `action-primary-*`. |
|
|
33
|
+
| `_colors-light.scss` | `$colors-light` — semantic palette for light mode: backgrounds, surfaces, text, borders, interactive states, actions, status colors. |
|
|
34
|
+
| `_colors-dark.scss` | `$colors-dark` — same semantic keys as light, dark-mode values. |
|
|
35
|
+
| `_typography.scss` | `$fonts` — primary / secondary / mono font stacks. |
|
|
36
|
+
| `_shape.scss` | `$radius` — border-radius scale (`none`, `sm`, `md`, `lg`, `xl`, `2xl`, `full`). |
|
|
37
|
+
| `_shadows.scss` | `$shadows-light` / `$shadows-dark` — elevation scales. Numbered scale 1..5 is the source of truth; t-shirt aliases (`sm`..`2xl`) layer on top. |
|
|
38
|
+
| `_icons.scss` | Icon configuration: SVG pack path (`$icon-path`, `$icon-pack`), default size, name aliases, and the Font Awesome wiring (`$fa-path`, `$fa-style`, `$fa-pro`). |
|
|
39
|
+
| `_components.scss` | `$components` — per-component runtime override map (e.g. `btn-radius`, `btn-padding-y`). Each entry is emitted as a CSS custom property AND used as the fallback inside `var(--key, …)` in the matching mixin. Keep in sync with `scss/_mixins.scss` and `scss/components/*.scss`. |
|
|
40
|
+
| `registry.json` | Static snapshot of preset palettes (sketchbook, royal-blue, grinning-gremlin, custom, etc.) carried over from the original boilerplate. **Not imported by the build, the validator, or the docs site** — it predates the runtime `public/themes/*` model. Treat it as historical reference; the live themes are the CSS files under `public/themes/`. |
|
|
41
|
+
|
|
42
|
+
## Cascade order (`_index.scss`)
|
|
43
|
+
|
|
44
|
+
```scss
|
|
45
|
+
@use './brand' as *; // identity colors first — referenced by colors
|
|
46
|
+
@use './colors-light' as *;
|
|
47
|
+
@use './colors-dark' as *;
|
|
48
|
+
@use './typography' as *;
|
|
49
|
+
@use './shape' as *;
|
|
50
|
+
@use './shadows' as *;
|
|
51
|
+
@use './components' as *; // last — may reference any of the above via var()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Order matters: `_colors-light.scss` reads `$brand` for `action-primary-*`,
|
|
55
|
+
and `_components.scss` references the radius / spacing / color tokens that
|
|
56
|
+
earlier partials define. Re-ordering will break compilation or silently
|
|
57
|
+
swap defaults.
|
|
58
|
+
|
|
59
|
+
## Adding a NEW theme
|
|
60
|
+
|
|
61
|
+
You almost certainly do **not** edit this folder. Themes are runtime CSS
|
|
62
|
+
files. See [`../../CONTRIBUTING-THEMES.md`](../../CONTRIBUTING-THEMES.md)
|
|
63
|
+
for the contract, the validator, and the PR checklist.
|
|
64
|
+
|
|
65
|
+
Edit this folder only when you are changing the **defaults the library
|
|
66
|
+
itself ships with** — adding a new token to the contract, retuning the
|
|
67
|
+
spacing scale, or wiring a new component map.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// BRAND COLORS - Royal Blue Theme
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// All colors WCAG AA compliant for accessibility
|
|
5
|
+
// Note: action-primary-* in _colors-light.scss intentionally references these
|
|
6
|
+
// brand values. Brand = identity, action-primary = semantic usage of brand.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
$brand: (
|
|
10
|
+
primary: #3A5FCD,
|
|
11
|
+
primary-hover: #2E4A99,
|
|
12
|
+
primary-active: #1E3A8A,
|
|
13
|
+
secondary: #FF6B6B,
|
|
14
|
+
secondary-hover:#E85555,
|
|
15
|
+
secondary-active:#C55,
|
|
16
|
+
accent: #09F,
|
|
17
|
+
accent-hover: #0088E6,
|
|
18
|
+
) !default;
|