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,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.
|