cssints 0.0.0-stage → 0.2.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 +188 -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-DhK69Xak.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/group-common-9zpBxfUF.d.mts +1 -0
- package/dist/group-types-9ttphdku.d.mts +55 -0
- package/dist/groups/compositing-2.d.mts +20 -0
- package/dist/groups/css-align-3.d.mts +29 -0
- package/dist/groups/css-anchor-position-1.d.mts +49 -0
- package/dist/groups/css-anchor-position-2.d.mts +50 -0
- package/dist/groups/css-animations-1.d.mts +28 -0
- package/dist/groups/css-animations-2.d.mts +29 -0
- package/dist/groups/css-backgrounds-3.d.mts +27 -0
- package/dist/groups/css-backgrounds-4.d.mts +29 -0
- package/dist/groups/css-borders-4.d.mts +95 -0
- package/dist/groups/css-box-4.d.mts +38 -0
- package/dist/groups/css-break-4.d.mts +23 -0
- package/dist/groups/css-cascade-5.d.mts +18 -0
- package/dist/groups/css-cascade-6.d.mts +18 -0
- package/dist/groups/css-color-4.d.mts +29 -0
- package/dist/groups/css-color-5.d.mts +32 -0
- package/dist/groups/css-color-6.d.mts +32 -0
- package/dist/groups/css-color-adjust-1.d.mts +20 -0
- package/dist/groups/css-color-hdr-1.d.mts +19 -0
- package/dist/groups/css-conditional-3.d.mts +17 -0
- package/dist/groups/css-conditional-4.d.mts +17 -0
- package/dist/groups/css-conditional-5.d.mts +22 -0
- package/dist/groups/css-contain-2.d.mts +19 -0
- package/dist/groups/css-content-3.d.mts +23 -0
- package/dist/groups/css-counter-styles-3.d.mts +16 -0
- package/dist/groups/css-display-4.d.mts +22 -0
- package/dist/groups/css-easing-2.d.mts +18 -0
- package/dist/groups/css-env-1.d.mts +16 -0
- package/dist/groups/css-flexbox-1.d.mts +24 -0
- package/dist/groups/css-fonts-4.d.mts +46 -0
- package/dist/groups/css-fonts-5.d.mts +44 -0
- package/dist/groups/css-forms-1.d.mts +19 -0
- package/dist/groups/css-gaps-1.d.mts +21 -0
- package/dist/groups/css-gcpm-3.d.mts +20 -0
- package/dist/groups/css-gcpm-4.d.mts +20 -0
- package/dist/groups/css-grid-2.d.mts +34 -0
- package/dist/groups/css-grid-3.d.mts +34 -0
- package/dist/groups/css-images-3.d.mts +25 -0
- package/dist/groups/css-images-4.d.mts +32 -0
- package/dist/groups/css-images-5.d.mts +33 -0
- package/dist/groups/css-inline-3.d.mts +28 -0
- package/dist/groups/css-lists-3.d.mts +26 -0
- package/dist/groups/css-logical-1.d.mts +35 -0
- package/dist/groups/css-masking-1.d.mts +37 -0
- package/dist/groups/css-multicol-2.d.mts +24 -0
- package/dist/groups/css-overflow-3.d.mts +28 -0
- package/dist/groups/css-overflow-4.d.mts +29 -0
- package/dist/groups/css-overflow-5.d.mts +31 -0
- package/dist/groups/css-overscroll-1.d.mts +22 -0
- package/dist/groups/css-page-3.d.mts +18 -0
- package/dist/groups/css-page-floats-3.d.mts +19 -0
- package/dist/groups/css-position-3.d.mts +29 -0
- package/dist/groups/css-position-4.d.mts +30 -0
- package/dist/groups/css-rhythm-1.d.mts +18 -0
- package/dist/groups/css-round-display-1.d.mts +16 -0
- package/dist/groups/css-ruby-1.d.mts +21 -0
- package/dist/groups/css-scroll-anchoring-1.d.mts +18 -0
- package/dist/groups/css-scroll-snap-1.d.mts +42 -0
- package/dist/groups/css-scroll-snap-2.d.mts +43 -0
- package/dist/groups/css-scrollbars-1.d.mts +19 -0
- package/dist/groups/css-shapes-1.d.mts +27 -0
- package/dist/groups/css-shapes-2.d.mts +27 -0
- package/dist/groups/css-size-adjust-1.d.mts +18 -0
- package/dist/groups/css-sizing-3.d.mts +34 -0
- package/dist/groups/css-sizing-4.d.mts +33 -0
- package/dist/groups/css-speech-1.d.mts +18 -0
- package/dist/groups/css-tables-3.d.mts +22 -0
- package/dist/groups/css-text-3.d.mts +36 -0
- package/dist/groups/css-text-4.d.mts +40 -0
- package/dist/groups/css-text-decor-3.d.mts +30 -0
- package/dist/groups/css-text-decor-4.d.mts +31 -0
- package/dist/groups/css-transforms-1.d.mts +31 -0
- package/dist/groups/css-transforms-2.d.mts +48 -0
- package/dist/groups/css-transitions-1.d.mts +22 -0
- package/dist/groups/css-transitions-2.d.mts +23 -0
- package/dist/groups/css-ui-4.d.mts +35 -0
- package/dist/groups/css-values-4.d.mts +38 -0
- package/dist/groups/css-values-5.d.mts +43 -0
- package/dist/groups/css-variables-2.d.mts +6 -0
- package/dist/groups/css-view-transitions-2.d.mts +20 -0
- package/dist/groups/css-viewport.d.mts +18 -0
- package/dist/groups/css-will-change-1.d.mts +18 -0
- package/dist/groups/css-writing-modes-4.d.mts +22 -0
- package/dist/groups/css2.d.mts +19 -0
- package/dist/groups/fill-stroke-3.d.mts +27 -0
- package/dist/groups/filter-effects-1.d.mts +32 -0
- package/dist/groups/filter-effects-2.d.mts +33 -0
- package/dist/groups/limited-availability.d.mts +2 -0
- package/dist/groups/mediaqueries-4.d.mts +16 -0
- package/dist/groups/mediaqueries-5.d.mts +16 -0
- package/dist/groups/motion-1.d.mts +24 -0
- package/dist/groups/newly-available.d.mts +631 -0
- package/dist/groups/not-discouraged.d.mts +727 -0
- package/dist/groups/scroll-animations-1.d.mts +30 -0
- package/dist/groups/selectors-4.d.mts +54 -0
- package/dist/groups/selectors-5.d.mts +54 -0
- package/dist/groups/widely-available.d.mts +616 -0
- package/dist/index.d.mts +48 -0
- package/dist/index.mjs +1269 -0
- package/dist/instrument-gCazujmb.mjs +1624 -0
- package/dist/lowered-D2KusPLp.mjs +28 -0
- package/dist/node-OJ_w_Usj.d.mts +233 -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-CTumHB8p.mjs +73 -0
- package/dist/runtime-Dt4Tm0Q7.d.mts +19 -0
- package/dist/runtime.d.mts +18 -0
- package/dist/runtime.mjs +25 -0
- package/dist/scope-ZVhUxQH1.d.mts +2101 -0
- package/dist/sheet-BKsMTa0c.mjs +757 -0
- package/dist/surface.d.mts +228 -0
- package/dist/surface.mjs +101 -0
- package/dist/theme-BsP2E4LB.mjs +929 -0
- package/dist/theme-types-Bqv0Ggz_.d.mts +1808 -0
- package/dist/trace-otlp-CbKFDsn9.mjs +14 -0
- package/dist/tui-types.d.mts +181 -0
- package/dist/tui.d.mts +23 -0
- package/dist/tui.mjs +427 -0
- package/dist/types.d.mts +2 -0
- package/dist/vite.d.mts +134 -0
- package/dist/vite.mjs +977 -0
- package/docs/API.md +1193 -0
- package/package.json +85 -4
- package/skills/cssints/SKILL.md +203 -0
- package/skills/cssints/references/errors.md +86 -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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexei Accio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,189 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CSS-in-TS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="https://cssints.dev/logo.svg" alt="" width="96" height="96">
|
|
4
|
+
|
|
5
|
+
cssints is compile-time CSS-in-TypeScript. You write calls such as `css.flex().alignItems("center")`. A Vite plugin runs them at build time and replaces each one with atomic, content-hashed class names in `@layer`s. Nothing of the styling code ships to the browser except a few lines that join class names: for a `cn()` with runtime arguments and for `cv`.
|
|
6
|
+
|
|
7
|
+
- Values are checked against the spec grammars (mdn-data) in the types and again at build time. A typo is an error in the editor and in the build.
|
|
8
|
+
- Conditions (`media`, `container`, `supports`, `hover`), variants (`cv`), design tokens and themes (`createTokens`, `createGlobalTheme`, `check.contrast`) are all build-time, and all typed.
|
|
9
|
+
- LightningCSS optimizes the output. Browser support (`@mdn/browser-compat-data` against your browserslist) is reported as warnings, except for what LightningCSS lowers for those targets.
|
|
10
|
+
|
|
11
|
+
> **Alpha.** cssints is on npm at 0.x. The API can still change between minor versions, the plugin packages
|
|
12
|
+
> may move, and some browsers and frameworks are checked less than others (docs/API.md, "Browser support and what 0.1
|
|
13
|
+
> does not have"). Pin the version, and report what breaks.
|
|
14
|
+
|
|
15
|
+
Decisions and findings are in [docs/wayfinder.md](docs/wayfinder.md); the API reference is [docs/API.md](docs/API.md). Both are also a site with search and type hovers: [apps/docs](apps/docs) (`cd apps/docs && bun run dev`); deployed to Cloudflare with Alchemy: <https://cssints.dev>.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install --save-dev cssints vite@8 # or: bun add -d cssints vite@8, pnpm add -D cssints vite@8
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
cssints needs Vite 8 (Vite+ works too) and Node 22.18 or later (the plugin runs your modules with type stripping). It is a
|
|
24
|
+
dev dependency: nothing of it ships except the few lines behind `cn` with runtime arguments and `cv`. The plugin packages are installed the same
|
|
25
|
+
way, each beside `cssints` (`npm install --save-dev @cssints/fluid`); the list is on the
|
|
26
|
+
[plugin packages](docs/API.md#plugin-packages-cssints-dk1q) page. In this repository `cssints` is a workspace package
|
|
27
|
+
(`"cssints": "workspace:*"`).
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// vite.config.ts
|
|
31
|
+
import cssints, { cssTarget, targets } from "cssints/vite";
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
plugins: [cssints()],
|
|
35
|
+
css: { transformer: "lightningcss", lightningcss: { targets } },
|
|
36
|
+
build: { cssMinify: "lightningcss", cssTarget },
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// the entry, once
|
|
42
|
+
import "virtual:cssints.css";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`targets` and `cssTarget` come from the browserslist query of the support warnings: the `BROWSERSLIST` variable, else the standard config (`"browserslist"` in `package.json` or `.browserslistrc`, with the `BROWSERSLIST_ENV`, `NODE_ENV` or `production` section), else `defaults`. The exports are computed when `vite.config.ts` is imported, before Vite's root is known, so they look the config up from `process.cwd()` and upward; the support warnings look it up from Vite's `root` (2026-10-09, cssints-p3d2). They are the same when Vite runs from the app's directory; with `vite <dir>` from elsewhere, give `targets` and `cssTarget` the app's config yourself. Vite's CSS minify reads `cssTarget`, not `targets`: without it the minifier merges the declarations of a `firstThatWorks` into the last one. Add `"types": ["vite/client"]` to `tsconfig.json` for the `virtual:` import.
|
|
46
|
+
|
|
47
|
+
### Frameworks
|
|
48
|
+
|
|
49
|
+
Any framework that builds JSX through a Vite plugin works; the engine evaluates your modules itself, so it does not care what the plugin makes of JSX, nor in which order the plugins are listed. `apps/sky` is the Solid 2 app (`solid-js` and `@solidjs/web` 2.0.0-rc.13, `@solidjs/vite-plugin` 3.0.0-next.47):
|
|
50
|
+
|
|
51
|
+
```ts skip
|
|
52
|
+
// vite.config.ts
|
|
53
|
+
import solid from "@solidjs/vite-plugin";
|
|
54
|
+
import cssints, { cssTarget, targets } from "cssints/vite";
|
|
55
|
+
|
|
56
|
+
export default {
|
|
57
|
+
plugins: [cssints(), solid()],
|
|
58
|
+
css: { transformer: "lightningcss", lightningcss: { targets } },
|
|
59
|
+
build: { cssMinify: "lightningcss", cssTarget },
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`tsconfig.json` is Solid's: `"jsx": "preserve"`, `"jsxImportSource": "@solidjs/web"`, `"types": ["vite/client"]`. Solid's `class` takes a string, an object or an array, so `cn` is not needed for the join: `class={[card, on() && p(4)]}` and `class={badge({ tone: tone() })}` work as in React. A site's arguments are still build-time values, so a signal goes in the condition or in a `cv` variant, not in `p(n())`. Under `vite dev` an edit of a component replaces it and resets its state below it (Solid's refresh); an edit of a style changes the class strings and the sheet with no reload.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { bg, color, cn, cv, flex, hover, media, p, style, token } from "cssints" with { type: "cssints" };
|
|
67
|
+
|
|
68
|
+
const md = media("(width >= 40rem)");
|
|
69
|
+
const accent = token.color("#2563eb", "accent");
|
|
70
|
+
|
|
71
|
+
const button = cv({
|
|
72
|
+
base: cn(p(2), color("white"), bg(accent), hover(bg("#1d4ed8"))),
|
|
73
|
+
variants: { wide: { true: md(p(4)) } },
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const Row = ({ wide, on }: { wide?: boolean; on?: boolean }) => (
|
|
77
|
+
<div className={cn(flex().alignItems("center").gap(2), on && p(1))} style={style(token.set(accent, "#dc2626"))}>
|
|
78
|
+
<button className={button({ wide })}>Save</button>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`with { type: "cssints" }` marks what runs at build time. The plugin replaces each call with a class string, so the browser gets `__cx([], "_1s47490 _1ob5z5m _g2iyum", on && "_lqk467")` and `button` is a call of the runtime with `{ base: "_1t8gqy5 ...", ... }`, so it never sees `flex()`. `virtual:cssints.css` is one sheet with every rule once, sorted by layer, then condition, then shorthand before longhand, then class name:
|
|
84
|
+
|
|
85
|
+
```css
|
|
86
|
+
@layer _.k, _.a, _.b, _.c, _.d, _.e;
|
|
87
|
+
@property --accent{syntax:"<color>";inherits:true;initial-value:#2563eb}
|
|
88
|
+
@layer _.a {
|
|
89
|
+
._1t8gqy5{padding:0.5rem}
|
|
90
|
+
._lqk467{padding:0.25rem}
|
|
91
|
+
._g2iyum{gap:0.5rem}
|
|
92
|
+
._17xzvrh{background-color:var(--accent)}
|
|
93
|
+
._1binn6n{color:white}
|
|
94
|
+
._1ob5z5m{align-items:center}
|
|
95
|
+
._1s47490{display:flex}
|
|
96
|
+
}
|
|
97
|
+
@layer _.b {
|
|
98
|
+
._1tsjws2:hover{background-color:#1d4ed8}
|
|
99
|
+
@media (width >= 40rem) {
|
|
100
|
+
._tdcza3{padding:1rem}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### In a terminal
|
|
106
|
+
|
|
107
|
+
`import * as css from "cssints/tui" with { type: "cssints" }` gives the members a terminal has (cells, colours, borders, flex, states, `cursor`), and the plugin compiles them to prop objects for [OpenTUI](https://opentui.com) renderables instead of classes; `@cssints/tui` applies their states and themes: [packages/tui](packages/tui/README.md), docs/API.md "TUI".
|
|
108
|
+
|
|
109
|
+
More in [docs/API.md](docs/API.md): property names and short names, `flex()` and `grid()` chains, conditions and layers, tokens and themes, `cn` and `cv`, and the errors you can get.
|
|
110
|
+
|
|
111
|
+
## Debugging
|
|
112
|
+
|
|
113
|
+
`CSSINTS_TRACE=otlp bun run build` (or `dev`) sends a trace of every Vite hook, with the file of each module, to a local OpenTelemetry backend; unset, it costs nothing. How to start the backend in one `docker run`, and the spans: [docs/API.md](docs/API.md#debugging-traces-of-the-engine-cssints_trace-cssints-r2qq).
|
|
114
|
+
|
|
115
|
+
## For agents
|
|
116
|
+
|
|
117
|
+
`cssints` ships two [agent skills](https://agentskills.io) in its package, `skills/cssints` (writing styles: setup,
|
|
118
|
+
`cn`/`cv`, conditions, tokens and themes, the plugin packages, build errors) and `skills/cssints-plugin` (writing
|
|
119
|
+
a plugin on `scope()`, with its tests). Install them into your agent's skills folder:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx skills-npm # links the skills of every installed package that ships some
|
|
123
|
+
npx skills add ./node_modules/cssints/skills # or the skills CLI, for opencode, Claude Code, Codex, Cursor, ...
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Agents working on this repository get `.agents/skills/cssints-contributor` (also `.claude/skills`) with no install.
|
|
127
|
+
|
|
128
|
+
`@cssints/mcp` is an MCP server with the build's own checks (`check_declaration`, `check_support`, `check_contrast`,
|
|
129
|
+
`compile`, `search_docs`, `list_plugins`), in [packages/mcp](packages/mcp) with the config for opencode, Claude Code
|
|
130
|
+
and Cursor. The skills use it when it is configured and work without it.
|
|
131
|
+
|
|
132
|
+
## Layout
|
|
133
|
+
|
|
134
|
+
| Path | What |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `packages/cssints` | Vite plugin (`cssints/vite`) on the Vite Module Runner, and the `cssints` library |
|
|
137
|
+
| `packages/css-grammar` | Type-level recognizer and its runtime twin over mdn-data grammars, Typed OM values, browser support and prose rules |
|
|
138
|
+
| `packages/icons` | `@cssints/icons`: icons from iconify JSON collections or SVG folders, a mask style with no runtime |
|
|
139
|
+
| `packages/mise-en-mode` | `@cssints/mise-en-mode`: looks as themes in the layers app < author < viewer, on `data-mode` scopes, checked against the tokens and by `check.contrast` |
|
|
140
|
+
| `apps/demo` | React demo of every 0.1 feature |
|
|
141
|
+
| `apps/docs` | The documentation site: this file and `docs/API.md` as pages, with search and type hovers (`cd apps/docs && bun run dev`) |
|
|
142
|
+
| `docs/research`, `docs/archive` | Comparisons with other libraries; outdated plans |
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
bun install
|
|
148
|
+
bun run test # all packages (vp run -r test, cached where possible); includes the type-check of the code in docs/API.md and this file
|
|
149
|
+
bun run bench # type instantiation budgets (tsc and tsgo)
|
|
150
|
+
bun run check # format, lint, type-check (vp check), then the Effect diagnostics (bun run typecheck)
|
|
151
|
+
bun run diff:check # the added lines against master: anti-slop rules, key-file changes (tools/ai-automation)
|
|
152
|
+
cd apps/demo && bun run dev
|
|
153
|
+
cd apps/docs && bun run dev # the documentation site, on the pages of README.md and docs/API.md
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
See [AGENTS.md](AGENTS.md) for the layout, commands and code style.
|
|
157
|
+
|
|
158
|
+
## Credits
|
|
159
|
+
|
|
160
|
+
cssints is MIT. It stands on other people's work; this is what we took, from whom, and under which license. A package that copies data or code carries the upstream notice (a Credits or Licenses section of its README, or `THIRD_PARTY_NOTICES.md`).
|
|
161
|
+
|
|
162
|
+
### Data and code that ships
|
|
163
|
+
|
|
164
|
+
| Where | What | From | License |
|
|
165
|
+
|---|---|---|---|
|
|
166
|
+
| [@cssints/tokens](packages/tokens/README.md) | the Open Props, Radix Colors, Flexoki and Reasonable Colors sets | [Open Props](https://github.com/argyleink/open-props) (Adam Argyle), [Radix Colors](https://github.com/radix-ui/colors), [Flexoki](https://github.com/kepano/flexoki) (Steph Ango), [Reasonable Colors](https://github.com/matthewhowell/reasonable-colors) (Matthew Howell) | MIT, notices in its README |
|
|
167
|
+
| [@cssints/css-grammar](packages/css-grammar/THIRD_PARTY_NOTICES.md) | the CSS syntax strings (`src/mdn.ts`, `keywords.ts`, `functions.ts`), also in the types of `cssints` | [mdn-data](https://github.com/mdn/data); the numeric ranges it omits from [@webref/css](https://github.com/w3c/webref) | CC0-1.0; MIT (W3C) |
|
|
168
|
+
| @cssints/css-grammar | browser support warnings, read from the package (not copied) | [@mdn/browser-compat-data](https://github.com/mdn/browser-compat-data) | CC0-1.0 |
|
|
169
|
+
| [@cssints/fonts](packages/fonts/README.md) | the latin character weights and the metrics of Arial, Times New Roman and Courier New | [Capsize](https://github.com/seek-oss/capsize) (SEEK) | MIT, notice in its README |
|
|
170
|
+
| [@cssints/icons](packages/icons/README.md) | nothing: the collections are your dependency | [Iconify](https://iconify.design) `@iconify-json/*` | each collection's own |
|
|
171
|
+
| `tools/ai-automation/vendor` | lint rules, not published | [dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop) (Dillon Mulroy) | MIT, `LICENSE` and `UPSTREAM.md` |
|
|
172
|
+
| [apps/sky](apps/sky/NOTICE.md) | layout and token values; Inter; Phosphor icons | Mu and `@bsky.app/alf` (Bluesky Social PBC), [Inter](https://github.com/rsms/inter), [Phosphor](https://phosphoricons.com) | MIT; OFL-1.1; MIT |
|
|
173
|
+
| apps/demo | Material Design Icons (`@iconify-json/mdi`) | Pictogrammers | Apache-2.0 |
|
|
174
|
+
|
|
175
|
+
### Ideas and designs
|
|
176
|
+
|
|
177
|
+
No code is copied from these; each package README says which part follows which.
|
|
178
|
+
|
|
179
|
+
- **Plugin packages.** [Every Layout](https://every-layout.dev) and [smolcss](https://smolcss.dev) (layout), [nytimes/text-balancer](https://github.com/nytimes/text-balancer) and [react-wrap-balancer](https://github.com/shuding/react-wrap-balancer) (balance), [fluid-tailwind](https://github.com/barvian/fluid-tailwind) (fluid), [postcss-easing-gradients](https://github.com/larsenwork/postcss-easing-gradients) (gradient), [tailwindcss-motion](https://github.com/romboHQ/tailwindcss-motion) and [tailwindcss-animate](https://github.com/jamiebuilds/tailwindcss-animate) (motion), [the supersonic plugin](https://github.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation) and [lax.js](https://github.com/alexfoxy/lax.js) (reveal), [sync-skeleton](https://github.com/crutchcorn/sync-skeleton) (skeleton), [bramus/sticky-observer](https://github.com/bramus/sticky-observer) (stuck), [unpic-placeholder](https://github.com/ascorbic/unpic-placeholder) and [ThumbHash](https://github.com/evanw/thumbhash) (placeholder), [nutelch](https://github.com/meodai/nutelch) and [Leonardo](https://github.com/adobe/leonardo) (ramp), the resets of Josh Comeau, Jake Lazaroff, Vale and Andy Bell (reset), [Mise en Mode](https://standard.mode.place) and its Mode Contract (mise-en-mode), [brendonovich/vite-plugin-opencode](https://github.com/Brendonovich/vite-plugin-opencode) (the prompt box of devtools).
|
|
180
|
+
- **The engine.** The comparisons in [docs/research](docs/research) record what we took from [StyleX](https://stylexjs.com), [vanilla-extract](https://vanilla-extract.style), [Tailwind CSS](https://tailwindcss.com), [Tokenami](https://tokenami.com), [SugarCube](https://sugarcube.sh) and the [React Spectrum](https://react-spectrum.adobe.com) style macros; the first spark was [Haz's post](https://bsky.app/profile/haz.dev/post/3mebm2mx6jk27) on a CSS framework in TypeScript. Tokens follow the [Design Tokens Community Group](https://www.designtokens.org) format.
|
|
181
|
+
- **apps/sky** follows the look of Mu and the hue slider of Witchsky.
|
|
182
|
+
|
|
183
|
+
### Built on
|
|
184
|
+
|
|
185
|
+
[Effect](https://effect.website), [Vite](https://vite.dev) and Vite+, [OXC](https://oxc.rs), [LightningCSS](https://lightningcss.dev) (MPL-2.0; its wasm build runs in the playground), [culori](https://culorijs.org), [TypeScript](https://www.typescriptlang.org) and TypeScript Go, [Bun](https://bun.sh), [Solid](https://www.solidjs.com), [OpenTUI](https://opentui.com), [Shiki](https://shiki.style), [Monaco Editor](https://microsoft.github.io/monaco-editor/), [Playwright](https://playwright.dev), [fast-check](https://fast-check.dev), [Quint](https://quint-lang.org), [atcute](https://github.com/mary-ext/atcute), and [Beans](https://github.com/hmans/beans) for the tickets. Phosphor icons (MIT) are on this site through `@iconify-json/ph`. Every dependency keeps its own license; none is copied into the repository.
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
MIT
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//#region src/author-types.d.ts
|
|
2
|
+
/** A declaration a member emits: property and value (a custom property, `--_r`, is allowed). */
|
|
3
|
+
type Decl = readonly [prop: string, value: string];
|
|
4
|
+
/** A feature of `@mdn/browser-compat-data`: `css.at-rules.container.style_queries_for_custom_properties`. */
|
|
5
|
+
type BcdKey = `css.${string}`;
|
|
6
|
+
/**
|
|
7
|
+
* Global rules, registered once by `key` (`name@version`) in layer `_.k`; `atProperty` (the `@property` rules of its
|
|
8
|
+
* custom properties) goes at the top of the sheet. A target lacking a `requires` feature gets a support warning, never an
|
|
9
|
+
* error; `{ key, exact: true }` also counts a partial implementation as lacking (relative colour, whose channel scales
|
|
10
|
+
* differ in Chrome 119-121 and Safari 16.4-17.6). Two members share a kernel by pointing at the same object; the same key with other CSS is a build error, as is
|
|
11
|
+
* a custom property or a marker that two keys share.
|
|
12
|
+
*/
|
|
13
|
+
interface Kernel {
|
|
14
|
+
key: string;
|
|
15
|
+
css: string;
|
|
16
|
+
atProperty?: string;
|
|
17
|
+
requires?: (BcdKey | {
|
|
18
|
+
key: BcdKey;
|
|
19
|
+
exact: true;
|
|
20
|
+
})[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A global rule a member contributes. It is identified by its content (`layer` and `css`): the same rule from two
|
|
24
|
+
* styles is one, an edit of it replaces the old one, and it is in the sheet of the modules whose styles carry it, until
|
|
25
|
+
* they stop. It goes to layer `_.k` with the kernels. Unlike a kernel, two globals **may** declare the same custom
|
|
26
|
+
* property: a look sets the same `--color-*` in several source layers, which is the point of fill-through. Use it for a
|
|
27
|
+
* rule that is not a class.
|
|
28
|
+
*/
|
|
29
|
+
interface GlobalRule {
|
|
30
|
+
css: string;
|
|
31
|
+
/**
|
|
32
|
+
* A top-level `@layer` of this name to put the rule in (a rule that must beat the engine's `:root` fallbacks). The
|
|
33
|
+
* layer must be declared: by `layers` of the same `Made`, or by `layers()` in a module this one imports. Omitted:
|
|
34
|
+
* layer `_.k`.
|
|
35
|
+
*/
|
|
36
|
+
layer?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A theme a member contributes, as `createGlobalTheme(tokens, selector, overrides, { layer, scope })` makes one: the
|
|
40
|
+
* same checks (the paths and values of `overrides` against the types of `tokens`, a layer that is declared), the
|
|
41
|
+
* aliases that depend on an override declared again, and `check.contrast` reads it. `tokens` is a token set of
|
|
42
|
+
* `createTokens()` that the member got from its caller (a closure: the arguments of a member are joined into one string).
|
|
43
|
+
*/
|
|
44
|
+
interface ThemeRule {
|
|
45
|
+
tokens: object;
|
|
46
|
+
overrides: object;
|
|
47
|
+
selector: string;
|
|
48
|
+
layer?: string;
|
|
49
|
+
/** A selector: the rule goes inside `@scope (<scope>) { ... }`. */
|
|
50
|
+
scope?: string;
|
|
51
|
+
}
|
|
52
|
+
/** One element family of a runtime: the nearest `marker` ancestor's computed `read` is copied into `write`. */
|
|
53
|
+
interface ParentVar {
|
|
54
|
+
marker: string;
|
|
55
|
+
read: `--${string}`;
|
|
56
|
+
write: `--${string}`;
|
|
57
|
+
/** A property that changes when a CSS condition (`:hover`, a media query) changes `read`: its transition re-runs the copy. */
|
|
58
|
+
watch?: `--${string}`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The runtime half of a member. The client module that holds a site with the member imports
|
|
62
|
+
* `virtual:cssints/runtime/<name>`, which is empty unless a browserslist target lacks a feature in `polyfills` (and, with
|
|
63
|
+
* `parentVars`, has `@property`, which the script stands on); then it imports `script`, configures `cssints/parent-vars`,
|
|
64
|
+
* and `css` (the fallback rules) is added to the sheet in layer `_.k`. A `requires` of the member's kernel that is in
|
|
65
|
+
* `polyfills` warns only for the targets the runtime does not reach.
|
|
66
|
+
*/
|
|
67
|
+
interface Runtime {
|
|
68
|
+
polyfills: BcdKey[];
|
|
69
|
+
parentVars?: ParentVar[];
|
|
70
|
+
css?: string;
|
|
71
|
+
/** The plugin's browser script, a module specifier the app resolves (`"@cssints/balance/runtime"`): imported, never evaluated at build time. */
|
|
72
|
+
script?: string;
|
|
73
|
+
}
|
|
74
|
+
/** A build error when one `cn()` holds the member's marker and a property listed here (a name, or a pattern). */
|
|
75
|
+
interface Conflict {
|
|
76
|
+
props: (string | RegExp)[];
|
|
77
|
+
message: string;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { Kernel as a, GlobalRule as i, Conflict as n, Runtime as o, Decl as r, ThemeRule as s, BcdKey as t };
|
package/dist/check.d.mts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { t as ContrastError } from "./errors-D4ZbcwRo.mjs";
|
|
2
|
+
import { Warning } from "@cssints/css-grammar/support";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
//#region src/check.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The WCAG 2.x contrast ratio of two CSS colours, as `css.check.contrast` computes it: culori reads the colour, one
|
|
7
|
+
* outside sRGB is clipped to it, a translucent one has no contrast. `text` is the ratio cut (not rounded) at two places,
|
|
8
|
+
* as the check's message prints it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const contrast: (foreground: string, background: string) => Effect.Effect<{
|
|
11
|
+
ratio: number;
|
|
12
|
+
text: string;
|
|
13
|
+
}, ContrastError, never>;
|
|
14
|
+
/**
|
|
15
|
+
* What LightningCSS makes of `property: value` for the targets of a support warning (`support()` of
|
|
16
|
+
* `@cssints/css-grammar/support`), when it lowers the feature the warning is about: the value of the first declaration it
|
|
17
|
+
* writes (`oklch(…)` becomes a hex), else undefined. A build drops a warning that LightningCSS lowers; an inline `style()`
|
|
18
|
+
* never goes through it and keeps the warning. A custom property is `--name`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const loweredOf: (property: string, value: string, warning: Warning) => Effect.Effect<string | undefined, never, never>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { ContrastError };
|
package/dist/check.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { s as ContrastError } from "./registry-CTumHB8p.mjs";
|
|
2
|
+
import { t as fallbackOf } from "./lowered-D2KusPLp.mjs";
|
|
3
|
+
import { i as shown, r as ratio, t as luminance } from "./color-BYIxVLlS.mjs";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
//#region src/check.ts
|
|
6
|
+
/**
|
|
7
|
+
* The WCAG 2.x contrast ratio of two CSS colours, as `css.check.contrast` computes it: culori reads the colour, one
|
|
8
|
+
* outside sRGB is clipped to it, a translucent one has no contrast. `text` is the ratio cut (not rounded) at two places,
|
|
9
|
+
* as the check's message prints it.
|
|
10
|
+
*/
|
|
11
|
+
const contrast = Effect.fn("cssints.contrast")(function* (foreground, background) {
|
|
12
|
+
const [f, b] = yield* Effect.sync(() => [luminance(foreground), luminance(background)]);
|
|
13
|
+
if (f.lum === void 0) return yield* new ContrastError({
|
|
14
|
+
colour: foreground,
|
|
15
|
+
message: `"${foreground}" ${f.why}`
|
|
16
|
+
});
|
|
17
|
+
if (b.lum === void 0) return yield* new ContrastError({
|
|
18
|
+
colour: background,
|
|
19
|
+
message: `"${background}" ${b.why}`
|
|
20
|
+
});
|
|
21
|
+
const r = ratio(f.lum, b.lum);
|
|
22
|
+
return {
|
|
23
|
+
ratio: r,
|
|
24
|
+
text: shown(r)
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* What LightningCSS makes of `property: value` for the targets of a support warning (`support()` of
|
|
29
|
+
* `@cssints/css-grammar/support`), when it lowers the feature the warning is about: the value of the first declaration it
|
|
30
|
+
* writes (`oklch(…)` becomes a hex), else undefined. A build drops a warning that LightningCSS lowers; an inline `style()`
|
|
31
|
+
* never goes through it and keeps the warning. A custom property is `--name`.
|
|
32
|
+
*/
|
|
33
|
+
const loweredOf = Effect.fn("cssints.loweredOf")(function* (property, value, warning) {
|
|
34
|
+
return yield* Effect.sync(() => fallbackOf(property, value, warning));
|
|
35
|
+
});
|
|
36
|
+
//#endregion
|
|
37
|
+
export { ContrastError, contrast, loweredOf };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { clampRgb, modeA98, modeHsl, modeHwb, modeLab, modeLch, modeLrgb, modeOklab, modeOklch, modeP3, modeProphoto, modeRec2020, modeRgb, modeXyz50, modeXyz65, parse, useMode, wcagLuminance } from "culori/fn";
|
|
2
|
+
//#region src/color.ts
|
|
3
|
+
for (const mode of [
|
|
4
|
+
modeRgb,
|
|
5
|
+
modeHsl,
|
|
6
|
+
modeHwb,
|
|
7
|
+
modeLab,
|
|
8
|
+
modeLch,
|
|
9
|
+
modeOklab,
|
|
10
|
+
modeOklch,
|
|
11
|
+
modeLrgb,
|
|
12
|
+
modeP3,
|
|
13
|
+
modeA98,
|
|
14
|
+
modeProphoto,
|
|
15
|
+
modeRec2020,
|
|
16
|
+
modeXyz50,
|
|
17
|
+
modeXyz65
|
|
18
|
+
]) useMode(mode);
|
|
19
|
+
const parseColor = (text) => parse(text);
|
|
20
|
+
const luminance = (text) => {
|
|
21
|
+
const c = parse(text);
|
|
22
|
+
if (!c) return { why: "is not a colour the check can read" };
|
|
23
|
+
if ((c.alpha ?? 1) < 1) return { why: "is translucent, and has no contrast without a backdrop" };
|
|
24
|
+
return { lum: wcagLuminance(clampRgb(c)) };
|
|
25
|
+
};
|
|
26
|
+
const ratio = (a, b) => (Math.max(a, b) + .05) / (Math.min(a, b) + .05);
|
|
27
|
+
const shown = (r) => (Math.floor(r * 100) / 100).toFixed(2);
|
|
28
|
+
//#endregion
|
|
29
|
+
export { shown as i, parseColor as n, ratio as r, luminance as t };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { a as registry, d as MacroError, o as warn } from "./registry-CTumHB8p.mjs";
|
|
2
|
+
import { l as layerStatements } from "./sheet-BKsMTa0c.mjs";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
//#region src/contrast.ts
|
|
5
|
+
const VAR = /var\((--[^\s,)]+)\)/g;
|
|
6
|
+
const valueOf = (state, name, values) => {
|
|
7
|
+
const def = state.defs.get(name);
|
|
8
|
+
const text = values?.get(name) ?? def?.template;
|
|
9
|
+
return text === void 0 ? def?.value : text.replace(VAR, (m, n) => state.defs.has(n) ? valueOf(state, n, values) : m);
|
|
10
|
+
};
|
|
11
|
+
const affectedBy = (defs, names, out = /* @__PURE__ */ new Set()) => {
|
|
12
|
+
for (const n of names) if (!out.has(n)) {
|
|
13
|
+
out.add(n);
|
|
14
|
+
affectedBy(defs, defs.get(n)?.refs ?? [], out);
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
};
|
|
18
|
+
const together = ({ themed, layers }) => {
|
|
19
|
+
const side = (before) => layerStatements([...layers.values()].flatMap((l) => l.before === before ? [l.names] : [])).flat();
|
|
20
|
+
const order = [
|
|
21
|
+
...side(true),
|
|
22
|
+
"",
|
|
23
|
+
...side(false)
|
|
24
|
+
];
|
|
25
|
+
const rank = (layer) => order.indexOf(layer ?? "");
|
|
26
|
+
const groups = /* @__PURE__ */ new Map();
|
|
27
|
+
for (const t of [...themed.values()].sort((a, b) => rank(a.layer) - rank(b.layer) || (a.key < b.key ? -1 : 1))) {
|
|
28
|
+
if (!t.theme) continue;
|
|
29
|
+
const { label, scope, values } = t.theme;
|
|
30
|
+
const key = `${label}\t${scope ?? ""}`;
|
|
31
|
+
const group = groups.get(key) ?? groups.set(key, {
|
|
32
|
+
label,
|
|
33
|
+
values: /* @__PURE__ */ new Map()
|
|
34
|
+
}).get(key);
|
|
35
|
+
for (const [n, v] of values) group.values.set(n, v);
|
|
36
|
+
}
|
|
37
|
+
return groups.values();
|
|
38
|
+
};
|
|
39
|
+
const verify = (check, state, { luminance, ratio, shown }) => {
|
|
40
|
+
const { fg, bg, min } = check;
|
|
41
|
+
const path = (n) => state.defs.get(n)?.path ?? n;
|
|
42
|
+
const affecting = affectedBy(state.defs, [fg, bg]);
|
|
43
|
+
const subjects = [{ label: "the default values" }];
|
|
44
|
+
for (const { label, values } of together(state)) if ([...values.keys()].some((n) => affecting.has(n))) subjects.push({
|
|
45
|
+
label: `theme ${label}`,
|
|
46
|
+
values
|
|
47
|
+
});
|
|
48
|
+
const errors = [];
|
|
49
|
+
for (const { label, values } of subjects) {
|
|
50
|
+
const pair = [fg, bg].map((n) => {
|
|
51
|
+
const text = valueOf(state, n, values) ?? "";
|
|
52
|
+
return {
|
|
53
|
+
name: n,
|
|
54
|
+
text,
|
|
55
|
+
l: luminance(text)
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
const [f, b] = pair;
|
|
59
|
+
let ok = true;
|
|
60
|
+
for (const p of pair) if (p.l.why !== void 0) {
|
|
61
|
+
ok = false;
|
|
62
|
+
warn(state, `contrast ${p.name} ${p.text}`, `cssints: contrast is not checked for ${path(p.name)} in ${label}: "${p.text}" ${p.l.why}`, check.at);
|
|
63
|
+
}
|
|
64
|
+
if (!ok || f.l.lum === void 0 || b.l.lum === void 0) continue;
|
|
65
|
+
const r = ratio(f.l.lum, b.l.lum);
|
|
66
|
+
if (r < min) errors.push(`cssints: contrast ${shown(r)} is below ${min}: ${path(fg)} ${f.text} on ${path(bg)} ${b.text}, in ${label}`);
|
|
67
|
+
}
|
|
68
|
+
return errors;
|
|
69
|
+
};
|
|
70
|
+
const verifyChecks = Effect.fnUntraced(function* (select) {
|
|
71
|
+
const checks = [...registry.checks.values()].filter(select);
|
|
72
|
+
if (!checks.length) return;
|
|
73
|
+
const colours = yield* Effect.promise(() => import("./color-C8OzjAeg.mjs"));
|
|
74
|
+
for (const check of checks) {
|
|
75
|
+
const errors = verify(check, registry, colours);
|
|
76
|
+
if (errors.length) return yield* new MacroError({
|
|
77
|
+
message: errors.join("\n"),
|
|
78
|
+
...check.at
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
//#endregion
|
|
83
|
+
export { valueOf as n, verifyChecks as r, together as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/derived.d.ts
|
|
2
|
+
type Space = "srgb" | "srgb-linear" | "oklab" | "oklch" | "lab";
|
|
3
|
+
export interface Color {
|
|
4
|
+
s: Space;
|
|
5
|
+
c: number[];
|
|
6
|
+
a: number;
|
|
7
|
+
}
|
|
8
|
+
/** The custom property of :root that carries the data (derived-data.ts writes it). */
|
|
9
|
+
export declare const KEY = "--_derived";
|
|
10
|
+
export declare const evaluate: (text: string, get?: (name: string) => string | Color) => Color;
|
|
11
|
+
export declare const css: (c: Color) => string;
|
|
12
|
+
declare const _default: () => void;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { _default as default };
|