tailwind-oklch 0.3.0 → 0.5.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.
Files changed (4) hide show
  1. package/README.md +47 -17
  2. package/index.css +34 -0
  3. package/package.json +5 -5
  4. package/plugin.js +205 -6
package/README.md CHANGED
@@ -74,20 +74,41 @@ Set one axis at a time. The other two axes inherit from the parent or the root d
74
74
 
75
75
  The same pattern applies to `border-b-*` (border-bottom), `accent-*`, `from-*` (gradient from), `to-*` (gradient to), and `shadow-*`.
76
76
 
77
- ### Shorthand Utilities (all axes at once)
77
+ ### Global Hue and Chroma
78
78
 
79
- The plugin generates shorthands that set all three axes in a single class:
79
+ Most of the time, every color property on an element shares the same hue — the differences are in lightness and chroma. The `hue-*` utility sets the hue for **all** color properties at once:
80
80
 
81
+ ```html
82
+ <!-- Set hue once, vary L and C per property -->
83
+ <div class="hue-danger bg-1-mid text-10-lo border-3-mhi">
84
+ <button class="bg-5-mhi text-0-lo">Acknowledge</button>
85
+ <button class="bg-5-mhi text-10-lo">Cancel</button>
86
+ </div>
81
87
  ```
82
- {property}-{L}-{C}-{H}
88
+
89
+ `chroma-*` does the same for chroma:
90
+
91
+ ```html
92
+ <!-- Everything low-chroma -->
93
+ <div class="hue-primary chroma-lo bg-lc-1 text-lc-fore border-lc-3">
94
+ ```
95
+
96
+ Per-property utilities (`bg-h-*`, `text-c-*`, etc.) still work as overrides when you need one property to differ.
97
+
98
+ ### Shorthand Utilities
99
+
100
+ The plugin generates shorthands for common combinations:
101
+
102
+ **Two-axis: `{property}-{L}-{C}`** — sets luminance and chroma, inherits hue from the cascade (set by `hue-*` or `:root` default):
103
+
104
+ ```html
105
+ <div class="hue-accent bg-3-mhi text-10-lo border-2-mid">
83
106
  ```
84
107
 
85
- Examples:
108
+ **Three-axis: `{property}-{L}-{C}-{H}`** — sets all three axes explicitly in a single class:
86
109
 
87
110
  ```html
88
- <div class="bg-3-mhi-accent"> <!-- L=3, C=medium-high, H=accent -->
89
- <p class="text-fore-lo-neutral"> <!-- L=foreground, C=low, H=neutral -->
90
- <div class="border-5-mid-primary"> <!-- L=5, C=medium, H=primary -->
111
+ <div class="bg-3-mhi-accent text-fore-lo-neutral border-5-mid-primary">
91
112
  ```
92
113
 
93
114
  Available properties: `bg`, `text`, `border`, `border-b`, `accent`, `from`, `to`.
@@ -274,16 +295,25 @@ Override in a `@theme` block:
274
295
 
275
296
  ### Supported Properties
276
297
 
277
- | Prefix | CSS Property | Decomposed | Shorthand |
278
- |---|---|---|---|
279
- | `bg` | `background-color` | `bg-lc-*`, `bg-c-*`, `bg-h-*` | `bg-{L}-{C}-{H}` |
280
- | `text` | `color` | `text-lc-*`, `text-c-*`, `text-h-*` | `text-{L}-{C}-{H}` |
281
- | `border` | `border-color` | `border-lc-*`, `border-c-*`, `border-h-*` | `border-{L}-{C}-{H}` |
282
- | `border-b` | `border-bottom-color` | `border-b-lc-*`, `border-b-c-*`, `border-b-h-*` | `border-b-{L}-{C}-{H}` |
283
- | `accent` | `accent-color` | `accent-lc-*`, `accent-c-*`, `accent-h-*` | `accent-{L}-{C}-{H}` |
284
- | `from` | gradient from | `from-lc-*`, `from-c-*`, `from-h-*` | `from-{L}-{C}-{H}` |
285
- | `to` | gradient to | `to-lc-*`, `to-c-*`, `to-h-*` | `to-{L}-{C}-{H}` |
286
- | `shadow` | shadow color | `shadow-lc-*`, `shadow-c-*`, `shadow-h-*` | |
298
+ **Global context setters** (set all properties at once):
299
+
300
+ | Utility | Sets |
301
+ |---|---|
302
+ | `hue-{H}` | Hue for all properties (`--bg-h`, `--tx-h`, `--bd-h`, etc.) |
303
+ | `chroma-{C}` | Chroma for all properties (`--bg-c`, `--tx-c`, `--bd-c`, etc.) |
304
+
305
+ **Per-property utilities:**
306
+
307
+ | Prefix | CSS Property | Decomposed | 2-axis Shorthand | 3-axis Shorthand |
308
+ |---|---|---|---|---|
309
+ | `bg` | `background-color` | `bg-lc-*`, `bg-c-*`, `bg-h-*` | `bg-{L}-{C}` | `bg-{L}-{C}-{H}` |
310
+ | `text` | `color` | `text-lc-*`, `text-c-*`, `text-h-*` | `text-{L}-{C}` | `text-{L}-{C}-{H}` |
311
+ | `border` | `border-color` | `border-lc-*`, `border-c-*`, `border-h-*` | `border-{L}-{C}` | `border-{L}-{C}-{H}` |
312
+ | `border-b` | `border-bottom-color` | `border-b-lc-*`, `border-b-c-*`, `border-b-h-*` | `border-b-{L}-{C}` | `border-b-{L}-{C}-{H}` |
313
+ | `accent` | `accent-color` | `accent-lc-*`, `accent-c-*`, `accent-h-*` | `accent-{L}-{C}` | `accent-{L}-{C}-{H}` |
314
+ | `from` | gradient from | `from-lc-*`, `from-c-*`, `from-h-*` | `from-{L}-{C}` | `from-{L}-{C}-{H}` |
315
+ | `to` | gradient to | `to-lc-*`, `to-c-*`, `to-h-*` | `to-{L}-{C}` | `to-{L}-{C}-{H}` |
316
+ | `shadow` | shadow color | `shadow-lc-*`, `shadow-c-*`, `shadow-h-*` | — | — |
287
317
 
288
318
  ### Light / Dark Mode
289
319
 
package/index.css CHANGED
@@ -98,6 +98,7 @@
98
98
 
99
99
  :root:not(.dark) {
100
100
  --lc-dir: -1;
101
+ --lc-flip: 0;
101
102
  --lc-range-start: 0.95;
102
103
  --lc-range-end: 0.15;
103
104
 
@@ -127,6 +128,7 @@
127
128
 
128
129
  :root {
129
130
  --lc-dir: 1;
131
+ --lc-flip: 1;
130
132
 
131
133
  --bg-l: var(--l-5);
132
134
  --bg-c: var(--c-lo);
@@ -161,6 +163,38 @@
161
163
  --bdb-h: var(--hue-primary);
162
164
  }
163
165
 
166
+ /* ── Global Hue ─────────────────────────────────────────────────────────
167
+ Set the hue for ALL color properties at once. Per-property hue
168
+ utilities (bg-h-*, text-h-*, etc.) still work as overrides.
169
+ Usage: hue-primary, hue-accent, hue-danger, etc. */
170
+
171
+ @utility hue-* {
172
+ --bg-h: --value(--hue-*);
173
+ --tx-h: --value(--hue-*);
174
+ --bd-h: --value(--hue-*);
175
+ --bdb-h: --value(--hue-*);
176
+ --ac-h: --value(--hue-*);
177
+ --gf-h: --value(--hue-*);
178
+ --gt-h: --value(--hue-*);
179
+ --sh-h: --value(--hue-*);
180
+ }
181
+
182
+ /* ── Global Chroma ──────────────────────────────────────────────────────
183
+ Set the chroma for ALL color properties at once. Per-property chroma
184
+ utilities (bg-c-*, text-c-*, etc.) still work as overrides.
185
+ Usage: chroma-lo, chroma-mid, chroma-hi, etc. */
186
+
187
+ @utility chroma-* {
188
+ --bg-c: --value(--c-*);
189
+ --tx-c: --value(--c-*);
190
+ --bd-c: --value(--c-*);
191
+ --bdb-c: --value(--c-*);
192
+ --ac-c: --value(--c-*);
193
+ --gf-c: --value(--c-*);
194
+ --gt-c: --value(--c-*);
195
+ --sh-c: --value(--c-*);
196
+ }
197
+
164
198
  /* ── Background ──────────────────────────────────────────────────────── */
165
199
 
166
200
  @utility bg-lc-* {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwind-oklch",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "OKLCH color composition system for Tailwind CSS v4",
5
5
  "style": "index.css",
6
6
  "main": "index.css",
@@ -12,9 +12,6 @@
12
12
  "index.css",
13
13
  "plugin.js"
14
14
  ],
15
- "scripts": {
16
- "format": "oxfmt --write \"demo/src/**/*.{ts,tsx,js,mjs}\" \"demo/astro.config.mjs\""
17
- },
18
15
  "peerDependencies": {
19
16
  "tailwindcss": ">=4.0.0"
20
17
  },
@@ -27,5 +24,8 @@
27
24
  "license": "MIT",
28
25
  "devDependencies": {
29
26
  "oxfmt": "^0.32.0"
27
+ },
28
+ "scripts": {
29
+ "format": "oxfmt --write \"demo/src/**/*.{ts,tsx,js,mjs}\" \"demo/astro.config.mjs\""
30
30
  }
31
- }
31
+ }
package/plugin.js CHANGED
@@ -1,17 +1,23 @@
1
1
  /**
2
2
  * tailwind-oklch shorthand generator
3
3
  *
4
- * Generates .{prop}-{L}-{C}-{H} utilities for all combinations of
5
- * the 0–10 luminance contrast scale × chroma × hue stops across bg/text/border.
4
+ * Generates two kinds of shorthand utilities:
6
5
  *
7
- * Each shorthand sets the three axis variables AND applies the
8
- * resolved color, so children can inherit and override single axes
9
- * via decomposed utilities (e.g. hover:bg-lc-8).
6
+ * Three-axis: .{prop}-{L}-{C}-{H} — sets all three axes explicitly
7
+ * e.g. bg-3-mhi-accent
8
+ *
9
+ * Two-axis: .{prop}-{L}-{C} — sets L and C, inherits H from
10
+ * the cascade (set by hue-* or :root default)
11
+ * e.g. bg-3-mhi (pair with hue-accent on a parent)
12
+ *
13
+ * Each shorthand sets the axis variables AND applies the resolved
14
+ * color, so children can inherit and override single axes via
15
+ * decomposed utilities (e.g. hover:bg-lc-8).
10
16
  *
11
17
  * Load via: @plugin "tailwind-oklch/plugin";
12
18
  */
13
19
 
14
- module.exports = function ({ addUtilities }) {
20
+ module.exports = function ({ addUtilities, matchUtilities }) {
15
21
  const luminances = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'base', 'fore', 'none', 'full'];
16
22
  const chromas = ['lo', 'mlo', 'mid', 'mhi', 'hi'];
17
23
  const hues = ['primary', 'accent', 'success', 'warning', 'danger', 'info', 'neutral'];
@@ -28,8 +34,16 @@ module.exports = function ({ addUtilities }) {
28
34
  const utilities = {};
29
35
 
30
36
  for (const prop of properties) {
37
+ // Two-axis shorthands: .{prop}-{L}-{C} — inherits H from cascade
31
38
  for (const l of luminances) {
32
39
  for (const c of chromas) {
40
+ utilities[`.${prop.prefix}-${l}-${c}`] = {
41
+ [prop.vars[0]]: `var(--l-${l})`,
42
+ [prop.vars[1]]: `var(--c-${c})`,
43
+ [prop.css]: `oklch(var(${prop.vars[0]}) var(${prop.vars[1]}) var(${prop.vars[2]}))`,
44
+ };
45
+
46
+ // Three-axis shorthands: .{prop}-{L}-{C}-{H}
33
47
  for (const h of hues) {
34
48
  utilities[`.${prop.prefix}-${l}-${c}-${h}`] = {
35
49
  [prop.vars[0]]: `var(--l-${l})`,
@@ -48,6 +62,21 @@ module.exports = function ({ addUtilities }) {
48
62
 
49
63
  for (const l of luminances) {
50
64
  for (const c of chromas) {
65
+ // Two-axis gradient shorthands — inherit H from cascade
66
+ utilities[`.from-${l}-${c}`] = {
67
+ '--gf-l': `var(--l-${l})`,
68
+ '--gf-c': `var(--c-${c})`,
69
+ '--tw-gradient-from': `oklch(var(--gf-l) var(--gf-c) var(--gf-h))`,
70
+ '--tw-gradient-stops': stopsExpr,
71
+ };
72
+ utilities[`.to-${l}-${c}`] = {
73
+ '--gt-l': `var(--l-${l})`,
74
+ '--gt-c': `var(--c-${c})`,
75
+ '--tw-gradient-to': `oklch(var(--gt-l) var(--gt-c) var(--gt-h))`,
76
+ '--tw-gradient-stops': stopsExpr,
77
+ };
78
+
79
+ // Three-axis gradient shorthands
51
80
  for (const h of hues) {
52
81
  utilities[`.from-${l}-${c}-${h}`] = {
53
82
  '--gf-l': `var(--l-${l})`,
@@ -68,4 +97,174 @@ module.exports = function ({ addUtilities }) {
68
97
  }
69
98
 
70
99
  addUtilities(utilities);
100
+
101
+ // ── Arbitrary hue values ────────────────────────────────────────────────
102
+ // hue-[180] → sets all hue properties to 180 (degrees).
103
+ // bg-h-[280] → sets only background hue to 280.
104
+
105
+ const hueVars = ['--bg-h', '--tx-h', '--bd-h', '--bdb-h', '--ac-h', '--gf-h', '--gt-h', '--sh-h'];
106
+
107
+ matchUtilities(
108
+ {
109
+ hue: (value) => Object.fromEntries(hueVars.map((v) => [v, value])),
110
+ },
111
+ { type: ['integer'] },
112
+ );
113
+
114
+ for (const prop of properties) {
115
+ matchUtilities(
116
+ {
117
+ [`${prop.prefix}-h`]: (value) => ({
118
+ [prop.vars[2]]: value,
119
+ [prop.css]: `oklch(var(${prop.vars[0]}) var(${prop.vars[1]}) var(${prop.vars[2]}))`,
120
+ }),
121
+ },
122
+ { type: ['integer'] },
123
+ );
124
+ }
125
+
126
+ // Gradient from/to arbitrary hue
127
+ matchUtilities(
128
+ {
129
+ 'from-h': (value) => ({
130
+ '--gf-h': value,
131
+ '--tw-gradient-from': 'oklch(var(--gf-l) var(--gf-c) var(--gf-h))',
132
+ '--tw-gradient-stops': stopsExpr,
133
+ }),
134
+ 'to-h': (value) => ({
135
+ '--gt-h': value,
136
+ '--tw-gradient-to': 'oklch(var(--gt-l) var(--gt-c) var(--gt-h))',
137
+ '--tw-gradient-stops': stopsExpr,
138
+ }),
139
+ },
140
+ { type: ['integer'] },
141
+ );
142
+
143
+ // Shadow arbitrary hue
144
+ matchUtilities(
145
+ {
146
+ 'shadow-h': (value) => ({
147
+ '--sh-h': value,
148
+ '--tw-shadow-color': 'oklch(var(--sh-l) var(--sh-c) var(--sh-h))',
149
+ }),
150
+ },
151
+ { type: ['integer'] },
152
+ );
153
+
154
+ // ── Arbitrary chroma values ───────────────────────────────────────────
155
+ // chroma-[15] → sets all chroma properties to 0.15.
156
+ // bg-c-[20] → sets only background chroma to 0.20.
157
+
158
+ const chromaVars = ['--bg-c', '--tx-c', '--bd-c', '--bdb-c', '--ac-c', '--gf-c', '--gt-c', '--sh-c'];
159
+
160
+ const chromaValue = (value) => {
161
+ const v = Number(value) / 100;
162
+ return `${Math.round(v * 1e6) / 1e6}`;
163
+ };
164
+
165
+ matchUtilities(
166
+ {
167
+ chroma: (value) => {
168
+ const c = chromaValue(value);
169
+ return Object.fromEntries(chromaVars.map((v) => [v, c]));
170
+ },
171
+ },
172
+ { type: ['integer'] },
173
+ );
174
+
175
+ for (const prop of properties) {
176
+ matchUtilities(
177
+ {
178
+ [`${prop.prefix}-c`]: (value) => ({
179
+ [prop.vars[1]]: chromaValue(value),
180
+ [prop.css]: `oklch(var(${prop.vars[0]}) var(${prop.vars[1]}) var(${prop.vars[2]}))`,
181
+ }),
182
+ },
183
+ { type: ['integer'] },
184
+ );
185
+ }
186
+
187
+ // Gradient from/to arbitrary chroma
188
+ matchUtilities(
189
+ {
190
+ 'from-c': (value) => ({
191
+ '--gf-c': chromaValue(value),
192
+ '--tw-gradient-from': 'oklch(var(--gf-l) var(--gf-c) var(--gf-h))',
193
+ '--tw-gradient-stops': stopsExpr,
194
+ }),
195
+ 'to-c': (value) => ({
196
+ '--gt-c': chromaValue(value),
197
+ '--tw-gradient-to': 'oklch(var(--gt-l) var(--gt-c) var(--gt-h))',
198
+ '--tw-gradient-stops': stopsExpr,
199
+ }),
200
+ },
201
+ { type: ['integer'] },
202
+ );
203
+
204
+ // Shadow arbitrary chroma
205
+ matchUtilities(
206
+ {
207
+ 'shadow-c': (value) => ({
208
+ '--sh-c': chromaValue(value),
209
+ '--tw-shadow-color': 'oklch(var(--sh-l) var(--sh-c) var(--sh-h))',
210
+ }),
211
+ },
212
+ { type: ['integer'] },
213
+ );
214
+
215
+ // ── Auto-flip luminance for arbitrary values ──────────────────────────
216
+ // bg-lc-[60] → light mode L=0.60, dark mode L=0.40 (simple 1−x flip).
217
+ // Uses --lc-flip (0 in light, 1 in dark) so the transform is pure CSS.
218
+ // Formula: L = v + flip × (1 − 2v) where v = input / 100
219
+ // flip=0 → v (light: use value as-is)
220
+ // flip=1 → 1−v (dark: reflect around 0.5)
221
+
222
+ const lcFlipValue = (value) => {
223
+ const v = Number(value) / 100;
224
+ const delta = 1 - 2 * v;
225
+ // Round to avoid floating-point noise in the CSS output
226
+ const vR = Math.round(v * 1e6) / 1e6;
227
+ const dR = Math.round(delta * 1e6) / 1e6;
228
+ return `calc(${vR} + var(--lc-flip) * ${dR})`;
229
+ };
230
+
231
+ for (const prop of properties) {
232
+ matchUtilities(
233
+ {
234
+ [`${prop.prefix}-lc`]: (value) => ({
235
+ [prop.vars[0]]: lcFlipValue(value),
236
+ [prop.css]: `oklch(var(${prop.vars[0]}) var(${prop.vars[1]}) var(${prop.vars[2]}))`,
237
+ }),
238
+ },
239
+ { type: ['integer'] },
240
+ );
241
+ }
242
+
243
+ // Gradient from/to auto-flip luminance
244
+ matchUtilities(
245
+ {
246
+ 'from-lc': (value) => ({
247
+ '--gf-l': lcFlipValue(value),
248
+ '--tw-gradient-from': 'oklch(var(--gf-l) var(--gf-c) var(--gf-h))',
249
+ '--tw-gradient-stops': stopsExpr,
250
+ }),
251
+ 'to-lc': (value) => ({
252
+ '--gt-l': lcFlipValue(value),
253
+ '--tw-gradient-to': 'oklch(var(--gt-l) var(--gt-c) var(--gt-h))',
254
+ '--tw-gradient-stops': stopsExpr,
255
+ }),
256
+ },
257
+ { type: ['integer'] },
258
+ );
259
+
260
+ // Shadow auto-flip luminance
261
+ matchUtilities(
262
+ {
263
+ 'shadow-lc': (value) => ({
264
+ '--sh-l': lcFlipValue(value),
265
+ '--tw-shadow-color': 'oklch(var(--sh-l) var(--sh-c) var(--sh-h))',
266
+ }),
267
+ },
268
+ { type: ['integer'] },
269
+ );
71
270
  };