cssints 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +178 -2
- package/dist/author-types-C1AhsnrT.d.mts +80 -0
- package/dist/check.d.mts +22 -0
- package/dist/check.mjs +37 -0
- package/dist/color-BYIxVLlS.mjs +29 -0
- package/dist/color-C8OzjAeg.mjs +2 -0
- package/dist/contrast-BUKVao74.mjs +83 -0
- package/dist/derived.d.mts +14 -0
- package/dist/derived.mjs +391 -0
- package/dist/errors-D4ZbcwRo.d.mts +32 -0
- package/dist/frame-BoBKjgBB.mjs +92 -0
- package/dist/frame.d.mts +72 -0
- package/dist/frame.mjs +2 -0
- package/dist/index.d.mts +34 -0
- package/dist/index.mjs +1132 -0
- package/dist/instrument-Bdtk2INz.mjs +172 -0
- package/dist/lib-2OEIfPAE.d.mts +9 -0
- package/dist/lowered-CeoXbm0P.mjs +28 -0
- package/dist/node-CdhAGmF3.d.mts +229 -0
- package/dist/parent-vars.d.mts +13 -0
- package/dist/parent-vars.mjs +65 -0
- package/dist/plugin-types.d.mts +377 -0
- package/dist/plugin.d.mts +3 -0
- package/dist/plugin.mjs +30 -0
- package/dist/registry-n9JCX4pl.mjs +72 -0
- package/dist/runtime-CTvuM-Hm.d.mts +5 -0
- package/dist/runtime.d.mts +18 -0
- package/dist/runtime.mjs +21 -0
- package/dist/scope-oWEaWE9n.d.mts +2095 -0
- package/dist/sheet-CO7a-mdA.mjs +756 -0
- package/dist/surface.d.mts +228 -0
- package/dist/surface.mjs +101 -0
- package/dist/theme-BGEbFAYh.mjs +906 -0
- package/dist/theme-types-DHdtTZhz.d.mts +1803 -0
- package/dist/trace-otlp-CbKFDsn9.mjs +14 -0
- package/dist/tui-types.d.mts +181 -0
- package/dist/tui.d.mts +30 -0
- package/dist/tui.mjs +399 -0
- package/dist/types.d.mts +2 -0
- package/dist/vite.d.mts +118 -0
- package/dist/vite.mjs +980 -0
- package/docs/API.md +1148 -0
- package/package.json +81 -4
- package/skills/cssints/SKILL.md +200 -0
- package/skills/cssints/references/errors.md +85 -0
- package/skills/cssints/references/plugins.md +91 -0
- package/skills/cssints/references/tokens.md +91 -0
- package/skills/cssints-plugin/SKILL.md +177 -0
- package/skills/cssints-plugin/references/table.md +124 -0
- package/skills/cssints-plugin/references/testing.md +122 -0
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cssints",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CSS in TypeScript: typed css.* calls compiled to atomic CSS at build time (a Vite plugin)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/alexeiaccio/css-in-ts-compiler.git",
|
|
9
|
+
"directory": "packages/cssints"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://cssints.dev",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"skills",
|
|
15
|
+
"docs"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/types.d.mts",
|
|
22
|
+
"browser": "./dist/runtime.mjs",
|
|
23
|
+
"default": "./dist/index.mjs"
|
|
24
|
+
},
|
|
25
|
+
"./vite": {
|
|
26
|
+
"types": "./dist/vite.d.mts",
|
|
27
|
+
"default": "./dist/vite.mjs"
|
|
28
|
+
},
|
|
29
|
+
"./plugin": {
|
|
30
|
+
"types": "./dist/plugin-types.d.mts",
|
|
31
|
+
"default": "./dist/plugin.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./frame": {
|
|
34
|
+
"types": "./dist/frame.d.mts",
|
|
35
|
+
"default": "./dist/frame.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./surface": {
|
|
38
|
+
"types": "./dist/surface.d.mts",
|
|
39
|
+
"default": "./dist/surface.mjs"
|
|
40
|
+
},
|
|
41
|
+
"./parent-vars": {
|
|
42
|
+
"types": "./dist/parent-vars.d.mts",
|
|
43
|
+
"default": "./dist/parent-vars.mjs"
|
|
44
|
+
},
|
|
45
|
+
"./derived": {
|
|
46
|
+
"types": "./dist/derived.d.mts",
|
|
47
|
+
"default": "./dist/derived.mjs"
|
|
48
|
+
},
|
|
49
|
+
"./check": {
|
|
50
|
+
"types": "./dist/check.d.mts",
|
|
51
|
+
"default": "./dist/check.mjs"
|
|
52
|
+
},
|
|
53
|
+
"./tui": {
|
|
54
|
+
"types": "./dist/tui-types.d.mts",
|
|
55
|
+
"default": "./dist/tui.mjs"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@cssints/css-grammar": "^0.1.0",
|
|
60
|
+
"culori": "^4.0.2",
|
|
61
|
+
"effect": "4.0.0",
|
|
62
|
+
"happy-dom": "^20.14.5",
|
|
63
|
+
"lightningcss": "^1.32.0",
|
|
64
|
+
"magic-string": "^0.30.0",
|
|
65
|
+
"mdn-data": "2.27.1",
|
|
66
|
+
"oxc-parser": "^0.149.0"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"@opencode/client": "^2.0.26",
|
|
70
|
+
"vite": "^8.3.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependenciesMeta": {
|
|
73
|
+
"@opencode/client": {
|
|
74
|
+
"optional": true
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"engines": {
|
|
78
|
+
"node": ">=22"
|
|
79
|
+
},
|
|
80
|
+
"publishConfig": {
|
|
81
|
+
"access": "public"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cssints
|
|
3
|
+
description: >-
|
|
4
|
+
Write, review and fix styles in a project that uses cssints, the CSS-in-TypeScript compiler (a Vite plugin that
|
|
5
|
+
runs css.* calls at build time and replaces them with atomic class strings). Load it when code imports from
|
|
6
|
+
"cssints" with { type: "cssints" }, uses cn(), cx(), cv(), css.* members, media()/hover()/container()/within(),
|
|
7
|
+
createTokens, createGlobalTheme, check.contrast, or an @cssints/* package (layout, motion, fluid, fonts, reset,
|
|
8
|
+
tokens, icons, ...), and when a build fails with a "cssints:" message such as "must be known at build time",
|
|
9
|
+
"without the attribute", "unexpected ... expected <...>", "overlap without one containing the other" or
|
|
10
|
+
"wraps ... the marker of kernel".
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# cssints: styles in TypeScript, compiled at build time
|
|
14
|
+
|
|
15
|
+
Every `css.*` call runs at build time in Vite's Module Runner and is replaced by a class string. The values are
|
|
16
|
+
checked against the CSS grammars (mdn-data) twice: in the types, where the call is, and again by the build, with the
|
|
17
|
+
file and the place of the call. One sheet, `virtual:cssints.css`, holds every rule once, in `@layer`s. The browser
|
|
18
|
+
gets class strings, `cx` and the few lines behind `cv`. The reference is `docs/API.md` of the cssints repository; the
|
|
19
|
+
parts below are what you need most of the time.
|
|
20
|
+
|
|
21
|
+
If the cssints MCP server is configured, prefer it for checks: `check_declaration` (is this value valid, and the
|
|
22
|
+
message the build would give), `check_support` (browser support for the project's browserslist), `check_contrast`,
|
|
23
|
+
`compile` (a module's classes, CSS, errors and warnings), `search_docs` and `list_plugins`. On a running dev server
|
|
24
|
+
of an app with `cssints({ devtools: true })` (`url`, default `http://localhost:5173`): `explain_class` (a class from
|
|
25
|
+
the DOM → its declarations, conditions, layer, tokens and the call that made it), `module_styles` (a file's calls and
|
|
26
|
+
their CSS) and `dev_errors` (the build errors Vite has now). In a headless Chromium (playwright-core):
|
|
27
|
+
`inspect_element` (a CSS selector → the element's classes by call, the winner per property, what it overrides, ✓/✗
|
|
28
|
+
against the computed style) and `why_property` (why one property has its value), with `hover`, `focus`, `width` and
|
|
29
|
+
`height`. Without it, the build is the check: run the dev server or `vite build` and read the message.
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// vite.config.ts
|
|
35
|
+
import cssints, { cssTarget, targets } from "cssints/vite";
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
plugins: [cssints()], // any order beside React, Solid, ... plugins
|
|
39
|
+
css: { transformer: "lightningcss", lightningcss: { targets }, devSourcemap: true },
|
|
40
|
+
build: { cssMinify: "lightningcss", cssTarget },
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- The entry imports `"virtual:cssints.css"` once. `tsconfig.json` needs `"types": ["vite/client"]`.
|
|
45
|
+
- `targets` and `cssTarget` come from the browserslist (`BROWSERSLIST`, else `package.json` / `.browserslistrc`, else
|
|
46
|
+
`defaults`), read from `process.cwd()`: run Vite from the app's folder. Support warnings read the config of Vite's
|
|
47
|
+
`root`, and each is said at the `css.*` call that made it, once per file per build (in dev, again after an edit).
|
|
48
|
+
- `devSourcemap: true` maps each class rule in Chrome's Styles pane to the call that made it; in a build,
|
|
49
|
+
`build: { sourcemap: true }` (or `"hidden"`) maps the sheet asset too (bundle analysers split its bytes by module).
|
|
50
|
+
|
|
51
|
+
## The import attribute
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { cx } from "cssints"; // runtime: the only member without the attribute
|
|
55
|
+
import { cn, cv, hover, media, p } from "cssints" with { type: "cssints" }; // build time
|
|
56
|
+
import * as css from "cssints" with { type: "cssints" }; // the same, as a namespace
|
|
57
|
+
import { stack } from "./stack.ts" with { type: "cssints" }; // your own helper module, run at build time
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- A build-time member imported without the attribute is a build error:
|
|
61
|
+
`imports build-time p from "cssints" without the attribute`.
|
|
62
|
+
- Plugin packages are imported with the attribute too: `import { stack } from "@cssints/layout" with { type: "cssints" }`.
|
|
63
|
+
- `import type { Style } from "cssints"` needs no attribute.
|
|
64
|
+
|
|
65
|
+
## Arguments are build-time values
|
|
66
|
+
|
|
67
|
+
`p(n)` with `n` a prop, a parameter or state fails: `css arguments must be known at build time: n is not defined
|
|
68
|
+
where the call is evaluated`. The types do not catch it. Choose at runtime between styles made at build time:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<div className={cx(base, on && p(4))} /> // a condition: cx skips falsy values
|
|
72
|
+
<button className={button({ size })} /> // a cv() variant
|
|
73
|
+
<div style={style(token.set(accent, "#dc2626"))} /> // a token set inline (static values)
|
|
74
|
+
<div style={{ "--accent": value }} /> // a dynamic value: a plain style prop
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
A helper module imported with the attribute may take parameters (`stack(4)`): each call is a site of its own. A call
|
|
78
|
+
the run does not reach (`false && p(4)`, a branch) still gets its class string.
|
|
79
|
+
|
|
80
|
+
## Properties and values
|
|
81
|
+
|
|
82
|
+
- Every property is a camelCase member (`paddingLeft`, `alignItems`); short names `p pt pr pb pl`, `m mt mr mb ml`,
|
|
83
|
+
`bg`. There is no `items` or `justify`.
|
|
84
|
+
- A bare number is a spacing step of 0.25rem in `padding*`, `margin*`, `gap*`, `inset*`: `p(4)` is `1rem`. Elsewhere
|
|
85
|
+
it is a `<number>`: `opacity(0.5)`; `width(4)` is a type error, write `width("4rem")`.
|
|
86
|
+
- Parts are joined by a space: `border("1px solid", accent)`, `padding("1rem 2rem")`, `margin("0", "auto")`.
|
|
87
|
+
- Keywords are written in their canonical case: `color("RED")` is an error that says to write `"red"`.
|
|
88
|
+
- `flex()` and `grid()` with no argument set `display` and chain container properties without the prefix:
|
|
89
|
+
`flex().direction("column").alignItems("center").gap(2)`. With an argument they are the shorthand
|
|
90
|
+
(`flex("1")`, a plain style that does not chain). Item properties (`flexGrow`, `alignSelf`) are plain members.
|
|
91
|
+
- CSS functions are calls: `calc(t.space.md, "*", 2)`, `clamp("1rem", "2vw", "2rem")`, `colorMix("in oklab", a, "black 15%")`,
|
|
92
|
+
`linearGradient("to right", [a, "25%"], "blue")`. Clashing names end in `Fn`: `colorFn`, `translateFn`, `attrFn`.
|
|
93
|
+
- `firstThatWorks("100dvh", "100vh")` is a value with fallbacks in one class.
|
|
94
|
+
- `keyframes({ from: opacity(0), to: opacity(1) })` returns a name for `animation(fade, "200ms")`.
|
|
95
|
+
|
|
96
|
+
## Merging: cn, cx, cv
|
|
97
|
+
|
|
98
|
+
- `cn(...)` merges at build time into one string; the later of one property wins (`cn(p(4), p(2))` is `p(2)`). A
|
|
99
|
+
longhand beats its shorthand whatever the order. Two properties that overlap without one containing the other
|
|
100
|
+
(`borderTop` and `borderColor`, `marginLeft` and `marginInline`) are a build error: set one, or the longhands.
|
|
101
|
+
- `cx(...)` joins strings at runtime (skips falsy values, flattens arrays; no clsx objects). It sees no conflicts.
|
|
102
|
+
- `cv({ base, variants, defaultVariants, compoundVariants })` is the function you call; props are typed. A variant
|
|
103
|
+
wins over the base, a compound (`{ size: "lg", tone: "loud", class: p(6) }`) over both. Values are `css.*` styles,
|
|
104
|
+
never CSS strings.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const button = cv({
|
|
108
|
+
base: cn(p(2), fontSize("1rem")),
|
|
109
|
+
variants: { size: { sm: p(1), lg: p(4) }, disabled: { true: opacity(0.5) } },
|
|
110
|
+
defaultVariants: { size: "sm" },
|
|
111
|
+
});
|
|
112
|
+
button({ size: "lg", disabled: true });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Conditions and layers
|
|
116
|
+
|
|
117
|
+
Conditions are wrappers that nest: `hover(bg("#eee"))`, `media("(width >= 40rem)")(p(4))`, `md(hover(...))`.
|
|
118
|
+
|
|
119
|
+
- Pseudo-classes: `hover`, `focus`, `focusVisible`, `active`, `disabled` and the other standard ones without arguments.
|
|
120
|
+
- `media(q)`, `container("sidebar (width < 30rem)")`, `supports("(display: grid)")`; queries are checked, and a range
|
|
121
|
+
needs parentheses: `(width >= 40rem)`, not `min-width > 40rem`. Bind them once: `const md = media("(width >= 40rem)")`.
|
|
122
|
+
- `attr("aria-current", "page")(color("blue"))`; `attr(name)` is presence; `op` is `=`, `~=`, `|=`, `^=`, `$=`, `*=`.
|
|
123
|
+
- Parent state: `group()` (or `group("card")`) marks the ancestor, `within(hover, "card")(...)` reads it.
|
|
124
|
+
- Layer = number of conditions: none `_.a`, one `_.b`, up to four (`_.e`); a fifth is an error. More conditions win.
|
|
125
|
+
In one layer the sheet orders rules (pseudo-classes by their usual order, queries by min-width, `attr()` after
|
|
126
|
+
pseudo-classes), not your argument order.
|
|
127
|
+
- `global(selector, ...styles)` is a rule with no class (a reset): in `_.k`, below every class. It takes `media()`,
|
|
128
|
+
`container()` and `supports()`, not `hover` (put the state in the selector).
|
|
129
|
+
- `layers("app", "author", "viewer")` declares top-level layers after the engine's; `{ before: true }` before them.
|
|
130
|
+
- No pseudo-elements and no `:has()` in 0.1.
|
|
131
|
+
|
|
132
|
+
A condition wraps declarations. A plugin member that is only a kernel's marker class (`pretty()`, `balance()`,
|
|
133
|
+
`frame()` with no method, `group()`) cannot go under a condition: `:hover wraps _balance-prettyCore, the marker of
|
|
134
|
+
kernel balance.prettyCore, with no declaration of its own`. Put the member outside, and the condition around one of
|
|
135
|
+
its methods (`cn(skeleton(), hover(skeleton().highlight("white")))`) or a plain property.
|
|
136
|
+
|
|
137
|
+
## Tokens, themes, contrast
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const t = createTokens({
|
|
141
|
+
color: { $type: "color", bg: { $value: "#ffffff" }, fg: { $value: "#111827" } },
|
|
142
|
+
space: { $type: "dimension", md: { $value: "1rem" } },
|
|
143
|
+
});
|
|
144
|
+
createGlobalTheme(t, media("(prefers-color-scheme: dark)"), {
|
|
145
|
+
color: { bg: "#0b0b0f", fg: "#f3f4f6" },
|
|
146
|
+
});
|
|
147
|
+
createGlobalTheme(t, ".brand", { color: { fg: "#7c2d12" } });
|
|
148
|
+
check.contrast(t.color.fg, t.color.bg, { min: 4.5 }); // every theme too; below min is a build error
|
|
149
|
+
export const card = cn(bg(t.color.bg), color(t.color.fg), p(t.space.md));
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
A token is its `var(--name)` and fits where a value of its type fits. Pass the token, not `"var(--color-bg)"` (a
|
|
153
|
+
warning). Single tokens: `token.color("#2563eb", "accent")` (named, settable) or `token.color("#6b7280")` (private).
|
|
154
|
+
DTCG JSON, Figma exports, references `{color.accent}`, derived values and layers of themes:
|
|
155
|
+
[references/tokens.md](references/tokens.md).
|
|
156
|
+
|
|
157
|
+
## Plugin packages: which one
|
|
158
|
+
|
|
159
|
+
Import them with the attribute, except `@cssints/tokens` and `@cssints/ramp` (plain data for `createTokens`). A
|
|
160
|
+
member returns a class string (or a value) that goes into `cn()`/`cx()`. Details and gotchas:
|
|
161
|
+
[references/plugins.md](references/plugins.md).
|
|
162
|
+
|
|
163
|
+
| Need | Package |
|
|
164
|
+
| ------------------------------------------- | ------------------------------------------------------------ |
|
|
165
|
+
| Stacks, clusters, sidebars, auto grids | `@cssints/layout`: `stack()`, `cluster()`, `sidebar()`, ... |
|
|
166
|
+
| A length that grows with the viewport | `@cssints/fluid`: `fontSize(fluid("1rem", "1.5rem"))` |
|
|
167
|
+
| Web fonts with a metric-matched fallback | `@cssints/fonts`: `font(family, { provider })` |
|
|
168
|
+
| A CSS reset below every style | `@cssints/reset`: `reset({ media?, prose? })` |
|
|
169
|
+
| Ready-made tokens (Open Props, Radix, ...) | `@cssints/tokens/<set>`: data for `createTokens` |
|
|
170
|
+
| A palette with contrast steps | `@cssints/ramp`: `ramp(base, { mode, ... })` into tokens |
|
|
171
|
+
| Icons as a mask class | `@cssints/icons`: `icons({ provider, ... })` |
|
|
172
|
+
| Looks per `data-mode` (app, author, viewer) | `@cssints/mise-en-mode`: `look(t, name, source, overrides)` |
|
|
173
|
+
| Eased gradients | `@cssints/gradient`: `gradient(kind, dir, from, to, ...)` |
|
|
174
|
+
| Enter and exit animations | `@cssints/motion`: `motion().preset("fade-in")` |
|
|
175
|
+
| Animations on scroll into view | `@cssints/reveal`: `reveal().preset("slide-in-up")` |
|
|
176
|
+
| Styles while a sticky header is stuck | `@cssints/stuck`: `sticky().top("0")`, `stuck(styles)` |
|
|
177
|
+
| Balanced headings, no orphans | `@cssints/balance`: `balance()`, `pretty()` |
|
|
178
|
+
| Squircle corners | `@cssints/squircle`: `squircle().radius("24px")` |
|
|
179
|
+
| Masonry | `@cssints/masonry`: `masonry().columns("12rem")` |
|
|
180
|
+
| Image placeholders from the image's colours | `@cssints/placeholder`: `backgroundImage(placeholder(path))` |
|
|
181
|
+
| Loading skeletons in one phase | `@cssints/skeleton`: `skeleton().radius("50%")` |
|
|
182
|
+
| Concentric radius, parent-relative colour | `cssints/frame`: `frame()`; `cssints/surface`: `surface()` |
|
|
183
|
+
|
|
184
|
+
A member conflicts with the properties its kernel sets: `cn(stack(), display("grid"))` is a build error. Use the
|
|
185
|
+
member's methods instead.
|
|
186
|
+
|
|
187
|
+
## Build errors
|
|
188
|
+
|
|
189
|
+
Read the message: it names the property or the member, the bad value and what the grammar expected, and it points at
|
|
190
|
+
the call. The common ones, with fixes: [references/errors.md](references/errors.md).
|
|
191
|
+
|
|
192
|
+
## Rules of thumb
|
|
193
|
+
|
|
194
|
+
- Make every style at module level or in a helper module; choose between them at runtime with `cx`, `cv` or tokens.
|
|
195
|
+
- Prefer `cn()` to `cx()` when the parts are fixed: it is checked for conflicts and leaves nothing at runtime.
|
|
196
|
+
- Prefer a token and a theme to a second style for each colour mode.
|
|
197
|
+
- Write `check.contrast` for each text/background pair you use; it checks every theme.
|
|
198
|
+
- A support warning is not an error: the value works where the targets have it. LightningCSS lowers what it can
|
|
199
|
+
(`oklch()` gets a fallback) and those values do not warn.
|
|
200
|
+
- Styles of a library on cssints are evaluated by the app's plugin; such a package must be a direct dependency.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# cssints build errors and warnings
|
|
2
|
+
|
|
3
|
+
A value error is given twice: as a type error on the bad part in the editor, and as a build error with the file and
|
|
4
|
+
the place of the call (the Vite overlay points at it). Some errors only the build sees: runtime arguments, conflicts,
|
|
5
|
+
contrast, ranges in some positions, `supports()` property names. The messages below are the real text, shortened with
|
|
6
|
+
`…`.
|
|
7
|
+
|
|
8
|
+
## Imports and build-time values
|
|
9
|
+
|
|
10
|
+
| Message | Meaning and fix |
|
|
11
|
+
| ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
12
|
+
| `<file> imports build-time p from "cssints" without the attribute. Import it as: … with { type: "cssints" }` | Every member but `cx` runs at build time. Add `with { type: "cssints" }` to that import (keep `cx` in a plain import, or in the same one). |
|
|
13
|
+
| `css arguments must be known at build time: n is not defined where the call is evaluated (a parameter, a prop or state)` | The call reads a runtime value. Make the styles at build time and choose at runtime: `cx(on && p(4))`, a `cv()` variant, or a token set in `style={...}`. Also when a local shadows a global (`open`, `name`). |
|
|
14
|
+
| `imports _cv from "cssints", which is gone` | Old code: `cv({ ... })` is the function now. Drop `_cv(` and its closing bracket. |
|
|
15
|
+
|
|
16
|
+
## Grammar errors
|
|
17
|
+
|
|
18
|
+
The shape is `<property>: unexpected "<token>" in "<value>", expected <grammar>`.
|
|
19
|
+
|
|
20
|
+
| Example | Fix |
|
|
21
|
+
| ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `padding: unexpected "-0.25rem" in "-0.25rem", expected <length-percentage [0,∞]>` (`p(-1)`) | Out of the range. A negative spacing is not a padding; use `margin` for negative values. |
|
|
23
|
+
| `color: unexpected "RED" in "RED" ("RED" is red in CSS; write "red"), expected <color>` | Keywords and function names have one spelling, the canonical lowercase one. |
|
|
24
|
+
| `color: unexpected "1px" … ("var(--gap)" is checked as "1px": a token stands for a value of its type)` | A token of the wrong type: a length token in a colour property. Use a token of the property's type. |
|
|
25
|
+
| `width: unexpected "calc(1px" … (cannot add length and time)` | Math on mixed kinds. Check the units and the tokens in the `calc()`. |
|
|
26
|
+
| `@media: unexpected "min-width" in "screen and min-width > 40rem", expected <media-query>` | A range needs parentheses: `media("(width >= 40rem)")`. |
|
|
27
|
+
| `@supports: unexpected "grdi" in "(display: grdi)", expected <supports-in-parens>` | A typo in the declaration of `supports()`. |
|
|
28
|
+
| `selector: unexpected end in "a >", expected <complex-selector-list>` | A selector of `global()` or of a theme is incomplete. |
|
|
29
|
+
| `cv() base must be a css.* value (cn(...), a flex() chain or one property), got "padding: 1rem"` | CSS strings are gone everywhere: write `cn(p(4), fontSize("1rem"))`. |
|
|
30
|
+
|
|
31
|
+
A type error with no build error usually means the value is valid CSS that the types read too strictly (or the
|
|
32
|
+
reverse); trust the build's message and, with the MCP server, `check_declaration`.
|
|
33
|
+
|
|
34
|
+
## Chains
|
|
35
|
+
|
|
36
|
+
| Message | Fix |
|
|
37
|
+
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
38
|
+
| `flex() has no .padding(); it chains direction, wrap, flow, alignItems, …` | A chain has only its container properties. Merge the rest: `cn(flex().gap(2), p(4))`. |
|
|
39
|
+
| `plain styles do not chain (.gap); merge them with cn(...) or cx(...)` | `flex("1")` (with an argument) is the shorthand property, a plain style. |
|
|
40
|
+
| `sticky().top() is used twice` (and other members: a method is used once) | Each method of a chain once. Under a condition, a second chain is fine: `hover(x().y(...))`. |
|
|
41
|
+
|
|
42
|
+
## Conflicts
|
|
43
|
+
|
|
44
|
+
| Message | Fix |
|
|
45
|
+
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| `border-top and border-color overlap without one containing the other, so the sheet cannot order them …` | In one `cn()`. Set one of them, or the longhands (`borderTopColor`). Same for a physical and a logical side (`marginLeft`, `marginInline`). |
|
|
47
|
+
| `cv() base and variant active.yes: border-bottom and border-color overlap …` | The same rule between base, variants and compounds that can be active together. |
|
|
48
|
+
| `cv() variants a.x and b.x both set color and can be active together, so nothing says which wins` | Set the property in one variant, or in the base. |
|
|
49
|
+
| `layout: kernel stack-core sets display, flex-direction, gap (this style also sets display)` | A plugin member and a property its kernel sets, in one `cn()`. Use the member's method (`stack().gap(4)`), or drop the property. |
|
|
50
|
+
| `frame: frame() sets padding, margin, border-radius and the border: use its methods (this style also sets padding)` | The same, with the plugin's own message. |
|
|
51
|
+
|
|
52
|
+
`cx()` joins strings at runtime and checks nothing: a conflict across `cx()` arguments is not reported.
|
|
53
|
+
|
|
54
|
+
## Conditions
|
|
55
|
+
|
|
56
|
+
| Message | Fix |
|
|
57
|
+
| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| `:hover wraps _balance-prettyCore, the marker of kernel balance.prettyCore, with no declaration of its own: …` | A kernel is global CSS keyed to a class: it cannot be conditional. Put the member outside the condition; wrap one of its methods or a plain property (`hover(css.textWrap("pretty"))`). |
|
|
59
|
+
| `a style takes at most 4 conditions (layers _.a to _.e); this one has 5: …` | Fewer nested wrappers. Bind a combined query (`media("(width >= 40rem) and (hover: hover)")`) instead of two. |
|
|
60
|
+
| `global() takes media(), container() and supports() conditions, not :hover: write the state in the selector` | `global("a:hover", color("red"))`. |
|
|
61
|
+
| `within(media(...))` is an error | `within()` takes a pseudo-class wrapper or `attr(...)`, not a query. |
|
|
62
|
+
|
|
63
|
+
## Tokens, themes, layers
|
|
64
|
+
|
|
65
|
+
| Message | Fix |
|
|
66
|
+
| ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
67
|
+
| `contrast 2.84 is below 4.5: c.fg #999 on c.bg #fff, in the default values` | `check.contrast` failed (it names the theme when a theme fails). Change the colour, or the theme that names it. |
|
|
68
|
+
| warning: `padding: "var(--space-sm)" is the token space.sm: pass the token itself, …` | Pass `t.space.sm`; for a fallback, `css.var(t.space.sm, "4px")`. |
|
|
69
|
+
| warning: `… stays at its static default in chrome 109 …` | A derived colour the old-browser runtime cannot compute; it keeps its default there. Usually acceptable. |
|
|
70
|
+
| two tokens with one name, `2xs` and `_2xs` in one group, a cycle of references | Rename; a digit key `2xs` is `t.space._2xs` in code. |
|
|
71
|
+
| `$type "fontFamily"` (or `string`, a composite type) has no token type | Leave it out of the token file; set fonts with `fontFamily(...)` or `@cssints/fonts`. |
|
|
72
|
+
| an undeclared layer in `{ layer: "app" }`, the same layers in another order | Call `layers("app", ...)` first, in this module or one it imports; keep one order everywhere. |
|
|
73
|
+
|
|
74
|
+
## Support warnings
|
|
75
|
+
|
|
76
|
+
`cursor: "pointer" … is not supported by ios_saf … (css.properties.cursor)`: the value is valid, a browserslist target
|
|
77
|
+
lacks it. It is a warning, not an error. Options: accept it, add a fallback (`firstThatWorks(wanted, fallback)`), or
|
|
78
|
+
narrow the browserslist. The BCD key is in parentheses; `check_support` of the MCP server answers the same question.
|
|
79
|
+
Vite shows the warning at the `css.*` call that made it (file, line, column, a code frame). A build says it once per
|
|
80
|
+
file; a dev server again when that file is edited, not on every update. The targets are the browserslist of Vite's
|
|
81
|
+
`root`.
|
|
82
|
+
|
|
83
|
+
## When an error has `(trace <id>)`
|
|
84
|
+
|
|
85
|
+
The build ran with `CSSINTS_TRACE=otlp`; search for the id in the trace backend (Jaeger) to see the hook and the file.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# The plugin packages
|
|
2
|
+
|
|
3
|
+
Each package is written on `scope()` of `cssints/plugin` and has a README with its rules and limits
|
|
4
|
+
(`node_modules/@cssints/<name>/README.md`). Import them with `with { type: "cssints" }`, except `@cssints/tokens`
|
|
5
|
+
and `@cssints/ramp`, which are plain data. What the others share:
|
|
6
|
+
|
|
7
|
+
- A chain member (`stack()`, `motion()`, `skeleton()`) is a kernel class plus one class per method; each method sets a
|
|
8
|
+
custom property, so a method works under a condition: `media("(width >= 40rem)")(stack().gap(8))`.
|
|
9
|
+
- A member conflicts with what its kernel sets, in one `cn()`: use its methods, not the plain property.
|
|
10
|
+
- A member that is only a kernel class (`balance()`, `pretty()`, `skeleton()` with no method) cannot be inside a
|
|
11
|
+
condition. Put it outside; put a method inside.
|
|
12
|
+
- Fallbacks for older browsers (CSS under `@supports not`, or a small script) are in the build only when a browserslist
|
|
13
|
+
target needs them, and are delivered by the engine: nothing to import by hand.
|
|
14
|
+
- Files read at build time (icons, fonts, placeholder images) are not watched in dev: restart, or touch the module.
|
|
15
|
+
|
|
16
|
+
## Layout and type
|
|
17
|
+
|
|
18
|
+
**`@cssints/layout`**: `stack().gap(6)`, `cluster().gap(2).justify("space-between").align("center")`,
|
|
19
|
+
`sidebar().side("right").width("20rem").content("50%").gap(4)`, `switcher().threshold("30rem").gap(2)`,
|
|
20
|
+
`cover().minHeight("100vh")`, `autoGrid().min("16rem").gap(4)`. No media queries: they respond to their space.
|
|
21
|
+
`cn(stack(), display("grid"))` is a conflict; padding, sizes and colours are fine.
|
|
22
|
+
|
|
23
|
+
**`@cssints/masonry`**: `masonry().columns("12rem").gap(4)`. `display: grid-lanes` where the browser has it (Safari
|
|
24
|
+
26.4), CSS columns elsewhere (the order then runs down each column).
|
|
25
|
+
|
|
26
|
+
**`@cssints/fluid`**: `fontSize(fluid("1rem", "1.5rem", { from: "20rem", to: "80rem" }))`, a `clamp()` computed at
|
|
27
|
+
build time; `fluid.container(...)` against the container's width. px or rem only. A range that fails WCAG 1.4.4
|
|
28
|
+
(text resize) is a build error; `{ zoom: false }` for lengths that are not text.
|
|
29
|
+
|
|
30
|
+
**`@cssints/fonts`**: in a module of its own, `export const body = font("Inter", { provider: "fontsource", weights:
|
|
31
|
+
[400, 700] })` (or `provider: "fs", files: [...]`); then `body.family()` (the `@font-face` rules, a metric-matched
|
|
32
|
+
fallback face, `font-family`) and `body.trim({ lineHeight: 1.2 })` (cap-height trim: `text-box`, else capsize margins).
|
|
33
|
+
|
|
34
|
+
**`@cssints/balance`**: `balance()` (`text-wrap: balance`, with a 0.55 kB script for targets without it) and
|
|
35
|
+
`pretty()` (native only). Under a condition write `textWrap("balance")` instead.
|
|
36
|
+
|
|
37
|
+
## Base and tokens
|
|
38
|
+
|
|
39
|
+
**`@cssints/reset`**: `reset({ media: true, prose: true })` once, at the top level of a module the entry loads. In
|
|
40
|
+
`@layer reset` before the engine's layers: any style beats it. A layer of yours between:
|
|
41
|
+
`layers("reset", "base", { before: true })`.
|
|
42
|
+
|
|
43
|
+
**`@cssints/tokens`**: data, not a call. `createTokens({ radix: light })`, themes from `dark`;
|
|
44
|
+
`createTokens({ ...colors, size: sizes })` from `/open-props`. Pass only the scales you use (type cost).
|
|
45
|
+
|
|
46
|
+
**`@cssints/ramp`**: `ramp("#2563eb", { mode: "contrast", ratios: [1.2, 1.5, 3, 4.5, 7], background: "#fff" })` or
|
|
47
|
+
`{ mode: "lightness", steps: 9 }`, as a group of `createTokens`; `overrides(ramp(...))` for a theme. A contrast step
|
|
48
|
+
meets its ratio on that background only: make a second ramp for a dark background. Imported without the attribute
|
|
49
|
+
(plain data).
|
|
50
|
+
|
|
51
|
+
**`@cssints/mise-en-mode`**: `look(t, "dark", "app" | "author" | "viewer", overrides, { scope? })`: a theme on
|
|
52
|
+
`[data-mode="dark"]` (and `data-mode="dark/color"` for one aspect, a top-level token group) in the layers
|
|
53
|
+
`app < author < viewer`. Checked like `createGlobalTheme`, read by `check.contrast`.
|
|
54
|
+
|
|
55
|
+
**`@cssints/icons`**: in a module of its own, `export const mdi = icons<typeof import("@iconify-json/mdi/icons.json")>({
|
|
56
|
+
provider: "iconify-json", collection: "mdi" })` or `icons({ provider: "fs", dir: "src/icons" })`; then
|
|
57
|
+
`cn(mdi("close"), color("crimson"), width("1.5em"))`. One-colour icons take `color`; a wrong name is a build error with
|
|
58
|
+
a suggestion.
|
|
59
|
+
|
|
60
|
+
## Paint
|
|
61
|
+
|
|
62
|
+
**`@cssints/gradient`**: `backgroundImage(gradient("linear", "to bottom", "#000", "transparent", "ease-in-out", 5))`:
|
|
63
|
+
eased stops mixed in oklab. Each argument its own string; the direction is one string (`"to bottom"`).
|
|
64
|
+
|
|
65
|
+
**`@cssints/placeholder`**: `backgroundImage(placeholder("src/hero.jpg"))` (a gradient of the image's colours) or
|
|
66
|
+
`{ mode: "thumbhash" }` (a tiny PNG; pair it with `backgroundSize("cover")`). JPEG and PNG only; the path is relative
|
|
67
|
+
to the working directory.
|
|
68
|
+
|
|
69
|
+
**`@cssints/squircle`**: `squircle().radius("24px")`: `corner-shape: squircle`, else a mask of the same shape (which
|
|
70
|
+
clips shadows and outlines: put those on a wrapper). Set the radius with `radius()`, not `borderRadius`.
|
|
71
|
+
|
|
72
|
+
**`@cssints/skeleton`**: `cn(skeleton().radius("50%"), width("3rem"), height("3rem"))`: one clock on `:root`, so every
|
|
73
|
+
skeleton shimmers in phase; still under reduced motion. Do not set `background`, `background-image`, `-size`,
|
|
74
|
+
`-position`, `-attachment` or `border-radius` beside it (`backgroundColor` is fine and shows through).
|
|
75
|
+
|
|
76
|
+
**`cssints/frame`** (`frame().radius("1.5rem").padding(4).border("1px")`: a nested frame's radius is concentric) and
|
|
77
|
+
**`cssints/surface`** (`surface().color(canvas)`, `.lighten(0.06)`, `.darken()`, `.contrast()`, `.ink()`: a colour
|
|
78
|
+
relative to the parent surface). Both ship in `cssints`; a script fallback for browsers without style queries.
|
|
79
|
+
|
|
80
|
+
## Motion and scroll
|
|
81
|
+
|
|
82
|
+
**`@cssints/motion`**: `motion().preset("slide-in-up").duration("500ms").delay("100ms").easing("linear")`. Presets
|
|
83
|
+
`fade-*`, `slide-*-up|down|left|right`, `scale-*`, `blur-*` (in and out). Reduced motion turns movement into a fade.
|
|
84
|
+
No `animation*` property beside it.
|
|
85
|
+
|
|
86
|
+
**`@cssints/reveal`**: `reveal().preset("blur-in").range("entry 0% entry 100%")`: motion's entrance presets on
|
|
87
|
+
`animation-timeline: view()`; where a target lacks it, a 0.3 kB script plays the animation once on entry.
|
|
88
|
+
|
|
89
|
+
**`@cssints/stuck`**: `sticky().top("0")` on the sticky element, `stuck(color("red"))` or `stuck("bottom")(...)` on an
|
|
90
|
+
element inside it (a scroll-state query matches descendants only). An IntersectionObserver fallback where the targets
|
|
91
|
+
lack scroll-state queries.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Tokens, themes and contrast
|
|
2
|
+
|
|
3
|
+
## Single tokens
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import * as css from "cssints" with { type: "cssints" };
|
|
7
|
+
|
|
8
|
+
const accent = css.token.color("#2563eb", "accent"); // --accent, can be set
|
|
9
|
+
const gap = css.token.length("16px", { var: "gap", inherit: false });
|
|
10
|
+
const muted = css.token.color("#6b7280"); // --_<hash>, private: token.set() rejects it
|
|
11
|
+
|
|
12
|
+
export const a = css.cn(css.color(accent), css.gap(gap));
|
|
13
|
+
export const b = css.style(css.token.set(accent, "red")); // { "--accent": "red" } for a style prop
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Types: `color`, `length`, `percentage`, `lengthPercentage`, `number`, `integer`, `angle`, `time`, `customIdent`,
|
|
17
|
+
`resolution`, `url`, `image`, `transformList`, `easingFunction`, `shadow`. Each gives one `@property` rule. A value in
|
|
18
|
+
`rem`/`em`/`ch` gets a px initial value and the real value on `:root`, so it follows the user's font size.
|
|
19
|
+
|
|
20
|
+
## Token sets (DTCG)
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
export const t = css.createTokens({
|
|
24
|
+
color: {
|
|
25
|
+
$type: "color",
|
|
26
|
+
accent: { $value: "#2563eb" },
|
|
27
|
+
hover: { $value: "color-mix(in oklab, {color.accent}, black 15%)" }, // derived: follows accent in themes
|
|
28
|
+
link: { $value: "{color.accent}" }, // alias
|
|
29
|
+
},
|
|
30
|
+
space: {
|
|
31
|
+
$type: "dimension",
|
|
32
|
+
"2xs": { $value: "2px" },
|
|
33
|
+
md: { $value: { value: 1, unit: "rem" } },
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
// t.color.accent, t.space._2xs (a key that starts with a digit gets "_"), variable --space-2xs
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `$type` is inherited through groups and required: `color`, `dimension`, `number`, `duration`, `fontWeight`,
|
|
40
|
+
`cubicBezier`, `shadow`. `fontFamily`, `string`, `boolean` and the other composite types are errors.
|
|
41
|
+
- Names are the kebab-case path (`--color-accent`). The second argument maps a path to a name or `{ var, inherit }`.
|
|
42
|
+
- JSON works: `import tokens from "./tokens.json" with { type: "json" }; createTokens(tokens)`. The editor then knows
|
|
43
|
+
the paths but not the types; the build checks the values.
|
|
44
|
+
- Figma's own export (one DTCG file per mode) and TokensBrücke ("Split modes into separate files"): the default mode
|
|
45
|
+
is `createTokens(json)`, another mode is `createGlobalTheme(t, dark, darkJson)` as it is. Tokens Studio's format goes
|
|
46
|
+
through Style Dictionary with `@tokens-studio/sd-transforms` first (recipe in docs/API.md, "JSON from Figma").
|
|
47
|
+
- Ready-made sets: `@cssints/tokens/open-props`, `/radix`, `/flexoki`, `/reasonable-colors` (data: put a set under a
|
|
48
|
+
group, `createTokens({ radix: light })` gives `--radix-blue-11`). Palettes: `@cssints/ramp`.
|
|
49
|
+
|
|
50
|
+
## Themes
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const dark = css.media("(prefers-color-scheme: dark)");
|
|
54
|
+
css.createGlobalTheme(t, dark, { color: { accent: "#60a5fa" } }); // :root under the media query
|
|
55
|
+
css.createGlobalTheme(t, ".brand", { color: { accent: "#db2777" } }); // a selector; hover and link follow
|
|
56
|
+
css.createGlobalTheme(t.color, css.container("(width < 30rem)"), { accent: "#000" }); // a group, a container query
|
|
57
|
+
css.createGlobalTheme(t, ".soft", {
|
|
58
|
+
color: { link: "color-mix(in oklab, {color.accent}, white 20%)" },
|
|
59
|
+
}); // live
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- The condition is a `media()`, a `container()` or a selector. Not `hover` or `supports()`.
|
|
63
|
+
- Options: `{ layer: "app" }` (a layer declared with `layers()`), `{ scope: "article.post" }` (an `@scope` around a
|
|
64
|
+
selector theme).
|
|
65
|
+
- A theme re-declares the aliases and derived tokens that depend on what it overrides.
|
|
66
|
+
- Put themes in a module of their own and import it once from the entry (`import "./theme.ts"`); a module that uses a
|
|
67
|
+
token gets the theme rules for that token.
|
|
68
|
+
- Sheet order: tokens and themes are in `_.k`, below every class; a selector theme beats a condition theme.
|
|
69
|
+
|
|
70
|
+
## Contrast
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
css.check.contrast(t.color.fg, t.color.bg, { min: 4.5 }); // WCAG 2.x, 1 to 21
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- Checks the defaults and every theme that sets either token (themes of one selector are read together in cascade
|
|
77
|
+
order). A ratio below `min` is a build error with both colours and the theme.
|
|
78
|
+
- A value it cannot evaluate (`color-mix()` over another token, `currentColor`, `light-dark()`, a translucent colour)
|
|
79
|
+
is a warning, not a pass.
|
|
80
|
+
- A value set at runtime (a slider on `<html>`) is checked at its default only.
|
|
81
|
+
- `surface().ink()` picks black or white by lightness and is not a contrast guarantee: check the pair with tokens.
|
|
82
|
+
|
|
83
|
+
## Top-level layers
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
css.layers("app", "author", "viewer"); // after the engine's layers: they beat the engine's :root fallbacks
|
|
87
|
+
css.layers("reset", "base", { before: true }); // before the engine's: every cssints style beats them
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The same names in another order, or a layer on both sides, is a build error. `@cssints/reset` uses `reset` before the
|
|
91
|
+
engine's layers; `@cssints/mise-en-mode` uses `app < author < viewer`.
|