@stacksjs/ts-css 0.1.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/ENGINE.md +677 -0
  3. package/PLUGIN.md +217 -0
  4. package/dist/bin/cli.js +33 -0
  5. package/dist/bin/cssx.js +405 -0
  6. package/dist/chunk-0h48763m.js +2 -0
  7. package/dist/chunk-br9jax0b.js +2 -0
  8. package/dist/chunk-f9gzb0a5.js +3 -0
  9. package/dist/chunk-genwkevp.js +3 -0
  10. package/dist/chunk-jj3s4ctf.js +62 -0
  11. package/dist/chunk-mz87cw2e.js +2 -0
  12. package/dist/engine/build.d.ts +26 -0
  13. package/dist/engine/color-modifier.d.ts +8 -0
  14. package/dist/engine/config.d.ts +5 -0
  15. package/dist/engine/format.d.ts +20 -0
  16. package/dist/engine/generator.d.ts +14 -0
  17. package/dist/engine/index.d.ts +13 -0
  18. package/dist/engine/index.js +653 -0
  19. package/dist/engine/parser.d.ts +68 -0
  20. package/dist/engine/plugin.d.ts +22 -0
  21. package/dist/engine/preflight-forms.d.ts +6 -0
  22. package/dist/engine/preflight.d.ts +3 -0
  23. package/dist/engine/rules-advanced.d.ts +27 -0
  24. package/dist/engine/rules-effects.d.ts +35 -0
  25. package/dist/engine/rules-forms.d.ts +8 -0
  26. package/dist/engine/rules-grid.d.ts +13 -0
  27. package/dist/engine/rules-icons.d.ts +2 -0
  28. package/dist/engine/rules-interactivity.d.ts +41 -0
  29. package/dist/engine/rules-layout.d.ts +28 -0
  30. package/dist/engine/rules-transforms.d.ts +35 -0
  31. package/dist/engine/rules-typography.d.ts +47 -0
  32. package/dist/engine/rules.d.ts +56 -0
  33. package/dist/engine/scanner.d.ts +16 -0
  34. package/dist/engine/style/api.d.ts +95 -0
  35. package/dist/engine/style/collect.d.ts +10 -0
  36. package/dist/engine/style/evaluate.d.ts +11 -0
  37. package/dist/engine/style/hash.d.ts +15 -0
  38. package/dist/engine/style/index.d.ts +56 -0
  39. package/dist/engine/style/plugin.d.ts +27 -0
  40. package/dist/engine/style/priority.d.ts +12 -0
  41. package/dist/engine/style/registry.d.ts +56 -0
  42. package/dist/engine/style/types.d.ts +54 -0
  43. package/dist/engine/style/value.d.ts +15 -0
  44. package/dist/engine/transformer-compile-class.d.ts +34 -0
  45. package/dist/engine/types.d.ts +156 -0
  46. package/dist/index.js +1 -60
  47. package/dist/optimize/index.js +1 -1
  48. package/dist/parse/index.js +1 -1
  49. package/dist/parse/list.d.ts +1 -1
  50. package/dist/select/index.js +1 -1
  51. package/dist/what/index.js +1 -1
  52. package/package.json +35 -19
  53. package/dist/chunk-1kmkypmr.js +0 -3
  54. package/dist/chunk-2wsah70r.js +0 -2
  55. package/dist/chunk-9ep6bwwb.js +0 -2
  56. package/dist/chunk-edwg811g.js +0 -2
  57. package/dist/chunk-thy3p95k.js +0 -3
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ [Compare changes](https://github.com/stacksjs/ts-css/compare/v0.1.3...v0.1.4)
2
+
3
+ ## 🐛 Bug Fixes
4
+
5
+ - **build**: build the CLI the bin field points at ([61d4584](https://github.com/stacksjs/ts-css/commit/61d4584)) _(by Chris <chrisbreuer93@gmail.com>)_
6
+
7
+ ## 🧹 Chores
8
+
9
+ - release v0.1.4 ([5c4ca3d](https://github.com/stacksjs/ts-css/commit/5c4ca3d)) _(by Chris <chrisbreuer93@gmail.com>)_
10
+
11
+ ## Contributors
12
+
13
+ - _Chris <chrisbreuer93@gmail.com>_
14
+
1
15
  [Compare changes](https://github.com/stacksjs/ts-css/compare/v0.1.2...v0.1.3)
2
16
 
3
17
  ## ⚡ Performance Improvements
package/ENGINE.md ADDED
@@ -0,0 +1,677 @@
1
+ # ts-css
2
+
3
+ A CSS engine with two front ends and one atomic output: Tailwind-compatible **utility classes** for laying out markup, and StyleX-style **typed style objects** for styles that have to be computed, tokenised, or merged across a component boundary. Both compile into the same deduplicated atomic CSS, with nothing left at runtime.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @ts-css/core
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### Programmatic API
14
+
15
+ ```typescript
16
+ import { CSSGenerator, defaultConfig } from '@ts-css/core'
17
+
18
+ const gen = new CSSGenerator(defaultConfig)
19
+ gen.generate('flex')
20
+ gen.generate('items-center')
21
+ gen.generate('gap-4')
22
+ gen.generate('p-8')
23
+ gen.generate('bg-blue-500')
24
+ gen.generate('text-white')
25
+ gen.generate('rounded-lg')
26
+ gen.generate('hover:bg-blue-600')
27
+ gen.generate('dark:bg-gray-900')
28
+
29
+ const css = gen.toCSS(true) // true = include preflight
30
+ ```
31
+
32
+ ### Build API
33
+
34
+ ```typescript
35
+ import { build } from '@ts-css/core'
36
+
37
+ const result = await build({
38
+ content: ['./src/**/*.html', './src/**/*.tsx'],
39
+ output: './dist/styles.css',
40
+ minify: true,
41
+ })
42
+ ```
43
+
44
+ ### Style API
45
+
46
+ ```typescript
47
+ import { css } from '@ts-css/core'
48
+
49
+ const theme = css.defineVars({
50
+ accent: '#0b7',
51
+ surface: { default: '#fff', '@media (prefers-color-scheme: dark)': '#111' },
52
+ })
53
+
54
+ const styles = css.create({
55
+ card: {
56
+ padding: 16,
57
+ backgroundColor: theme.surface,
58
+ ':hover': { transform: 'translateY(-2px)' },
59
+ '@media (min-width: 768px)': { padding: 24 },
60
+ },
61
+ danger: { color: 'red' },
62
+ sized: (width: number) => ({ width }),
63
+ })
64
+
65
+ // { className: 'tc9faijott tcze3ap6z …', style: { '--tc2hxvo7bz': '120px' } }
66
+ css.props(styles.card, isDangerous && styles.danger, styles.sized(120))
67
+ ```
68
+
69
+ `css.props()` merges **per property**, not per class: a later argument replaces
70
+ an earlier one's `color` class outright rather than relying on the cascade to
71
+ outrank it. `null` removes a property, and falsy arguments are skipped.
72
+
73
+ Style factories turn their arguments into inline custom properties, so a
74
+ component rendered at a thousand widths still emits one rule.
75
+
76
+ | | |
77
+ | --- | --- |
78
+ | `css.create(styles)` | compile named style objects into atomic classes |
79
+ | `css.props(...styles)` | merge compiled styles into `className` / `style` |
80
+ | `css.defineVars(vars)` | declare custom properties, get typed `var()` references |
81
+ | `css.createTheme(vars, overrides)` | redeclare a variable group under a generated class |
82
+ | `css.keyframes(frames)` | declare an animation, get its generated name |
83
+ | `css.firstThatWorks(...values)` | progressive-enhancement fallbacks, in CSS order |
84
+ | `css.defineConsts(consts)` | build-time constants that never become CSS |
85
+
86
+ Point `styles` at the modules that declare them and `cssx build` emits their
87
+ CSS alongside the utilities. Outside the CLI, `collectStyles(patterns)` returns
88
+ the stylesheet directly and `stylePlugin()` collects styles during a
89
+ `Bun.build()`.
90
+
91
+ ### CLI
92
+
93
+ ```bash
94
+ cssx build
95
+ cssx build --watch
96
+ cssx build --minify
97
+ ```
98
+
99
+ ## Configuration
100
+
101
+ Create a `css.config.ts` in your project root:
102
+
103
+ ```typescript
104
+ import type { TsCssConfig } from '@ts-css/core'
105
+
106
+ export default {
107
+ content: ['./src/**/*.{html,tsx,stx}'],
108
+ styles: ['./src/**/*.styles.ts'],
109
+ output: './dist/styles.css',
110
+ minify: false,
111
+
112
+ theme: {
113
+ extend: {
114
+ colors: {
115
+ brand: {
116
+ 50: '#eff6ff',
117
+ 500: '#3b82f6',
118
+ 900: '#1e3a5a',
119
+ },
120
+ },
121
+ spacing: {
122
+ '18': '4.5rem',
123
+ '112': '28rem',
124
+ },
125
+ },
126
+ },
127
+
128
+ safelist: ['bg-brand-500', 'text-white'],
129
+ blocklist: ['opacity-0'],
130
+ } satisfies Partial<TsCssConfig>
131
+ ```
132
+
133
+ ### Theme
134
+
135
+ The default theme includes Tailwind-compatible values for:
136
+
137
+ - **colors** - Full color palette (slate, gray, zinc, red, orange, yellow, green, blue, indigo, purple, pink, etc.) with 50-950 shades in oklch
138
+ - **spacing** - 0 through 96, plus `px` (1px), fractional values
139
+ - **fontSize** - xs through 9xl with line-height pairs
140
+ - **fontFamily** - sans, serif, mono
141
+ - **screens** - sm (640px), md (768px), lg (1024px), xl (1280px), 2xl (1536px)
142
+ - **borderRadius** - none, sm, DEFAULT, md, lg, xl, 2xl, 3xl, full
143
+ - **boxShadow** - sm, DEFAULT, md, lg, xl, 2xl, inner, none
144
+
145
+ All theme values can be extended or overridden via `theme.extend`.
146
+
147
+ ## Utilities
148
+
149
+ ### Layout
150
+
151
+ | Utility | CSS |
152
+ |---------|-----|
153
+ | `block`, `inline-block`, `flex`, `grid`, `hidden` | `display: *` |
154
+ | `static`, `fixed`, `absolute`, `relative`, `sticky` | `position: *` |
155
+ | `top-*`, `right-*`, `bottom-*`, `left-*`, `inset-*` | Positioning |
156
+ | `z-*` | `z-index` |
157
+ | `overflow-*`, `overflow-x-*`, `overflow-y-*` | Overflow |
158
+ | `float-*`, `clear-*` | Float & clear (includes logical `start`/`end`) |
159
+ | `isolate`, `isolation-auto` | Isolation |
160
+ | `object-cover`, `object-contain`, `object-fill`, `object-none` | Object fit |
161
+ | `object-top`, `object-center`, `object-bottom`, etc. | Object position |
162
+ | `columns-*` | Multi-column layout |
163
+ | `break-before-*`, `break-after-*`, `break-inside-*` | Break behavior |
164
+ | `box-border`, `box-content` | Box sizing |
165
+ | `aspect-auto`, `aspect-square`, `aspect-video` | Aspect ratio |
166
+ | `visible`, `invisible`, `collapse` | Visibility |
167
+
168
+ ### Flexbox
169
+
170
+ | Utility | CSS |
171
+ |---------|-----|
172
+ | `flex-row`, `flex-col`, `flex-row-reverse`, `flex-col-reverse` | Direction |
173
+ | `flex-wrap`, `flex-nowrap`, `flex-wrap-reverse` | Wrap |
174
+ | `flex-1`, `flex-auto`, `flex-initial`, `flex-none` | Flex shorthand |
175
+ | `grow`, `grow-0`, `shrink`, `shrink-0` | Grow & shrink |
176
+ | `basis-*` | Flex basis (supports fractions: `basis-1/2`) |
177
+ | `justify-*`, `items-*`, `self-*` | Alignment |
178
+ | `justify-items-*`, `justify-self-*` | Justify items/self |
179
+ | `content-*` | Align content |
180
+ | `order-*`, `order-first`, `order-last`, `order-none` | Order |
181
+
182
+ ### Grid
183
+
184
+ | Utility | CSS |
185
+ |---------|-----|
186
+ | `grid-cols-*`, `grid-rows-*` | Template columns/rows (1-12, none, subgrid) |
187
+ | `col-span-*`, `col-start-*`, `col-end-*` | Column placement |
188
+ | `row-span-*`, `row-start-*`, `row-end-*` | Row placement |
189
+ | `grid-flow-row`, `grid-flow-col`, `grid-flow-dense` | Auto flow |
190
+ | `auto-cols-*`, `auto-rows-*` | Auto columns/rows |
191
+ | `gap-*`, `gap-x-*`, `gap-y-*` | Gap |
192
+ | `place-content-*`, `place-items-*`, `place-self-*` | Place shortcuts |
193
+
194
+ Arbitrary grid templates use underscore-to-space conversion:
195
+
196
+ ```html
197
+ <div class="grid grid-cols-[120px_1fr_200px]">
198
+ <!-- grid-template-columns: 120px 1fr 200px -->
199
+ ```
200
+
201
+ ### Spacing
202
+
203
+ | Utility | CSS |
204
+ |---------|-----|
205
+ | `p-*`, `px-*`, `py-*`, `pt-*`, `pr-*`, `pb-*`, `pl-*` | Padding |
206
+ | `ps-*`, `pe-*` | Padding inline start/end (logical) |
207
+ | `m-*`, `mx-*`, `my-*`, `mt-*`, `mr-*`, `mb-*`, `ml-*` | Margin |
208
+ | `ms-*`, `me-*` | Margin inline start/end (logical) |
209
+ | `space-x-*`, `space-y-*` | Space between children |
210
+
211
+ Negative values supported: `-m-4`, `-translate-x-1`.
212
+
213
+ ### Sizing
214
+
215
+ | Utility | CSS |
216
+ |---------|-----|
217
+ | `w-*`, `h-*` | Width & height |
218
+ | `size-*` | Width + height shorthand |
219
+ | `min-w-*`, `max-w-*`, `min-h-*`, `max-h-*` | Min/max sizing |
220
+
221
+ Values: spacing scale, `auto`, `full` (100%), `screen` (100vw/vh), `min`, `max`, `fit`, fractions (`w-1/2`).
222
+
223
+ ### Colors
224
+
225
+ All color utilities support opacity modifiers:
226
+
227
+ ```html
228
+ <!-- Integer opacity (0-100 scale) -->
229
+ <div class="bg-blue-500/50"> <!-- 50% opacity -->
230
+ <div class="text-white/75"> <!-- 75% opacity -->
231
+ <div class="border-black/10"> <!-- 10% opacity -->
232
+
233
+ <!-- Arbitrary opacity (0-1 scale) -->
234
+ <div class="bg-white/[0.04]"> <!-- 4% opacity -->
235
+ <div class="text-black/[0.87]"> <!-- 87% opacity -->
236
+ ```
237
+
238
+ | Utility | CSS |
239
+ |---------|-----|
240
+ | `bg-*` | Background color |
241
+ | `text-*` | Text color |
242
+ | `border-*` | Border color |
243
+ | `ring-*` | Ring color |
244
+ | `divide-*` | Divide color (supports opacity: `divide-white/10`) |
245
+ | `placeholder-*` | Placeholder color |
246
+ | `accent-*` | Accent color |
247
+ | `caret-*` | Caret color |
248
+ | `fill-*`, `stroke-*` | SVG fill & stroke |
249
+
250
+ Special values: `current` (currentColor), `transparent`, `inherit`, `white`, `black`.
251
+
252
+ ### Typography
253
+
254
+ | Utility | CSS |
255
+ |---------|-----|
256
+ | `text-xs` through `text-9xl` | Font size |
257
+ | `font-thin` through `font-black` | Font weight |
258
+ | `font-sans`, `font-serif`, `font-mono` | Font family |
259
+ | `italic`, `not-italic` | Font style |
260
+ | `tracking-*` | Letter spacing |
261
+ | `leading-*` | Line height |
262
+ | `text-left`, `text-center`, `text-right`, `text-justify` | Alignment |
263
+ | `uppercase`, `lowercase`, `capitalize`, `normal-case` | Text transform |
264
+ | `underline`, `overline`, `line-through`, `no-underline` | Decoration |
265
+ | `decoration-*` | Decoration style, color, thickness |
266
+ | `truncate`, `text-ellipsis`, `text-clip` | Text overflow |
267
+ | `text-wrap`, `text-nowrap`, `text-balance`, `text-pretty` | Text wrap |
268
+ | `line-clamp-*` | Line clamp |
269
+ | `indent-*` | Text indent |
270
+ | `antialiased`, `subpixel-antialiased` | Font smoothing |
271
+ | `tabular-nums`, `lining-nums`, `oldstyle-nums`, etc. | Font variant numeric |
272
+ | `hyphens-none`, `hyphens-manual`, `hyphens-auto` | Hyphens |
273
+ | `whitespace-*` | White space |
274
+ | `break-normal`, `break-words`, `break-all` | Word break |
275
+
276
+ ### Borders
277
+
278
+ | Utility | CSS |
279
+ |---------|-----|
280
+ | `border`, `border-0`, `border-2`, `border-4`, `border-8` | Border width |
281
+ | `border-t`, `border-r`, `border-b`, `border-l` | Side borders |
282
+ | `border-s`, `border-e` | Logical side borders (inline-start/end) |
283
+ | `border-x`, `border-y` | Axis borders |
284
+ | `border-solid`, `border-dashed`, `border-dotted`, `border-double`, `border-none` | Style |
285
+ | `rounded-*` | Border radius |
286
+ | `rounded-s-*`, `rounded-e-*` | Logical border radius |
287
+ | `rounded-ss-*`, `rounded-se-*`, `rounded-es-*`, `rounded-ee-*` | Corner-specific logical radius |
288
+ | `outline-*` | Outline width, style, color |
289
+ | `outline-offset-*` | Outline offset |
290
+ | `ring-*`, `ring-offset-*` | Ring |
291
+ | `divide-x`, `divide-y` | Divide width |
292
+ | `divide-*` | Divide color, style, opacity |
293
+
294
+ ### Effects & Filters
295
+
296
+ | Utility | CSS |
297
+ |---------|-----|
298
+ | `shadow-*` | Box shadow |
299
+ | `shadow-{color}` | Shadow color |
300
+ | `opacity-*` | Opacity (0-100) |
301
+ | `mix-blend-*` | Mix blend mode |
302
+ | `bg-blend-*` | Background blend mode |
303
+ | `blur-*`, `brightness-*`, `contrast-*`, `grayscale-*` | Filters |
304
+ | `invert-*`, `saturate-*`, `sepia-*`, `hue-rotate-*` | Filters |
305
+ | `drop-shadow-*` | Drop shadow filter |
306
+ | `backdrop-blur-*`, `backdrop-brightness-*`, etc. | Backdrop filters |
307
+
308
+ ### Backgrounds & Gradients
309
+
310
+ | Utility | CSS |
311
+ |---------|-----|
312
+ | `bg-fixed`, `bg-local`, `bg-scroll` | Background attachment |
313
+ | `bg-clip-border`, `bg-clip-padding`, `bg-clip-content`, `bg-clip-text` | Background clip |
314
+ | `bg-top`, `bg-center`, `bg-bottom`, etc. | Background position |
315
+ | `bg-repeat`, `bg-no-repeat`, `bg-repeat-x`, `bg-repeat-y` | Background repeat |
316
+ | `bg-auto`, `bg-cover`, `bg-contain` | Background size |
317
+
318
+ **Linear gradients:**
319
+
320
+ ```html
321
+ <div class="bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500">
322
+ ```
323
+
324
+ Directions: `bg-gradient-to-{t,tr,r,br,b,bl,l,tl}`.
325
+
326
+ **Radial gradients:**
327
+
328
+ ```html
329
+ <div class="bg-radial from-white to-blue-500">
330
+ <div class="bg-radial-at-t from-yellow-200 to-orange-500">
331
+ ```
332
+
333
+ Positions: `bg-radial-at-{t,tr,r,br,b,bl,l,tl,c}`.
334
+
335
+ **Conic gradients:**
336
+
337
+ ```html
338
+ <div class="bg-conic from-red-500 via-yellow-500 to-green-500">
339
+ <div class="bg-conic-from-r from-blue-500 to-purple-500">
340
+ ```
341
+
342
+ Starting angles: `bg-conic-from-{t,tr,r,br,b,bl,l,tl}`.
343
+
344
+ ### Transforms & Transitions
345
+
346
+ | Utility | CSS |
347
+ |---------|-----|
348
+ | `scale-*`, `scale-x-*`, `scale-y-*` | Scale |
349
+ | `rotate-*` | Rotate |
350
+ | `translate-x-*`, `translate-y-*` | Translate |
351
+ | `skew-x-*`, `skew-y-*` | Skew |
352
+ | `origin-*` | Transform origin |
353
+ | `transition`, `transition-all`, `transition-colors`, `transition-opacity`, `transition-shadow`, `transition-transform` | Transition property |
354
+ | `duration-*` | Transition duration |
355
+ | `ease-linear`, `ease-in`, `ease-out`, `ease-in-out` | Timing function |
356
+ | `delay-*` | Transition delay |
357
+ | `animate-spin`, `animate-ping`, `animate-pulse`, `animate-bounce`, `animate-none` | Animation |
358
+ | `perspective-*` | 3D perspective |
359
+ | `backface-visible`, `backface-hidden` | Backface visibility |
360
+
361
+ ### Interactivity
362
+
363
+ | Utility | CSS |
364
+ |---------|-----|
365
+ | `cursor-*` | Cursor style |
366
+ | `pointer-events-none`, `pointer-events-auto` | Pointer events |
367
+ | `resize`, `resize-x`, `resize-y`, `resize-none` | Resize |
368
+ | `select-none`, `select-text`, `select-all`, `select-auto` | User select |
369
+ | `scroll-auto`, `scroll-smooth` | Scroll behavior |
370
+ | `scroll-m-*`, `scroll-p-*` | Scroll margin/padding |
371
+ | `snap-x`, `snap-y`, `snap-both`, `snap-mandatory`, `snap-proximity` | Scroll snap |
372
+ | `snap-start`, `snap-end`, `snap-center`, `snap-align-none` | Snap align |
373
+ | `touch-auto`, `touch-none`, `touch-manipulation`, `touch-pan-*` | Touch action |
374
+ | `will-change-auto`, `will-change-scroll`, `will-change-contents`, `will-change-transform` | Will change |
375
+ | `appearance-none`, `appearance-auto` | Appearance |
376
+ | `scrollbar-auto`, `scrollbar-thin`, `scrollbar-none` | Scrollbar width |
377
+
378
+ ### Content
379
+
380
+ ```html
381
+ <div class="before:content-['*'] before:text-red-500">Required</div>
382
+ <div class="before:content-empty before:block before:h-4"></div>
383
+ <div class="after:content-[attr(data-count)]"></div>
384
+ ```
385
+
386
+ | Utility | CSS |
387
+ |---------|-----|
388
+ | `content-none` | `content: none` |
389
+ | `content-empty` | `content: ""` |
390
+ | `content-['text']` | `content: 'text'` |
391
+ | `content-[attr(data-*)]` | `content: attr(data-*)` |
392
+
393
+ ### SVG
394
+
395
+ | Utility | CSS |
396
+ |---------|-----|
397
+ | `fill-none`, `fill-current`, `fill-{color}` | Fill |
398
+ | `stroke-none`, `stroke-current`, `stroke-{color}` | Stroke color |
399
+ | `stroke-0`, `stroke-1`, `stroke-2` | Stroke width |
400
+
401
+ ### Tables
402
+
403
+ | Utility | CSS |
404
+ |---------|-----|
405
+ | `border-collapse`, `border-separate` | Border collapse |
406
+ | `border-spacing-*` | Border spacing |
407
+ | `table-auto`, `table-fixed` | Table layout |
408
+ | `caption-top`, `caption-bottom` | Caption side |
409
+
410
+ ## Variants
411
+
412
+ ### Pseudo-class Variants
413
+
414
+ ```html
415
+ <div class="hover:bg-blue-600 focus:ring-2 active:scale-95">
416
+ <div class="first:mt-0 last:mb-0 odd:bg-gray-50 even:bg-white">
417
+ <div class="disabled:opacity-50 checked:bg-blue-500 required:border-red-500">
418
+ <div class="focus-within:ring-2 focus-visible:outline-2">
419
+ <div class="visited:text-purple-600 target:ring-2">
420
+ <div class="open:rotate-180 empty:hidden">
421
+ <div class="valid:border-green-500 invalid:border-red-500 read-only:opacity-75">
422
+ <div class="autofill:bg-yellow-50 indeterminate:bg-gray-300">
423
+ ```
424
+
425
+ ### Negation Variants
426
+
427
+ ```html
428
+ <div class="not-first:mt-4 not-last:mb-4">
429
+ <div class="not-disabled:cursor-pointer not-empty:block">
430
+ <div class="not-checked:bg-gray-100 not-only:border-b">
431
+ <div class="not-first-of-type:pt-4 not-last-of-type:pb-4">
432
+ ```
433
+
434
+ ### Pseudo-element Variants
435
+
436
+ ```html
437
+ <div class="before:content-[''] before:block before:h-4">
438
+ <div class="after:content-['*'] after:text-red-500">
439
+ <li class="marker:text-blue-500">Item</li>
440
+ <input class="placeholder:text-gray-400">
441
+ <p class="selection:bg-blue-200">Selectable text</p>
442
+ <input class="file:bg-blue-50 file:border-0" type="file">
443
+ ```
444
+
445
+ ### Responsive Variants
446
+
447
+ ```html
448
+ <div class="p-4 sm:p-6 md:p-8 lg:p-12 xl:p-16 2xl:p-20">
449
+ ```
450
+
451
+ | Variant | Breakpoint |
452
+ |---------|-----------|
453
+ | `sm:` | 640px |
454
+ | `md:` | 768px |
455
+ | `lg:` | 1024px |
456
+ | `xl:` | 1280px |
457
+ | `2xl:` | 1536px |
458
+
459
+ ### Dark & Light Mode
460
+
461
+ ```html
462
+ <div class="bg-white dark:bg-gray-900 light:bg-gray-50">
463
+ <div class="text-gray-900 dark:text-white">
464
+ ```
465
+
466
+ Uses class-based strategy (`.dark`/`.light` parent class).
467
+
468
+ ### Group & Peer Variants
469
+
470
+ ```html
471
+ <!-- Group: parent state affects children -->
472
+ <div class="group">
473
+ <p class="group-hover:text-blue-500">Hovering parent</p>
474
+ <p class="group-focus:ring-2">Parent focused</p>
475
+ <p class="group-has-[:checked]:bg-blue-50">Parent has checked input</p>
476
+ </div>
477
+
478
+ <!-- Named groups (for nesting) -->
479
+ <div class="group/card">
480
+ <div class="group/button">
481
+ <span class="group-hover/card:text-blue-500">Card hovered</span>
482
+ </div>
483
+ </div>
484
+
485
+ <!-- Peer: sibling state affects element -->
486
+ <input class="peer" />
487
+ <p class="peer-invalid:text-red-500">Error message</p>
488
+ <p class="peer-has-[:checked]:text-green-500">Checked sibling</p>
489
+ ```
490
+
491
+ ### has: Variant
492
+
493
+ ```html
494
+ <div class="has-[:focus]:ring-2"> <!-- :has(:focus) -->
495
+ <div class="has-[input:checked]:bg-blue-50"> <!-- :has(input:checked) -->
496
+ <div class="has-[>img]:p-4"> <!-- :has(>img) -->
497
+ ```
498
+
499
+ ### aria-* Variants
500
+
501
+ ```html
502
+ <div class="aria-disabled:opacity-50"> <!-- [aria-disabled="true"] -->
503
+ <div class="aria-expanded:rotate-180"> <!-- [aria-expanded="true"] -->
504
+ <div class="aria-[sort=ascending]:text-blue-500"> <!-- [aria-sort=ascending] -->
505
+ ```
506
+
507
+ ### data-* Variants
508
+
509
+ ```html
510
+ <div class="data-loading:opacity-50"> <!-- [data-loading] -->
511
+ <div class="data-[state=active]:bg-white"> <!-- [data-state=active] -->
512
+ <div class="data-[theme=dark]:bg-black"> <!-- [data-theme=dark] -->
513
+ ```
514
+
515
+ ### Media Query Variants
516
+
517
+ ```html
518
+ <div class="landscape:flex-row portrait:flex-col">
519
+ <div class="motion-safe:animate-bounce motion-reduce:animate-none">
520
+ <div class="contrast-more:border-2 contrast-less:border-0">
521
+ <div class="forced-colors:border print:hidden">
522
+ ```
523
+
524
+ Media variants stack with responsive variants:
525
+
526
+ ```html
527
+ <div class="lg:landscape:flex-row">
528
+ <!-- @media (min-width: 1024px) and (orientation: landscape) -->
529
+ ```
530
+
531
+ ### @supports Variant
532
+
533
+ ```html
534
+ <div class="supports-[display:grid]:grid">
535
+ <div class="supports-[backdrop-filter:blur(0)]:backdrop-blur-sm">
536
+ ```
537
+
538
+ ### Container Queries
539
+
540
+ ```html
541
+ <div class="@container">
542
+ <div class="@sm:flex @md:grid @lg:grid-cols-3">
543
+ </div>
544
+ ```
545
+
546
+ ### Direction Variants
547
+
548
+ ```html
549
+ <div class="rtl:text-right ltr:text-left">
550
+ ```
551
+
552
+ ## Arbitrary Values
553
+
554
+ Use brackets for any CSS value:
555
+
556
+ ```html
557
+ <div class="w-[350px] h-[calc(100vh-4rem)] p-[clamp(1rem,3vw,2rem)]">
558
+ <div class="text-[#1a1a1a] bg-[rgb(255,0,0)] border-[oklch(50%_0.2_240)]">
559
+ <div class="grid-cols-[120px_1fr_200px]"> <!-- underscores become spaces -->
560
+ <div class="text-[clamp(1rem,3vw,2rem)]">
561
+ <div class="font-[600]">
562
+ <div class="tracking-[0.2em]">
563
+ <div class="leading-[1.7]">
564
+ ```
565
+
566
+ ### Arbitrary Properties
567
+
568
+ ```html
569
+ <div class="[mask-type:luminance]">
570
+ <div class="[text-wrap:balance]">
571
+ ```
572
+
573
+ ### Type Hints
574
+
575
+ ```html
576
+ <div class="text-[color:var(--brand)]">
577
+ <div class="text-[length:var(--size)]">
578
+ ```
579
+
580
+ ## Important Modifier
581
+
582
+ Prefix with `!` to apply `!important`:
583
+
584
+ ```html
585
+ <div class="!p-4 !text-red-500">
586
+ ```
587
+
588
+ ## Presets
589
+
590
+ ```typescript
591
+ import type { Preset } from '@ts-css/core'
592
+
593
+ const myPreset: Preset = {
594
+ name: 'my-preset',
595
+ theme: {
596
+ extend: {
597
+ colors: {
598
+ brand: '#3b82f6',
599
+ },
600
+ },
601
+ },
602
+ shortcuts: {
603
+ btn: 'px-4 py-2 rounded font-semibold',
604
+ },
605
+ }
606
+
607
+ export default {
608
+ presets: [myPreset],
609
+ }
610
+ ```
611
+
612
+ ## Shortcuts
613
+
614
+ Define reusable class combinations:
615
+
616
+ ```typescript
617
+ export default {
618
+ shortcuts: {
619
+ 'btn': 'px-4 py-2 rounded-lg font-semibold transition-colors',
620
+ 'btn-primary': 'btn bg-blue-500 text-white hover:bg-blue-600',
621
+ 'card': 'rounded-xl border border-gray-200 p-6 shadow-sm',
622
+ },
623
+ }
624
+ ```
625
+
626
+ ## Performance
627
+
628
+ Optimisations that carry the most weight:
629
+
630
+ - **O(1) static utility map** for ~80% of common utilities (display, flex, grid, transitions, etc.)
631
+ - **Pre-computed color map** with a flat cache for instant colour lookups
632
+ - **Class-level caching** prevents duplicate generation
633
+ - **Selector and media-query caching** avoids rebuilding either across rebuilds
634
+ - **Negative match cache** to skip known-unmatched utilities
635
+ - **Memoised `toCSS()`** — serialising is O(rules) and watch mode calls it every
636
+ pass, usually with nothing new to say; a revision counter turns an unchanged
637
+ rebuild into three integer comparisons
638
+ - **Theme-derived tables memoised per sub-object** — a build reusing the default
639
+ theme skips the palette scan entirely, which was two thirds of a cold build
640
+ - **Allocation-lean serialiser** — sorts an index array and groups in one pass,
641
+ rather than a wrapper object per rule plus two intermediate arrays
642
+ - **Content-hashed atomic rules** so two components declaring `padding: 16`
643
+ share one class
644
+
645
+ Cold build, Apple M3 Pro, Bun 1.3.14 — every engine constructed from scratch
646
+ per iteration, all given a utilities-only input so they are asked for the same
647
+ thing:
648
+
649
+ | Scenario | ts-css | UnoCSS | Tailwind v4 | Tailwind v3 |
650
+ | --- | ---: | ---: | ---: | ---: |
651
+ | Simple utilities (10) | **11.52 µs** | 901.82 µs | 987.70 µs | 11.77 ms |
652
+ | Real-world components (~60) | **64.95 µs** | 1.77 ms | 1.21 ms | 11.95 ms |
653
+ | Full project (~800) | **208.23 µs** | 4.55 ms | 1.66 ms | 13.11 ms |
654
+ | 1000 arbitrary values | **992.54 µs** | 97.71 ms | 3.33 ms | 26.55 ms |
655
+
656
+ Warm rebuild — engines held open the way watch mode holds them, every one
657
+ answering from its own cache:
658
+
659
+ | Scenario | ts-css | Tailwind v4 | UnoCSS |
660
+ | --- | ---: | ---: | ---: |
661
+ | Simple utilities (10) | **34.47 ns** | 100.65 ns | 34.66 µs |
662
+ | Full project (~800) | **796.45 ns** | 1.89 µs | 358.63 µs |
663
+
664
+ Style objects vs StyleX, with a correctness gate asserting both emit the same
665
+ number of atomic rules:
666
+
667
+ | Workload | ts-css | StyleX |
668
+ | --- | ---: | ---: |
669
+ | component (8 declarations) | **28.81 µs** | 507.29 µs |
670
+ | design system (200) | **531.81 µs** | 11.05 ms |
671
+
672
+ Full methodology, including the two flaws an earlier revision of the benchmark
673
+ had, is in the [repository README](https://github.com/cwcss/crosswind#performance).
674
+
675
+ ## License
676
+
677
+ MIT