teacss 0.2.0-alpha.7 → 0.3.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 CHANGED
@@ -1,6 +1,7 @@
1
- MIT License
1
+ # MIT License
2
2
 
3
- Copyright (c) 2022 Billgo
3
+ Copyright (c) 2021-PRESENT Anthony Fu <https://github.com/antfu>
4
+ Copyright (c) 2026-PRESENT Billgo <hi@billgo.me>
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,3 +1,358 @@
1
- # Teacss
1
+ # TeaCSS
2
2
 
3
- An unocss preset
3
+ **An LLM-friendly atomic CSS toolkit.**
4
+
5
+ Readable utilities for humans. Predictable syntax for AI agents.
6
+
7
+ ## Purpose
8
+
9
+ `teacss` is the application-facing package. Its root entry provides the
10
+ official `cn` and `recipe` runtime helpers plus Standard preset constant
11
+ tables, while its subpaths expose the engine, official presets, and supported
12
+ build integrations.
13
+
14
+ Use it for normal TeaCSS applications. Preset authors, custom integration
15
+ authors, and tooling authors should import the narrower `@teacss/*` packages
16
+ that own those extension APIs; the root entry intentionally does not expose
17
+ low-level factories such as `createDefiner`.
18
+
19
+ The base installation contains the runtime class helpers, engine core, and
20
+ official presets. Build integrations are opt-in peers so installing `teacss`
21
+ does not also install unused Astro, Vite, Rsbuild, or PostCSS tooling. Install
22
+ the matching `@teacss/*` integration package beside `teacss`; the stable
23
+ `teacss/*` subpath then re-exports that package:
24
+
25
+ | Build boundary | Additional installation | Import |
26
+ | --- | --- | --- |
27
+ | Vite | `bun add -d @teacss/vite vite` | `teacss/vite` |
28
+ | Astro | `bun add -d @teacss/astro astro vite` | `teacss/astro` |
29
+ | Rsbuild | `bun add -d @teacss/rsbuild @rsbuild/core` | `teacss/rsbuild` |
30
+ | PostCSS | `bun add -d @teacss/postcss postcss` | `teacss/postcss` |
31
+ | Bun Build | `bun add -d @teacss/bun` | `teacss/bun` |
32
+
33
+ The standalone CLI is intentionally separate because `teacss` has no CLI
34
+ subpath or executable:
35
+
36
+ ```sh
37
+ bun add -d @teacss/cli
38
+ ```
39
+
40
+ TeaCSS is an atomic CSS toolkit designed around a simple idea: utility classes should read like CSS declarations. Its primary audience is large language models, so every language and design tradeoff is judged first by whether a model can reliably predict the output. Instead of compressing meaning into short aliases, TeaCSS uses an explicit `property:value` syntax that remains easy for developers to read and easy for AI agents to generate, validate, and reason about. Predictability takes precedence over brevity.
41
+
42
+ ## Philosophy
43
+
44
+ TeaCSS keeps three principles in view:
45
+
46
+ * **Predictability over brevity.** A model should be able to infer the emitted CSS from the source token.
47
+ * **Clarity over cleverness.** Classes should be readable first, compact only when that does not hide intent.
48
+ * **One model over exceptions.** State, responsive behavior, relations, and arbitrary selectors all use the same trailing `@` condition axis. Write `p:4@hover`, never `hover:p:4`.
49
+
50
+ ## Usage
51
+
52
+ Install or update the optional TeaCSS skill:
53
+
54
+ ```sh
55
+ skills add teacss/skills --skill teacss
56
+ skills update teacss
57
+ ```
58
+
59
+ Restart Codex after installing the skill.
60
+
61
+ Install the application package and the integration used by the project. For
62
+ Vite:
63
+
64
+ ```sh
65
+ bun add teacss
66
+ bun add -d @teacss/vite vite
67
+ ```
68
+
69
+ Use the Vite integration:
70
+
71
+ ```ts
72
+ import { pluginTeacss } from "teacss/vite";
73
+
74
+ export default {
75
+ plugins: [pluginTeacss()],
76
+ };
77
+ ```
78
+
79
+ Create a TeaCSS entry at `index.css` or `src/index.css`:
80
+
81
+ ```css
82
+ @preset "standard";
83
+ @source "./src/**/*.{ts,tsx}";
84
+ @teacss;
85
+ ```
86
+
87
+ Add `@preset "icons";` when the app uses the official icon vocabulary.
88
+ Add `@preset "articles";` for the fixed semantic-descendant utility
89
+ `article:base`:
90
+
91
+ ```html
92
+ <article
93
+ class="article:base max-inline-size:65ch m-a:auto font-size:3x text-color:$color-foreground"
94
+ >
95
+ <h1>Title</h1>
96
+ <p>Readable long-form content.</p>
97
+ </article>
98
+ ```
99
+
100
+ The utility defaults to TeaCSS's shared `shortcuts` layer
101
+ (`LAYER_SHORTCUTS`). Under the default layer order, ordinary utilities in the
102
+ `utilities` layer emit later. The preset does not create an article-specific
103
+ layer.
104
+
105
+ The articles factory is available only from its explicit subpath:
106
+
107
+ ```ts
108
+ import presetArticles, { presetArticles as namedPresetArticles } from "teacss/preset-articles";
109
+ ```
110
+
111
+ The root `teacss` entry does not re-export it.
112
+
113
+ Import the generated stylesheet once from your app entry:
114
+
115
+ ```ts
116
+ import "virtual:teacss.css";
117
+ ```
118
+
119
+ Then write TeaCSS utilities in your markup:
120
+
121
+ ```html
122
+ <button class="p:4 bg-color:red-500 text-color:white bg-color:red-600@hover">
123
+ Submit
124
+ </button>
125
+ ```
126
+
127
+ The root entry also re-exports the Standard preset's constant tables for
128
+ programmatic use:
129
+
130
+ ```ts
131
+ import { breakpointKeywords, primaryColors } from "teacss";
132
+ ```
133
+
134
+ These are the same exports as `@teacss/preset-standard/constants`; there is no
135
+ separate `teacss/constants` subpath.
136
+
137
+ Grouped utilities can share the same condition:
138
+
139
+ ```html
140
+ <button class="{p:4;text-color:white;bg-color:red-500}@hover">
141
+ Submit
142
+ </button>
143
+ ```
144
+
145
+ ## Runtime Recipes
146
+
147
+ The application entry exports `recipe`, bound once to its exact official
148
+ `cn`:
149
+
150
+ ```ts
151
+ import { recipe, type ClassProp, type VariantProps } from "teacss";
152
+
153
+ const badge = recipe({
154
+ styles: "d:inline-flex align-items:center radius:full p-x:2",
155
+ variants: {
156
+ tone: {
157
+ neutral: "bg-color:gray-100 text-color:gray-900",
158
+ accent: "bg-color:blue-500 text-color:white",
159
+ },
160
+ },
161
+ compoundVariants: [
162
+ {
163
+ tone: "accent",
164
+ className: "font-weight:700",
165
+ },
166
+ ],
167
+ });
168
+
169
+ badge({
170
+ tone: "accent",
171
+ className: "p-x:3",
172
+ }); // string
173
+
174
+ type BadgeProps = VariantProps<typeof badge> & ClassProp;
175
+
176
+ function badgeClassName({ className, ...variants }: BadgeProps) {
177
+ return badge({ ...variants, className });
178
+ }
179
+ ```
180
+
181
+ A primitive string `styles` value selects the single-element form. Its variant
182
+ choices and compound outputs are strings; the recipe call accepts variants plus
183
+ `className` and returns the merged string directly.
184
+
185
+ Use a non-empty object `styles` value for multiple elements:
186
+
187
+ ```ts
188
+ const button = recipe({
189
+ styles: {
190
+ container: "d:inline-flex align-items:center p:2 p:4@md",
191
+ icon: "inline-size:4x",
192
+ },
193
+ variants: {
194
+ size: {
195
+ small: {
196
+ container: "p:2",
197
+ icon: "inline-size:3x",
198
+ },
199
+ large: {
200
+ container: "p:4",
201
+ icon: "inline-size:5x",
202
+ },
203
+ },
204
+ disabled: {
205
+ true: {
206
+ container: "opacity:50 pointer-events:none",
207
+ },
208
+ },
209
+ },
210
+ defaultVariants: {
211
+ size: "small",
212
+ disabled: false,
213
+ },
214
+ compoundVariants: [
215
+ {
216
+ size: "large",
217
+ disabled: true,
218
+ className: {
219
+ container: "font-weight:700",
220
+ icon: "inline-size:6x",
221
+ },
222
+ },
223
+ {
224
+ disabled: true,
225
+ parts: ["container", "icon"],
226
+ className: "opacity:80",
227
+ },
228
+ ],
229
+ });
230
+
231
+ const styles = button({
232
+ size: "large",
233
+ });
234
+
235
+ styles.container({
236
+ disabled: true,
237
+ className: "p:6",
238
+ });
239
+ styles.icon();
240
+
241
+ type ButtonProps = VariantProps<typeof button> & ClassProp;
242
+
243
+ function buttonClassName({ className, ...variants }: ButtonProps) {
244
+ return button(variants).container({ className });
245
+ }
246
+ ```
247
+
248
+ There is no distinguished or required `root` key in object `styles`; every
249
+ declared key is an ordinary style name. Variant choices are always target maps.
250
+ A compound either supplies a non-empty `className` target map, or pairs a
251
+ primitive-string `className` with a non-empty `parts` array to broadcast that
252
+ class to several effective named styles. `parts` may name declared or inherited
253
+ styles and must be dense, unique, and free of unknown names. The two compound
254
+ forms are mutually exclusive, and matching entries contribute in declaration
255
+ order. A recipe call returns a readonly resolver for every name.
256
+ Recipe-level selections are captured once. A resolver selection is local to
257
+ that invocation, compounds are re-evaluated for it, and `className` appends only
258
+ to that resolver. The one-sided `"true"` choice makes `disabled`
259
+ boolean-capable: boolean `false` is a branchless state that contributes no
260
+ fragment but can still drive defaults and compounds. Responsive behavior
261
+ remains suffix-only inside static values, such as `p:4@md`; recipe props do not
262
+ accept breakpoint objects.
263
+
264
+ Every definition without `extend` requires its own `styles` field. An extending
265
+ child may omit `styles` to inherit the parent's string or object mode, base
266
+ fragments, and existing named styles. If a child declares `styles`, it must keep
267
+ that mode; an object child may add new names:
268
+
269
+ ```ts
270
+ const primaryButton = recipe({
271
+ extend: button,
272
+ variants: {
273
+ emphasis: {
274
+ strong: {
275
+ container: "font-weight:700",
276
+ },
277
+ },
278
+ },
279
+ });
280
+
281
+ const strongBadge = recipe({
282
+ extend: badge,
283
+ styles: "font-weight:700",
284
+ });
285
+ ```
286
+
287
+ `parts` is reserved as a variant name. Nested `compoundVariants[].parts` is
288
+ supported; the superseded top-level definition field `parts` is not accepted
289
+ as a compatibility alias.
290
+
291
+ `@teacss/classes` exports the factory rather than a pre-bound definer. This
292
+ integration creates its own `recipe` from the official `cn`; custom
293
+ integrations follow the same boundary with their own merger:
294
+
295
+ ```ts
296
+ import { createDefiner, createMerger } from "@teacss/classes";
297
+
298
+ const merge = createMerger({ conflicts, plugins });
299
+ export const recipe = createDefiner({ merge });
300
+ ```
301
+
302
+ `createDefiner` and its type-only `RecipeDefiner` return type are intentionally
303
+ available only from `@teacss/classes`; the named return type keeps declarations
304
+ for exported integration-owned definers compact. Normal recipe libraries
305
+ peer-depend on and externalize `teacss`. A custom integration peer-depends on
306
+ `@teacss/classes` and exports the exact definer used for extendable parents.
307
+ Consumers deduplicate the owning integration. Recreating a definer, loading
308
+ another runtime copy or version, or resolving the parent and definer from
309
+ different provider-package instances breaks extension identity even if the
310
+ declarations and merger are compatible. Deduplicating only `@teacss/classes`
311
+ is insufficient for a duplicated custom-definer provider. `VariantProps`
312
+ extraction does not need runtime identity; `extend` does.
313
+
314
+ ## Runtime Class Merging
315
+
316
+ Use the application runtime merger for component class names. It understands the
317
+ standard vocabulary and canonical `icon:<collection>-<icon-name>` utilities:
318
+
319
+ ```ts
320
+ import { cn } from "teacss";
321
+
322
+ cn("p:4 d:flex", "p:8"); // "d:flex p:8"
323
+ cn("p:4", "p:invalid"); // "p:invalid"
324
+ cn("reset:li", "reset:ul"); // "reset:ul"
325
+ cn("d:block", "reset:li"); // "d:block reset:li"
326
+ ```
327
+
328
+ The default `cn` is one direct merger composed from `pluginStandard` and
329
+ `pluginIcon`; it does not chain preset-local mergers. Resolution is based
330
+ on parsed shape, conditions, importance, and declared footprints. It does not
331
+ validate values, so an unsupported winner may emit no CSS. The supported
332
+ `reset:*` profiles apply to the current element: headings (`reset:h1` through
333
+ `reset:h6`), links and controls
334
+ (`reset:a|button|select|input-text|textarea`), and lists
335
+ (`reset:li|ol|ul|menu`). `reset:input-text` is only for text-like inputs; there
336
+ is no generic `reset:input`. Profiles replace one another as a single opaque
337
+ family and never delete an unrelated utility. Document-root and shadow-host
338
+ defaults come from the standard preset's automatic preflight.
339
+
340
+ Generic class merging is a separate package for custom conflict metadata:
341
+
342
+ ```sh
343
+ bun add @teacss/classes
344
+ ```
345
+
346
+ ```ts
347
+ import { createMerger } from "@teacss/classes";
348
+ ```
349
+
350
+ ## Status
351
+
352
+ TeaCSS is currently pre-1.0 and under active development.
353
+
354
+ Syntax, configuration, package boundaries, and public APIs may change before the first stable release.
355
+
356
+ ## License
357
+
358
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1 @@
1
+ export * from "@teacss/astro";
package/dist/astro.js ADDED
@@ -0,0 +1 @@
1
+ export*from"@teacss/astro";export{};
package/dist/bun.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "@teacss/bun";
package/dist/bun.js ADDED
@@ -0,0 +1 @@
1
+ export*from"@teacss/bun";export{};
package/dist/core.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { Preset, Rule, UserConfig, Variant, createGenerator } from "@teacss/core";
2
+ export { type Preset, type Rule, type UserConfig, type Variant, createGenerator };
package/dist/core.js ADDED
@@ -0,0 +1 @@
1
+ import{createGenerator as e}from"@teacss/core";export{e as createGenerator};
package/dist/index.d.ts CHANGED
@@ -1,39 +1,7 @@
1
- export { default as cn } from 'classnames';
2
- import * as CLSX from 'clsx';
3
- import { clsx } from 'clsx';
4
-
5
- type ClassValue = CLSX.ClassValue;
6
- type ClassProp = {
7
- class: ClassValue;
8
- className?: never;
9
- } | {
10
- class?: never;
11
- className: ClassValue;
12
- } | {
13
- class?: never;
14
- className?: never;
15
- };
16
- type OmitUndefined<T> = T extends undefined ? never : T;
17
- type StringToBoolean<T> = T extends "true" | "false" ? boolean : T;
18
-
19
- declare function cxm(...inputs: ClassValue[]): string;
20
- type VariantProps<Component extends (...args: any) => any> = Omit<OmitUndefined<Parameters<Component>[0]>, "class" | "className">;
21
- type CxOptions = Parameters<typeof clsx>;
22
- type CxReturn = ReturnType<typeof clsx>;
23
- declare const cx: typeof clsx;
24
- type ConfigSchema = Record<string, Record<string, ClassValue>>;
25
- type ConfigVariants<T extends ConfigSchema> = {
26
- [Variant in keyof T]?: StringToBoolean<keyof T[Variant]> | null | undefined;
27
- };
28
- type ConfigVariantsMulti<T extends ConfigSchema> = {
29
- [Variant in keyof T]?: StringToBoolean<keyof T[Variant]> | StringToBoolean<keyof T[Variant]>[] | undefined;
30
- };
31
- type Config<T> = T extends ConfigSchema ? {
32
- variants?: T;
33
- defaultVariants?: ConfigVariants<T>;
34
- compoundVariants?: (T extends ConfigSchema ? (ConfigVariants<T> | ConfigVariantsMulti<T>) & ClassProp : ClassProp)[];
35
- } : never;
36
- type Props<T> = T extends ConfigSchema ? ConfigVariants<T> & ClassProp : ClassProp;
37
- declare const cva: <T>(base?: ClassValue, config?: Config<T>) => (props?: Props<T>) => string;
38
-
39
- export { type CxOptions, type CxReturn, type VariantProps, cva, cx, cxm };
1
+ import { ClassProp, Merger, RecipeDefiner, VariantProps } from "@teacss/classes";
2
+ export * from "@teacss/preset-standard/constants";
3
+ //#region src/index.d.ts
4
+ declare const cn: Merger;
5
+ declare const recipe: RecipeDefiner;
6
+ //#endregion
7
+ export { type ClassProp, type VariantProps, cn, recipe };