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.
Files changed (198) hide show
  1. package/AGENTS.md +343 -0
  2. package/CHANGELOG.md +1036 -0
  3. package/CLAUDE.md +9 -0
  4. package/CONTRACT.md +580 -0
  5. package/GEMINI.md +9 -0
  6. package/LICENSE +21 -0
  7. package/LICENSE-third-party +88 -0
  8. package/MIGRATION.md +312 -0
  9. package/README.md +380 -0
  10. package/ROADMAP.md +714 -0
  11. package/THEMING.md +290 -0
  12. package/THREE-TIERS.md +158 -0
  13. package/VERSIONING.md +200 -0
  14. package/_index.scss +34 -0
  15. package/api.scss +16 -0
  16. package/bin/README.md +57 -0
  17. package/bin/cia.cjs +114 -0
  18. package/bin/migrate-bootstrap.cjs +427 -0
  19. package/bin/migrate-tailwind.cjs +832 -0
  20. package/css-is-awesome.instructions.md +533 -0
  21. package/dist/css-is-awesome.core.css +598 -0
  22. package/dist/css-is-awesome.core.min.css +1 -0
  23. package/dist/css-is-awesome.css +3733 -0
  24. package/dist/css-is-awesome.min.css +1 -0
  25. package/dist/css-is-awesome.utilities.css +2445 -0
  26. package/dist/css-is-awesome.utilities.min.css +1 -0
  27. package/dist/tokens.css +508 -0
  28. package/dist/tokens.d.ts +266 -0
  29. package/dist/tokens.min.css +1 -0
  30. package/figma-tokens/README.md +57 -0
  31. package/figma-tokens/primitives-brand.scss +121 -0
  32. package/figma-tokens/semantic-dark.scss +61 -0
  33. package/figma-tokens/semantic-light.scss +61 -0
  34. package/figma-tokens/tokens.json +144 -0
  35. package/llm.txt +183 -0
  36. package/mcp/server.cjs +1495 -0
  37. package/package.json +199 -0
  38. package/public/icons/README.md +278 -0
  39. package/public/icons/arrow-right.svg +4 -0
  40. package/public/icons/check.svg +4 -0
  41. package/public/icons/chevron-down.svg +4 -0
  42. package/public/icons/close.svg +4 -0
  43. package/public/icons/core/arrow-down.svg +13 -0
  44. package/public/icons/core/arrow-left.svg +13 -0
  45. package/public/icons/core/arrow-right.svg +13 -0
  46. package/public/icons/core/arrow-up.svg +13 -0
  47. package/public/icons/core/bell.svg +13 -0
  48. package/public/icons/core/calendar.svg +15 -0
  49. package/public/icons/core/check.svg +12 -0
  50. package/public/icons/core/chevron-down.svg +12 -0
  51. package/public/icons/core/chevron-left.svg +12 -0
  52. package/public/icons/core/chevron-right.svg +12 -0
  53. package/public/icons/core/chevron-up.svg +12 -0
  54. package/public/icons/core/clock.svg +13 -0
  55. package/public/icons/core/close.svg +13 -0
  56. package/public/icons/core/copy.svg +13 -0
  57. package/public/icons/core/download.svg +14 -0
  58. package/public/icons/core/edit.svg +13 -0
  59. package/public/icons/core/error.svg +14 -0
  60. package/public/icons/core/external-link.svg +14 -0
  61. package/public/icons/core/eye-off.svg +15 -0
  62. package/public/icons/core/eye.svg +13 -0
  63. package/public/icons/core/filter.svg +12 -0
  64. package/public/icons/core/heart.svg +12 -0
  65. package/public/icons/core/help.svg +14 -0
  66. package/public/icons/core/home.svg +13 -0
  67. package/public/icons/core/info.svg +14 -0
  68. package/public/icons/core/loading.svg +12 -0
  69. package/public/icons/core/lock.svg +13 -0
  70. package/public/icons/core/mail.svg +13 -0
  71. package/public/icons/core/menu.svg +14 -0
  72. package/public/icons/core/message.svg +12 -0
  73. package/public/icons/core/minus.svg +12 -0
  74. package/public/icons/core/more-horizontal.svg +14 -0
  75. package/public/icons/core/pause.svg +13 -0
  76. package/public/icons/core/play.svg +12 -0
  77. package/public/icons/core/plus.svg +13 -0
  78. package/public/icons/core/refresh.svg +15 -0
  79. package/public/icons/core/save.svg +14 -0
  80. package/public/icons/core/search.svg +13 -0
  81. package/public/icons/core/settings.svg +13 -0
  82. package/public/icons/core/share.svg +14 -0
  83. package/public/icons/core/sort.svg +15 -0
  84. package/public/icons/core/star.svg +12 -0
  85. package/public/icons/core/success.svg +13 -0
  86. package/public/icons/core/trash.svg +16 -0
  87. package/public/icons/core/unlock.svg +13 -0
  88. package/public/icons/core/upload.svg +14 -0
  89. package/public/icons/core/user.svg +13 -0
  90. package/public/icons/core/users.svg +15 -0
  91. package/public/icons/core/warning.svg +14 -0
  92. package/public/icons/download.svg +4 -0
  93. package/public/icons/edit.svg +4 -0
  94. package/public/icons/menu.svg +4 -0
  95. package/public/icons/search.svg +4 -0
  96. package/public/theme.css +4179 -0
  97. package/public/themes/README.md +102 -0
  98. package/public/themes/boilerplate/theme.css +154 -0
  99. package/public/themes/boilerplate-dark/theme.css +171 -0
  100. package/public/themes/boilerplate-light/theme.css +171 -0
  101. package/public/themes/cupertino/theme.css +165 -0
  102. package/public/themes/cupertino-dark/theme.css +200 -0
  103. package/public/themes/cupertino-light/theme.css +227 -0
  104. package/public/themes/glass/theme.css +190 -0
  105. package/public/themes/glass-dark/theme.css +201 -0
  106. package/public/themes/glass-light/theme.css +229 -0
  107. package/public/themes/graphite/theme.css +155 -0
  108. package/public/themes/graphite-dark/theme.css +223 -0
  109. package/public/themes/graphite-light/theme.css +191 -0
  110. package/public/themes/press/theme.css +189 -0
  111. package/public/themes/press-dark/theme.css +199 -0
  112. package/public/themes/press-light/theme.css +235 -0
  113. package/public/themes/prism/theme.css +159 -0
  114. package/public/themes/prism-dark/theme.css +185 -0
  115. package/public/themes/prism-light/theme.css +188 -0
  116. package/public/themes/sketchbook/theme.css +152 -0
  117. package/public/themes/sketchbook-dark/theme.css +194 -0
  118. package/public/themes/sketchbook-light/theme.css +188 -0
  119. package/public/themes/terminal/theme.css +159 -0
  120. package/public/themes/terminal-dark/theme.css +229 -0
  121. package/public/themes/terminal-light/theme.css +152 -0
  122. package/scripts/README.md +70 -0
  123. package/scripts/icon-contract.json +67 -0
  124. package/scripts/icon-validator.js +205 -0
  125. package/scripts/prepare-dist.mjs +65 -0
  126. package/scripts/theme-a11y.js +415 -0
  127. package/scripts/theme-contract.json +171 -0
  128. package/scripts/theme-validator.js +674 -0
  129. package/scss/README.md +55 -0
  130. package/scss/_animations-utilities.scss +107 -0
  131. package/scss/_animations.scss +248 -0
  132. package/scss/_app-styles.scss +15 -0
  133. package/scss/_generator.scss +174 -0
  134. package/scss/_icons.scss +475 -0
  135. package/scss/_index.scss +17 -0
  136. package/scss/_layout.scss +597 -0
  137. package/scss/_mixins.scss +1036 -0
  138. package/scss/_system.scss +291 -0
  139. package/scss/_utilities.scss +377 -0
  140. package/scss/api.scss +61 -0
  141. package/scss/components/_accordion.scss +72 -0
  142. package/scss/components/_buttons.scss +171 -0
  143. package/scss/components/_copy-button.scss +69 -0
  144. package/scss/components/_data.scss +190 -0
  145. package/scss/components/_feedback.scss +197 -0
  146. package/scss/components/_forms.scss +272 -0
  147. package/scss/components/_index.scss +17 -0
  148. package/scss/components/_navigation.scss +186 -0
  149. package/scss/components/_overlay.scss +259 -0
  150. package/scss/components/_stepper.scss +159 -0
  151. package/scss/components/_tabs.scss +72 -0
  152. package/scss/core.scss +54 -0
  153. package/scss/examples/_theming.scss +107 -0
  154. package/scss/examples/_usage.scss +317 -0
  155. package/scss/main.scss +64 -0
  156. package/scss/recipes/README.md +110 -0
  157. package/scss/recipes/_bare-tags.scss +232 -0
  158. package/scss/recipes/_recipe-template.md +119 -0
  159. package/scss/recipes/combobox.md +509 -0
  160. package/scss/recipes/dialog.md +265 -0
  161. package/scss/recipes/print-to-pdf.md +263 -0
  162. package/scss/theme/README.md +67 -0
  163. package/scss/theme/_brand.scss +18 -0
  164. package/scss/theme/_colors-dark.scss +68 -0
  165. package/scss/theme/_colors-light.scss +70 -0
  166. package/scss/theme/_components.scss +94 -0
  167. package/scss/theme/_icons.scss +104 -0
  168. package/scss/theme/_index.scss +62 -0
  169. package/scss/theme/_shadows.scss +65 -0
  170. package/scss/theme/_shape.scss +13 -0
  171. package/scss/theme/_typography.scss +9 -0
  172. package/scss/theme/registry.json +447 -0
  173. package/scss/themes/boilerplate-dark.scss +212 -0
  174. package/scss/themes/boilerplate-light.scss +209 -0
  175. package/scss/themes/boilerplate.scss +181 -0
  176. package/scss/themes/cupertino-dark.scss +238 -0
  177. package/scss/themes/cupertino-light.scss +266 -0
  178. package/scss/themes/cupertino.scss +194 -0
  179. package/scss/themes/glass-dark.scss +238 -0
  180. package/scss/themes/glass-light.scss +266 -0
  181. package/scss/themes/glass.scss +220 -0
  182. package/scss/themes/graphite-dark.scss +260 -0
  183. package/scss/themes/graphite-light.scss +228 -0
  184. package/scss/themes/graphite.scss +182 -0
  185. package/scss/themes/press-dark.scss +237 -0
  186. package/scss/themes/press-light.scss +273 -0
  187. package/scss/themes/press.scss +241 -0
  188. package/scss/themes/prism-dark.scss +217 -0
  189. package/scss/themes/prism-light.scss +220 -0
  190. package/scss/themes/prism.scss +190 -0
  191. package/scss/themes/sketchbook-dark.scss +231 -0
  192. package/scss/themes/sketchbook-light.scss +231 -0
  193. package/scss/themes/sketchbook.scss +178 -0
  194. package/scss/themes/terminal-dark.scss +267 -0
  195. package/scss/themes/terminal-light.scss +175 -0
  196. package/scss/themes/terminal.scss +185 -0
  197. package/scss/tokens.scss +23 -0
  198. package/scss/utilities-only.scss +4 -0
@@ -0,0 +1,533 @@
1
+ ---
2
+ applyTo: "**"
3
+ ---
4
+
5
+ # css-is-awesome — system instructions
6
+
7
+ Authoring rules for css-is-awesome itself AND for consumer apps that use it. Drop this file at the repo root of any project that consumes the library; AI assistants (Cursor, GitHub Copilot, Claude Code, etc.) will pick it up via the `applyTo:` glob.
8
+
9
+ ## TL;DR — three consumer tiers, pick the lowest one that works for you
10
+
11
+ 1. **Tier 2 — mixins (the primary path).** `@use 'css-is-awesome/api' as cia;` then `.your-class { @include cia.btn(primary, $px: 6); }`. You name the selector; cia supplies the look. Needs a Sass build.
12
+ 2. **Tier 1 — drop-in CSS (no build).** Link `dist/css-is-awesome.css` and a theme file, then use `.cia-*` utility classes in markup.
13
+ 3. **Tier 3 — bare tags (Pico-mode).** `@use 'css-is-awesome/scss/recipes/bare-tags';` styles every common HTML element, wrapped in `:where()` so your own styles always win.
14
+
15
+ **cia ships zero JavaScript and no component library.** There is no `<Button>` to import from this package. If you want React components, you write them in your app and style them with cia mixins; for interactive patterns read the *recipes* (see below) rather than inventing markup.
16
+
17
+ You only write per-component SCSS when you're deviating from a base mixin's defaults — and even then, deviate through the mixin's **arguments**, not hand-written CSS.
18
+
19
+ ---
20
+
21
+ ## File structure (React components)
22
+
23
+ Convention for **your** app's components (and cia's own docs site) — the npm package itself ships no React. Each component lives in its own folder under `src/components/`:
24
+
25
+ ```
26
+ src/components/Button/
27
+ ├── Button.tsx (PascalCase — the component file)
28
+ ├── Button.module.scss (PascalCase — the styles, OPTIONAL)
29
+ ├── button.types.ts (kebab-case — types, OPTIONAL)
30
+ └── index.ts (re-export barrel)
31
+ ```
32
+
33
+ - **Component file** — PascalCase: `Button.tsx`.
34
+ - **Style module** — PascalCase: `Button.module.scss`. **Optional** — only create when you're overriding a base mixin or adding component-specific styles. Don't create empty SCSS files.
35
+ - **Types file** — kebab-case: `button.types.ts`. **Optional** — only when prop unions are non-trivial (multiple discriminated variants, exported sub-types, generic constraints). Tiny components can keep types inline in the .tsx file.
36
+ - **Barrel** — `index.ts` re-exports default + named exports.
37
+
38
+ ---
39
+
40
+ ## Styling — when to write SCSS, when not to
41
+
42
+ ### Don't write SCSS when
43
+
44
+ You're applying the default look and Tier 1 utilities are enabled — put `class="cia-btn cia-btn-primary"` straight in the markup, no stylesheet needed.
45
+
46
+ ### Do write SCSS when
47
+
48
+ You're deviating from a base mixin's defaults. Then:
49
+
50
+ ```scss
51
+ // MyButton.module.scss
52
+ @use 'css-is-awesome/api' as cia;
53
+
54
+ .myCta { @include cia.btn(primary, $px: 6, $r: full); }
55
+ ```
56
+
57
+ ### Mixin-first
58
+
59
+ Every visual primitive in the library is a mixin (`btn-base`, `card-base`, `input-base`, `tag`, etc.) plus a router (`btn($variant)`, `alert($status)`, `badge($status)`). Variants are reached by name, not by class. The mixin is the API; classes (utilities + React component classes) are consumers of it.
60
+
61
+ ### Apply token-driven values, not literals
62
+
63
+ ```scss
64
+ // Yes — token-driven, theme-swap-safe.
65
+ padding-block: cia.space(2);
66
+ color: cia.color(text-primary);
67
+ border-radius: cia.radius(md);
68
+
69
+ // No — literal, breaks theme-swap.
70
+ padding: 8px;
71
+ color: #2A241E;
72
+ border-radius: 4px;
73
+ ```
74
+
75
+ Tokens come from the theme contract (`scripts/theme-contract.json` — **127 required + 36 optional = 163 slots**). A single theme file styles the page on its own (it emits a bare `:root`); when several themes are loaded together they swap via `<html data-theme="press-light">`. Either way every token resolves to the active theme's value.
76
+
77
+ Spacing is a token too. `cia.space(4)` resolves to `var(--space-4)`, and the numbered scale `--space-0`…`--space-9` is contract-required, so a theme can re-proportion the page and not just recolor it.
78
+
79
+ ### Flex via `cia.flex`
80
+
81
+ `cia.flex` is the one flex primitive. Pass only what differs from the
82
+ defaults — `$direction: row`, `$align: center`, `$justify: start`,
83
+ `$wrap: nowrap`, `$gap: null`, `$inline: false`.
84
+
85
+ ```scss
86
+ // Header bar / accordion trigger
87
+ @include cia.flex($justify: between, $gap: 3);
88
+
89
+ // Vertical stack with gap
90
+ @include cia.flex($direction: column, $gap: 4);
91
+
92
+ // Perfectly centered children
93
+ @include cia.flex($justify: center);
94
+
95
+ // Inline chip lockup
96
+ @include cia.flex($inline: true, $gap: 2);
97
+ ```
98
+
99
+ `$justify` accepts the shorthand `start`/`end`/`center`/`between`/
100
+ `around`/`evenly` (mapped to `flex-start`/`flex-end`/`space-between`/
101
+ etc. on emit). `$align` accepts `start`/`end`/`center`/`baseline`/
102
+ `stretch`. Full CSS values pass through unchanged, so
103
+ `$justify: space-between` still works if you prefer the long form.
104
+ The shorthand matches the `cia-justify-between` / `cia-items-center`
105
+ utility-class vocabulary.
106
+
107
+ ### No `!important`
108
+
109
+ Banned. Stylelint enforces (`declaration-no-important: true`). Sole exception: inside `@media (prefers-reduced-motion: reduce)` overrides for accessibility, where `!important` is the canonical pattern (and explicitly disabled with a comment).
110
+
111
+ If you're tempted to reach for `!important`, the right answer is one of: (a) use `:where(...)` to drop the library selector to specificity 0, (b) override the relevant CSS variable, (c) call the mixin with explicit args.
112
+
113
+ ### Logical properties — required for new code
114
+
115
+ Use `padding-block` / `padding-inline` / `margin-block` / `margin-inline` / `border-block-start` / etc. Don't use `padding-top` / `margin-left` / `border-left` etc. unless you specifically need physical (rare).
116
+
117
+ ```scss
118
+ /* Yes */
119
+ padding-block: cia.space(2);
120
+ padding-inline: cia.space(4);
121
+ margin-block-end: cia.space(3);
122
+ border-block-end: 1px solid cia.color(border-default);
123
+
124
+ /* No */
125
+ padding-top: cia.space(2);
126
+ padding-bottom: cia.space(2);
127
+ padding-left: cia.space(4);
128
+ padding-right: cia.space(4);
129
+ margin-bottom: cia.space(3);
130
+ border-bottom: 1px solid cia.color(border-default);
131
+ ```
132
+
133
+ Logical properties auto-flip for RTL languages (Arabic, Hebrew) and vertical writing modes. The library's internal mixins emit logical properties.
134
+
135
+ > **Note on the public utility classes (`.cia-mt-*`, `.cia-mr-*`, `.cia-pl-*`, etc.):** these still emit physical properties in v1.x to preserve API stability for early adopters. They will migrate to logical properties in v2.x.
136
+
137
+ ### Pseudo-elements over decorative DOM
138
+
139
+ Decorative? Use `::before` / `::after`. Functional (focusable, interactive, semantic)? Use a real element.
140
+
141
+ ```tsx
142
+ // Yes — decoration via pseudo-element
143
+ <button className="my-btn-with-shine">Save</button>
144
+
145
+ // .my-btn-with-shine::after { content: ""; ... }
146
+
147
+ // No — decoration via wrapper
148
+ <button>
149
+ <span className="shine-overlay" />
150
+ Save
151
+ </button>
152
+ ```
153
+
154
+ Pseudo-elements aren't focusable, aren't tab stops, screen readers ignore them by default — exactly what you want for decoration.
155
+
156
+ ### Sizing units
157
+
158
+ Order of preference: **rem → em → vw/vh → ch → %**.
159
+
160
+ - `rem` for spacing, font sizes, layout (consistent scale, scales with user font preferences).
161
+ - `em` for sizes relative to current element font size.
162
+ - `vw` / `vh` (or `svw` / `svh`) for viewport-relative layout. Use common fractions: `25vw`, `50vw`, `75vw`.
163
+ - `ch` for text-width sizing.
164
+ - `%` only when the size genuinely must be a percentage of the parent (`width: 100%` to fill).
165
+
166
+ ### Print / PDF
167
+
168
+ Print is a pure-CSS layer — zero JS. "The page IS the PDF source." The
169
+ browser's native Print → Save as PDF is the generator; cia just supplies the
170
+ `@media print` styling. Four mixins:
171
+
172
+ - **`print`** — bare `@media print { @content }` wrapper. Co-locate it inside
173
+ a selector to override that element on paper.
174
+ - **`print-base($freeze-animations: true, $size: letter, $margin: 0.5in)`** —
175
+ page-level defaults, ON by default. Include it **once at the stylesheet
176
+ ROOT** — it emits `@page` (invalid when nested in a selector), freezes
177
+ animations so nothing prints invisible, and emits the print variable
178
+ control plane.
179
+ - **`print-hidden`** — hide an element on paper (the "hide the nav" case).
180
+ - **`print-only`** — show an element only on paper (e.g. an inline URL
181
+ footer); hidden on screen.
182
+
183
+ ```scss
184
+ @use 'css-is-awesome/api' as cia;
185
+ @include cia.print-base; // at ROOT — sets @page, freezes animations, emits vars
186
+ .site-nav { @include cia.print-hidden; } // hide chrome on paper
187
+ ```
188
+
189
+ `print-base` emits three custom properties — the variable filter system:
190
+
191
+ - **`--is-print`** — `0` on screen, `1` on paper. Read it in `calc()` /
192
+ `opacity` / `@container style(--is-print: 1)` for custom print effects.
193
+ - **`--print-hide`** — display applied to `print-hidden` elements (default
194
+ `none`). Override locally (`--print-hide: revert`) to keep one element on
195
+ paper with no rule rewrite.
196
+ - **`--print-show`** — display applied to `print-only` elements (default
197
+ `revert`). Override locally (`--print-show: flex`) to lay out a print-only
198
+ block.
199
+
200
+ Visibility is variable-driven by design: override `--print-hide` /
201
+ `--print-show` per element instead of rewriting rules.
202
+
203
+ ---
204
+
205
+ ## HTML & semantic structure
206
+
207
+ ### Use native interactive elements
208
+
209
+ ```tsx
210
+ // Yes
211
+ <button onClick={handleClose}>Close</button>
212
+ <a href="/about">About</a>
213
+ <dialog open={isOpen}>...</dialog>
214
+ <input type="checkbox" />
215
+
216
+ // No — never
217
+ <div onClick={handleClose}>Close</div>
218
+ <span onClick={navigate}>About</span>
219
+ <div role="dialog">...</div> // unless you're doing something a real <dialog> can't
220
+ ```
221
+
222
+ Native elements come with keyboard handling, focus management, and screen-reader semantics for free. `<div onclick>` requires re-implementing all three; people miss steps.
223
+
224
+ Enforced by `eslint-plugin-jsx-a11y` (already in `next/core-web-vitals`).
225
+
226
+ ### Form controls need labels
227
+
228
+ ```tsx
229
+ // Yes
230
+ <label htmlFor="email">Email</label>
231
+ <input id="email" type="email" />
232
+
233
+ // or wrap
234
+ <label>
235
+ Email
236
+ <input type="email" />
237
+ </label>
238
+
239
+ // or your own field wrapper, styled with cia.form-group / cia.label-base / cia.input-base
240
+ <FormField label="Email"><input type="email" /></FormField>
241
+ ```
242
+
243
+ ### Element hierarchy guidance
244
+
245
+ For new components, prefer the most semantic native root: `<button>` for a button, `<dialog>` for a modal, `<table>` for tabular data, `<nav>` for navigation, `<article>` for self-contained content blocks, `<section>` for major content groupings. **Avoid `<div>` as the root unless no semantic element fits.**
246
+
247
+ The library does NOT enforce a single root element across all components — `Button` is `<button>`, `Modal` is `<dialog>`, `DataTable` is `<table>`, etc.
248
+
249
+ ### No same-element nesting
250
+
251
+ Never nest an element inside the same element type:
252
+
253
+ - No `<div>` inside `<div>`. Find the semantic tag that belongs there (`<header>`, `<nav>`, `<aside>`, `<section>`, `<article>`).
254
+ - No `<span>` inside `<span>`. Same — pick the semantic inline tag.
255
+ - No `<p>` inside `<p>`. Browsers will silently break this; the inner `<p>` closes the outer.
256
+
257
+ ### Minimal DOM
258
+
259
+ Every element must earn its place. If removing it changes nothing visually or semantically, remove it. Use pseudo-elements for decoration (see above).
260
+
261
+ ---
262
+
263
+ ## TypeScript
264
+
265
+ ### `type` for props, `interface` for data models
266
+
267
+ ```ts
268
+ // Component props
269
+ type ButtonProps = {
270
+ variant?: "primary" | "secondary" | "outline" | "ghost";
271
+ onClick?: () => void;
272
+ };
273
+
274
+ // Data entities
275
+ interface User {
276
+ id: string;
277
+ email: string;
278
+ name: string;
279
+ }
280
+ ```
281
+
282
+ `type` for value shapes, function signatures, unions, intersections. `interface` for entities that might be extended (API responses, domain objects, things you might `extends` later).
283
+
284
+ ### Co-locate types
285
+
286
+ Tiny components keep types inline in the `.tsx`. Components with non-trivial type surface (`DataTable`, `Tabs`, `Modal` with a discriminated prop union) extract types into a sibling `component-name.types.ts`:
287
+
288
+ ```
289
+ src/components/DataTable/
290
+ ├── DataTable.tsx // component logic only
291
+ ├── data-table.types.ts // Column<T>, SortState, DataTableProps<T>, etc.
292
+ └── DataTable.module.scss
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Component authoring
298
+
299
+ ### Arrow-function default export
300
+
301
+ ```tsx
302
+ const Button = (props: ButtonProps) => {
303
+ // ...
304
+ };
305
+ export default Button;
306
+
307
+ // Named-only (no default) is fine for pure helpers, but components default-export.
308
+ ```
309
+
310
+ For compound components (`Tabs.List`, `Tabs.Trigger`, `Tabs.Panel`), export a default that has named properties attached: `Tabs.List = TabsList; export default Tabs;` — this is the React convention for compound components.
311
+
312
+ ### Internal helpers use arrow functions
313
+
314
+ ```tsx
315
+ const handleClose = () => { ... };
316
+ const formatLabel = (name: string) => name.replace(/-/g, " ");
317
+ ```
318
+
319
+ ### Event handlers prefixed `handle`
320
+
321
+ `handleClose`, `handleSelect`, `handleToggle`, `handleKey`. Prop callbacks for handlers stay as `onClose`, `onSelect`, etc. (the `on*` is the React prop convention; `handle*` is the local function convention).
322
+
323
+ ### Class-name joining via array
324
+
325
+ ```tsx
326
+ className={[styles.btn, variant && styles[variant], className].filter(Boolean).join(" ")}
327
+ ```
328
+
329
+ Don't use template-string concat. Don't reach for `clsx`/`classnames` for the simple cases — the array+filter+join idiom is sufficient and dependency-free.
330
+
331
+ ### State management
332
+
333
+ - Local `useState` for component-specific UI state (open/close, selected item, loading).
334
+ - Lift to context only when multiple unrelated components need the same data.
335
+ - No global stores in this library — consumers can wire their own.
336
+
337
+ ### Conditional rendering
338
+
339
+ ```tsx
340
+ // Guard clause for early null
341
+ if (!open) return null;
342
+
343
+ // Inline ternary for simple branches
344
+ return (
345
+ <section>
346
+ {loading ? <Spinner /> : <List items={items} />}
347
+ </section>
348
+ );
349
+ ```
350
+
351
+ ---
352
+
353
+ ## Theming
354
+
355
+ ### One file = one theme
356
+
357
+ Each theme is a single file declaring all **127 required** contract tokens (plus any of the 36 optional ones it wants). It emits **two selectors at once**:
358
+
359
+ ```css
360
+ :root, :root[data-theme="<name>"] { … }
361
+ ```
362
+
363
+ - The bare `:root` makes a theme **drop-in**: serve one theme file as your `theme.css` and the page restyles with **no markup change**. `data-theme` is optional in that case.
364
+ - The `[data-theme]` half is what lets several themes coexist. `public/theme.css` ships all 24 themes consolidated; there, `<html data-theme="<name>">` is **required**, and the bundle is built with `$standalone: false` so the bare `:root` is dropped and the blocks can't collide.
365
+
366
+ Per-theme files at `public/themes/<name>/theme.css` are also published for download. All shipped blocks pass the WCAG 2.2 AA contrast audit (22 pairs per theme) out of the box.
367
+
368
+ **24 themes across 8 families.** Every family ships three files: the unsuffixed parent (both modes in one file via `light-dark()`) plus explicit `-light` and `-dark` siblings that pin a single `color-scheme`. Families: `sketchbook` (default), `press`, `graphite`, `glass`, `cupertino`, `terminal`, `prism`, and the unbranded `boilerplate` starter. The unsuffixed names are **first-class themes, not backward-compat aliases** — `sketchbook` is the auto-switching one, `sketchbook-light` / `sketchbook-dark` are the pinned ones. `terminal` is the one asymmetry: its unsuffixed file is dark-only (sacred), so `terminal-light` is a separate brand rather than its light mode.
369
+
370
+ Library defaults emit under **`:where(:root)`** (specificity 0,0,0), so any theme declaration outranks them regardless of load order. This is `:where()` and deliberately **not** `@layer` — cia is unlayered by decision.
371
+
372
+ ### Add a theme
373
+
374
+ 1. Read `scripts/theme-contract.json` — declare every required token (127 required slots in v1; 36 more are optional). That includes the numbered spacing scale `--space-0` … `--space-9`.
375
+ 2. Author it through the mixin, never a hand-written selector:
376
+ ```scss
377
+ // @mixin theme($name, $scheme: light dark, $standalone: true)
378
+ @include m.theme('mybrand') { --paper: light-dark(#fff, #0b0b0f); /* … */ }
379
+ ```
380
+ Pass `$standalone: false` only for a block destined for a multi-theme bundle.
381
+ 3. Run `npm run build:css:themes` — it builds every theme **and** regenerates `public/theme.css`. It is part of `npm run build:css:all`.
382
+ 4. Run `npm run validate-themes` to confirm the contract. The validator also runs a WCAG 2.2 AA contrast audit; a11y FAILs are fatal by default. Pass `--allow-a11y-fail` to downgrade contrast failures to a report-only warning while you iterate (the older `--strict` flag is accepted as a no-op alias).
383
+ 5. Run `npm run check:theme-drift` to prove the committed CSS matches the SCSS source. CI runs this **before** `validate-themes`, because `validate-themes` reads the committed CSS and would otherwise pass on a stale artifact.
384
+ 6. Add the theme name to `ThemePicker`'s `THEMES` array and the layout's `VALID_THEMES` set.
385
+
386
+ **Never hand-edit `public/theme.css` or `public/themes/**/theme.css`.** They are generated from `scss/themes/*.scss` and gated by `check:theme-drift`.
387
+
388
+ See `/docs/authoring/themes` for the full guide.
389
+
390
+ ### Spacing is themeable — set the numbered step
391
+
392
+ The numbered scale (`--space-0` … `--space-9`) is the source of truth and is contract-required. The six t-shirt names (`--space-2xs`, `--space-xs`, `--space-sm`, `--space-md`, `--space-lg`, `--space-xl`) are **optional**, and the library emits them as `var()` references:
393
+
394
+ ```css
395
+ --space-md: var(--space-4); /* alias follows the step */
396
+ ```
397
+
398
+ Components call `cia.space(4)` → `var(--space-4)`. The t-shirt names previously emitted as independent literals, so a theme that set only `--space-md` moved a variable nothing read — which is why theme swaps used to recolor the page but never re-proportion it. **Set the numbered step.**
399
+
400
+ ### Radius: the per-component knobs
401
+
402
+ `--radius-avatar` / `--radius-badge` / `--radius-button` / `--radius-card` / `--radius-input` / `--radius-modal` were removed from the contract because nothing read them. Use the knobs that are actually wired: `--btn-radius`, `--card-radius`, `--input-radius`, `--modal-radius`, `--badge-radius`, `--tag-radius` (all optional). Each cascades from a generic radius — `--btn-radius: var(--radius-md, 0.25rem)` — so set `--radius-md` to move everything, or the component knob to move one thing.
403
+
404
+ ### Adding fonts — two lines
405
+
406
+ ```css
407
+ /* 1) Declare the slug in your global stylesheet (globals.css, theme.css, etc.) */
408
+ :root {
409
+ --font-meme: 'Helvetica Neue', Helvetica, Arial, sans-serif;
410
+ }
411
+ ```
412
+
413
+ ```scss
414
+ /* 2) Use it from any component (mixin form OR raw CSS — both work) */
415
+ .logo { @include cia.font($family: meme, $color: text-primary, $lh: 0.95, $ls: -0.01em); }
416
+ .stamp { font-family: var(--font-meme); }
417
+ ```
418
+
419
+ `cia.font` takes every text-style property in one call: `$type` (weight + style preset), `$size`, `$lh`, `$ls`, `$family`, `$color`. Pass only what you need — null defaults skip the emit.
420
+
421
+ `cia.font($family: <slug>)` emits `font-family: var(--font-<slug>);` — no registration, no Sass-side magic. The slug is just a CSS variable name. As long as `--font-<slug>` is declared *somewhere* in scope (globals, theme, page, block), the browser resolves it.
422
+
423
+ **Override anywhere CSS variables work:**
424
+
425
+ ```scss
426
+ :root { --font-meme: 'Inter', sans-serif; } // site-wide
427
+ [data-theme="x"] { --font-meme: 'Press Start 2P', monospace; } // per-theme
428
+ .landing-page { --font-meme: 'Caveat', cursive; } // one page
429
+ .hero { --font-meme: 'Pacifico', cursive; } // one block
430
+ <h1 style="--font-meme: 'Comic Sans MS'"> // one element
431
+ ```
432
+
433
+ **Hosted fonts (Google Fonts / CDN)** — use `cia.font-face(name, url)` from a *global* Sass file (not a `.module.scss`, because CSS Modules' pure mode rejects the `@import` placement). It registers the URL once and emits the `@import url(...)`.
434
+
435
+ ```scss
436
+ // src/styles/fonts.scss (a global .scss imported from layout.tsx)
437
+ @include cia.font-face('Pacifico', 'https://fonts.googleapis.com/css2?family=Pacifico&display=swap');
438
+ ```
439
+
440
+ ```css
441
+ /* src/app/globals.css — declare the variable that components consume */
442
+ :root { --font-pacifico: 'Pacifico', cursive; }
443
+ ```
444
+
445
+ ```scss
446
+ /* component */
447
+ .headline { @include cia.font($family: pacifico); }
448
+ ```
449
+
450
+ **Self-hosted fonts** — `cia.font-face-local('Untitled Sans', '/fonts/UntitledSans.woff2')` for the `@font-face` declaration; declare the CSS variable separately the same way.
451
+
452
+ ---
453
+
454
+ ## Accessibility
455
+
456
+ - Native interactive elements (above) are the foundation.
457
+ - All images have `alt` text.
458
+ - Form controls have `<label>` (above).
459
+ - Use `role` and `aria-*` only when native semantics are insufficient — e.g., disclosure widgets, custom dropdowns, ARIA live regions.
460
+ - Keyboard navigation works: dialogs trap focus, Escape closes overlays, arrow keys cycle through tab lists.
461
+ - Focus rings come from `cia.focus-ring` (or `:focus-visible`) — don't remove the outline without a replacement.
462
+ - Honor `prefers-reduced-motion` — animation mixins do this automatically; don't fight it.
463
+ - Color is never the only cue — pair status colors with an icon or text label.
464
+
465
+ ---
466
+
467
+ ## Recipes — build a component without a component library
468
+
469
+ cia ships **no component library on purpose**. When you need an interactive
470
+ pattern (dialog, combobox, print-to-PDF), read the matching *recipe* instead of
471
+ inventing markup or reaching for a dependency.
472
+
473
+ A recipe is a markdown file at `scss/recipes/<name>.md` carrying:
474
+
475
+ - the raw, correct HTML structure (native elements first)
476
+ - the `cia.X` mixin calls that style it
477
+ - an a11y checklist graded against WCAG 2.2 AA
478
+ - framework-neutral notes so it ports to React / Vue / Svelte / vanilla
479
+
480
+ **Shipped today:** `dialog`, `combobox`, `print-to-pdf`. Queued for 1.0.0:
481
+ `datepicker`, `data-table`, `command-palette`.
482
+
483
+ How to reach them:
484
+
485
+ - **AI agents** — `list_recipes` / `get_recipe(name)` over MCP. Prefer this over
486
+ writing an interactive pattern from memory; the recipe encodes the a11y work.
487
+ - **Humans** — `/docs/recipes`, or read the markdown directly.
488
+
489
+ Note the two different things living in `scss/recipes/`: `<slug>.md` files are
490
+ *pattern* recipes (read them, don't import them), while `_<slug>.scss` files —
491
+ e.g. `_bare-tags.scss` — are real opt-in SCSS you `@use`.
492
+
493
+ ---
494
+
495
+ ## Versioning & contributions
496
+
497
+ - **SemVer** post-1.0 strictly. Breaking changes bump major. See `VERSIONING.md`.
498
+ - **Conventional Commits** drive the auto-changelog. `feat:`, `fix:`, `chore:`, `docs:`, `refactor:` etc.
499
+ - **CONTRIBUTING.md** has the full setup, PR, and review flow.
500
+ - **`.github/ISSUE_TEMPLATE/`** for bug reports, feature requests, theme submissions.
501
+
502
+ ---
503
+
504
+ ## Decision tree — which tier should I use?
505
+
506
+ **Q1: Do you have a Sass build?**
507
+
508
+ - **No** → Tier 1 (drop-in CSS). Link `dist/css-is-awesome.css` + a theme file. Use `.cia-*` utilities in markup.
509
+ - **Yes** → Q2.
510
+
511
+ **Q2: Do you want to style plain HTML elements wholesale (`<h1>`, `<button>`, `<table>`) without adding classes?**
512
+
513
+ - **Yes** → Tier 3 (Pico-mode). `@use 'css-is-awesome/scss/recipes/bare-tags';` once. It's `:where()`-wrapped, so anything you write later wins.
514
+ - **No, I'm styling my own components** → Q3.
515
+
516
+ **Q3: Is there a cia mixin for what you're building?**
517
+
518
+ - **Yes** → Tier 2 (the primary path). `Component.module.scss`, `@use 'css-is-awesome/api' as cia;`, `.myCta { @include cia.btn(primary, $px: 6); }`. Reach every variation through the mixin's **arguments**.
519
+ - **No mixin fits, and it's an interactive pattern (dialog, combobox, …)** → read the matching **recipe** (`get_recipe` over MCP, or `scss/recipes/<name>.md`). Don't invent the markup.
520
+ - **No mixin fits, and it's novel** → compose the primitives (`cia.flex`, `cia.stack`, `cia.pad`, `cia.font`, `cia.color`, `cia.space`). If a visual dimension is only reachable by hand-written CSS, that's a **missing mixin input** — add it.
521
+
522
+ ---
523
+
524
+ ## Anti-patterns (don't do these)
525
+
526
+ - Adding `!important` to "win" a specificity fight. Use `:where()` or CSS variables.
527
+ - Hardcoding hex colors / pixel values. Always go through tokens or mixin args.
528
+ - Creating an empty `*.module.scss` to satisfy a convention. The file is optional.
529
+ - Using `<div onclick>` instead of `<button>`. Native elements first, always.
530
+ - Same-element nesting (`<div>` in `<div>`, `<p>` in `<p>`).
531
+ - Wrapper divs for purely decorative effects. Use `::before` / `::after`.
532
+ - Template-string `className` concatenation. Use `[a, b].filter(Boolean).join(" ")`.
533
+ - Reaching for `next/font` or runtime CSS-in-JS. Tokens + mixins + theme contract are the system.