@djangocfg/ui-core 2.1.432 → 2.1.433
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/package.json +4 -4
- package/src/styles/README.md +10 -0
- package/src/theme/ForceTheme.tsx +49 -78
- package/src/theme/README.md +85 -0
- package/src/theme/TROUBLESHOOTING.md +89 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.433",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"check": "tsc --noEmit"
|
|
107
107
|
},
|
|
108
108
|
"peerDependencies": {
|
|
109
|
-
"@djangocfg/i18n": "^2.1.
|
|
109
|
+
"@djangocfg/i18n": "^2.1.433",
|
|
110
110
|
"consola": "^3.4.2",
|
|
111
111
|
"lucide-react": "^0.545.0",
|
|
112
112
|
"moment": "^2.30.1",
|
|
@@ -180,8 +180,8 @@
|
|
|
180
180
|
"@chenglou/pretext": "*"
|
|
181
181
|
},
|
|
182
182
|
"devDependencies": {
|
|
183
|
-
"@djangocfg/i18n": "^2.1.
|
|
184
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
183
|
+
"@djangocfg/i18n": "^2.1.433",
|
|
184
|
+
"@djangocfg/typescript-config": "^2.1.433",
|
|
185
185
|
"@types/node": "^25.2.3",
|
|
186
186
|
"@types/react": "^19.2.15",
|
|
187
187
|
"@types/react-dom": "^19.2.3",
|
package/src/styles/README.md
CHANGED
|
@@ -42,6 +42,16 @@ This makes opacity modifiers (`bg-card/40`, `border-foreground/20`) resolve thro
|
|
|
42
42
|
|
|
43
43
|
> **Do NOT wrap tokens in `hsl(var(--X))`.** Tokens are already full colors, so `hsl(hsl(...))` is invalid and falls back to the default. Use `var(--X)` or `color-mix(in oklab, var(--X) N%, transparent)` for manual opacity.
|
|
44
44
|
|
|
45
|
+
> **Do NOT set a token to a bare HSL triplet.** The utilities read the token
|
|
46
|
+
> **raw** (`.bg-muted { background-color: var(--muted) }`), so `--muted: 0 0% 10%`
|
|
47
|
+
> resolves to the *string* `"0 0% 10%"` — not a color — and the declaration is
|
|
48
|
+
> silently dropped: **transparent fills + white-fallback borders.** Always write
|
|
49
|
+
> the full color: `--muted: hsl(0 0% 10%)`. This bites most often when a component
|
|
50
|
+
> overrides tokens **inline** (e.g. an old `ForceTheme` wrapper). If part of a page
|
|
51
|
+
> has vanished backgrounds / white borders, that's this — see
|
|
52
|
+
> [`../theme/TROUBLESHOOTING.md`](../theme/TROUBLESHOOTING.md) and prefer
|
|
53
|
+
> `ThemeOverride` over inline token maps.
|
|
54
|
+
|
|
45
55
|
## Semantic tokens — never use raw color scales
|
|
46
56
|
|
|
47
57
|
**Rule:** never write `bg-amber-500`, `text-green-700`, `border-gray-200`. Use semantic tokens — they adapt to both themes and to whatever preset is active.
|
package/src/theme/ForceTheme.tsx
CHANGED
|
@@ -18,88 +18,59 @@ interface ForceThemeProps {
|
|
|
18
18
|
className?: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Token contract (ui-core Tailwind v4): base tokens are **full CSS colors**,
|
|
23
|
+
* never bare HSL triplets. The utilities consume them raw — `.bg-muted {
|
|
24
|
+
* background-color: var(--muted) }` — so `--muted: 0 0% 10%` (a triplet) is an
|
|
25
|
+
* invalid color and the declaration is dropped (transparent fills, white
|
|
26
|
+
* borders). Every value here is therefore wrapped in `hsl(...)`. The separate
|
|
27
|
+
* `--color-*` block is intentionally gone: ui-core's `@theme inline` already
|
|
28
|
+
* maps `--color-X: var(--X)`, so re-declaring it here is redundant and was the
|
|
29
|
+
* only reason this component half-worked before. See ui-core styles README
|
|
30
|
+
* §"Token format".
|
|
31
|
+
*/
|
|
22
32
|
const darkThemeVars = {
|
|
23
|
-
|
|
24
|
-
'--
|
|
25
|
-
'--
|
|
26
|
-
'--card': '0 0%
|
|
27
|
-
'--
|
|
28
|
-
'--popover': '0 0%
|
|
29
|
-
'--
|
|
30
|
-
'--primary': '
|
|
31
|
-
'--
|
|
32
|
-
'--secondary': '0 0%
|
|
33
|
-
'--
|
|
34
|
-
'--muted': '0 0%
|
|
35
|
-
'--
|
|
36
|
-
'--accent': '0 0%
|
|
37
|
-
'--
|
|
38
|
-
'--destructive': '0
|
|
39
|
-
'--
|
|
40
|
-
'--
|
|
41
|
-
'--
|
|
42
|
-
'--ring': '217 91% 60%',
|
|
43
|
-
// Tailwind color tokens (used by bg-*, text-*, etc)
|
|
44
|
-
'--color-background': 'hsl(0 0% 4%)',
|
|
45
|
-
'--color-foreground': 'hsl(0 0% 98%)',
|
|
46
|
-
'--color-card': 'hsl(0 0% 8%)',
|
|
47
|
-
'--color-card-foreground': 'hsl(0 0% 98%)',
|
|
48
|
-
'--color-primary': 'hsl(217 91% 60%)',
|
|
49
|
-
'--color-primary-foreground': 'hsl(0 0% 100%)',
|
|
50
|
-
'--color-secondary': 'hsl(0 0% 98%)',
|
|
51
|
-
'--color-secondary-foreground': 'hsl(0 0% 9%)',
|
|
52
|
-
'--color-muted': 'hsl(0 0% 10%)',
|
|
53
|
-
'--color-muted-foreground': 'hsl(0 0% 60%)',
|
|
54
|
-
'--color-accent': 'hsl(0 0% 15%)',
|
|
55
|
-
'--color-accent-foreground': 'hsl(0 0% 98%)',
|
|
56
|
-
'--color-destructive': 'hsl(0 84% 60%)',
|
|
57
|
-
'--color-destructive-foreground': 'hsl(0 0% 98%)',
|
|
58
|
-
'--color-border': 'hsl(0 0% 15%)',
|
|
59
|
-
'--color-input': 'hsl(0 0% 15%)',
|
|
60
|
-
'--color-ring': 'hsl(217 91% 60%)',
|
|
33
|
+
'--background': 'hsl(0 0% 4%)',
|
|
34
|
+
'--foreground': 'hsl(0 0% 98%)',
|
|
35
|
+
'--card': 'hsl(0 0% 8%)',
|
|
36
|
+
'--card-foreground': 'hsl(0 0% 98%)',
|
|
37
|
+
'--popover': 'hsl(0 0% 12%)',
|
|
38
|
+
'--popover-foreground': 'hsl(0 0% 98%)',
|
|
39
|
+
'--primary': 'hsl(217 91% 60%)',
|
|
40
|
+
'--primary-foreground': 'hsl(0 0% 100%)',
|
|
41
|
+
'--secondary': 'hsl(0 0% 98%)',
|
|
42
|
+
'--secondary-foreground': 'hsl(0 0% 9%)',
|
|
43
|
+
'--muted': 'hsl(0 0% 10%)',
|
|
44
|
+
'--muted-foreground': 'hsl(0 0% 60%)',
|
|
45
|
+
'--accent': 'hsl(0 0% 15%)',
|
|
46
|
+
'--accent-foreground': 'hsl(0 0% 98%)',
|
|
47
|
+
'--destructive': 'hsl(0 84% 60%)',
|
|
48
|
+
'--destructive-foreground': 'hsl(0 0% 98%)',
|
|
49
|
+
'--border': 'hsl(0 0% 15%)',
|
|
50
|
+
'--input': 'hsl(0 0% 15%)',
|
|
51
|
+
'--ring': 'hsl(217 91% 60%)',
|
|
61
52
|
} as React.CSSProperties;
|
|
62
53
|
|
|
63
|
-
// Light theme CSS variables
|
|
64
54
|
const lightThemeVars = {
|
|
65
|
-
|
|
66
|
-
'--
|
|
67
|
-
'--
|
|
68
|
-
'--card': '0 0%
|
|
69
|
-
'--
|
|
70
|
-
'--popover': '0 0%
|
|
71
|
-
'--
|
|
72
|
-
'--primary': '
|
|
73
|
-
'--
|
|
74
|
-
'--secondary': '0 0%
|
|
75
|
-
'--
|
|
76
|
-
'--muted': '0 0%
|
|
77
|
-
'--
|
|
78
|
-
'--accent': '0 0%
|
|
79
|
-
'--
|
|
80
|
-
'--destructive': '0
|
|
81
|
-
'--
|
|
82
|
-
'--
|
|
83
|
-
'--
|
|
84
|
-
'--ring': '217 91% 60%',
|
|
85
|
-
// Tailwind color tokens (used by bg-*, text-*, etc)
|
|
86
|
-
'--color-background': 'hsl(0 0% 96%)',
|
|
87
|
-
'--color-foreground': 'hsl(0 0% 9%)',
|
|
88
|
-
'--color-card': 'hsl(0 0% 100%)',
|
|
89
|
-
'--color-card-foreground': 'hsl(0 0% 9%)',
|
|
90
|
-
'--color-primary': 'hsl(217 91% 60%)',
|
|
91
|
-
'--color-primary-foreground': 'hsl(0 0% 100%)',
|
|
92
|
-
'--color-secondary': 'hsl(0 0% 9%)',
|
|
93
|
-
'--color-secondary-foreground': 'hsl(0 0% 98%)',
|
|
94
|
-
'--color-muted': 'hsl(0 0% 96%)',
|
|
95
|
-
'--color-muted-foreground': 'hsl(0 0% 40%)',
|
|
96
|
-
'--color-accent': 'hsl(0 0% 92%)',
|
|
97
|
-
'--color-accent-foreground': 'hsl(0 0% 9%)',
|
|
98
|
-
'--color-destructive': 'hsl(0 84% 60%)',
|
|
99
|
-
'--color-destructive-foreground': 'hsl(0 0% 98%)',
|
|
100
|
-
'--color-border': 'hsl(0 0% 90%)',
|
|
101
|
-
'--color-input': 'hsl(0 0% 90%)',
|
|
102
|
-
'--color-ring': 'hsl(217 91% 60%)',
|
|
55
|
+
'--background': 'hsl(0 0% 96%)',
|
|
56
|
+
'--foreground': 'hsl(0 0% 9%)',
|
|
57
|
+
'--card': 'hsl(0 0% 100%)',
|
|
58
|
+
'--card-foreground': 'hsl(0 0% 9%)',
|
|
59
|
+
'--popover': 'hsl(0 0% 100%)',
|
|
60
|
+
'--popover-foreground': 'hsl(0 0% 9%)',
|
|
61
|
+
'--primary': 'hsl(217 91% 60%)',
|
|
62
|
+
'--primary-foreground': 'hsl(0 0% 100%)',
|
|
63
|
+
'--secondary': 'hsl(0 0% 9%)',
|
|
64
|
+
'--secondary-foreground': 'hsl(0 0% 98%)',
|
|
65
|
+
'--muted': 'hsl(0 0% 96%)',
|
|
66
|
+
'--muted-foreground': 'hsl(0 0% 40%)',
|
|
67
|
+
'--accent': 'hsl(0 0% 92%)',
|
|
68
|
+
'--accent-foreground': 'hsl(0 0% 9%)',
|
|
69
|
+
'--destructive': 'hsl(0 84% 60%)',
|
|
70
|
+
'--destructive-foreground': 'hsl(0 0% 98%)',
|
|
71
|
+
'--border': 'hsl(0 0% 90%)',
|
|
72
|
+
'--input': 'hsl(0 0% 90%)',
|
|
73
|
+
'--ring': 'hsl(217 91% 60%)',
|
|
103
74
|
} as React.CSSProperties;
|
|
104
75
|
|
|
105
76
|
export function ForceTheme({ theme, children, className }: ForceThemeProps) {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Theme — providers, switches, and forced themes
|
|
2
|
+
|
|
3
|
+
The runtime side of the design system. `styles/` ships the **tokens** (CSS
|
|
4
|
+
variables) and the Tailwind utilities that read them; `theme/` is the **React**
|
|
5
|
+
layer that decides *which* token set is live (light / dark / a preset) and lets
|
|
6
|
+
you switch or pin it.
|
|
7
|
+
|
|
8
|
+
> Read [`../styles/README.md`](../styles/README.md) first for the **token
|
|
9
|
+
> contract** — it is load-bearing for everything here. The one rule that bites:
|
|
10
|
+
> **base tokens are full CSS colors (`hsl(...)`), never bare triplets.**
|
|
11
|
+
|
|
12
|
+
## Exports
|
|
13
|
+
|
|
14
|
+
| Export | What it does |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `ThemeProvider` / `useThemeContext` | Wraps `next-themes`. Owns the `html.dark` class + the user's light/dark/system choice. Mount once at the app root (via `BaseApp` in `@djangocfg/layouts`, which mounts it for you). |
|
|
17
|
+
| `ThemeToggle` | A light/dark toggle button. |
|
|
18
|
+
| `ThemeSegmented` | A segmented light/dark/system control. |
|
|
19
|
+
| `ThemeOverride` | **Pathname-aware forced theme.** Mutates the real `next-themes` value while the route matches a rule, restores the user's pick when it leaves. Globs (`*`, `**`) supported. This is the **correct** way to force a route's theme. |
|
|
20
|
+
| `resolveForcedTheme` | Pure helper — first matching rule → forced theme (or `null`). Pair with `ForcedThemeProvider`. |
|
|
21
|
+
| `ForcedThemeProvider` / `useForcedTheme` | Lets descendants read the currently-forced theme. |
|
|
22
|
+
| `ForceTheme` | **Scoped, inline-var theme for a subtree.** A `<div class={theme}>` that re-declares the token vars inline. Use sparingly — see the trap below. |
|
|
23
|
+
|
|
24
|
+
## Choosing: `ThemeOverride` vs `ForceTheme`
|
|
25
|
+
|
|
26
|
+
They look similar; they are not interchangeable.
|
|
27
|
+
|
|
28
|
+
| | `ThemeOverride` (preferred) | `ForceTheme` (last resort) |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| How | mutates the real `next-themes` value → toggles `html.dark` | wraps children in `<div class={theme} style={inline vars}>` |
|
|
31
|
+
| Scope | the **whole app** while the route matches | just that **subtree** |
|
|
32
|
+
| Tokens | the real preset/`theme.css` tokens cascade — **always valid** | re-declares a hardcoded token map inline |
|
|
33
|
+
| Active preset | **respected** (macos/ios/django-cfg/…) | **ignored** — ships its own generic palette |
|
|
34
|
+
| Risk | none | bare-triplet trap (below); also overrides your brand accent |
|
|
35
|
+
|
|
36
|
+
**Default to `ThemeOverride`** for "this route is always dark" (the common case —
|
|
37
|
+
a marketing landing, a docs section). It's how the cmdop site forces its homepage
|
|
38
|
+
dark:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// in the app shell (RootAppLayout)
|
|
42
|
+
<BaseApp theme={{ defaultTheme: 'dark' }}>
|
|
43
|
+
<ThemeOverride pathname={pathnameWithoutLocale} rules={[{ path: '/', theme: 'dark' }]} />
|
|
44
|
+
{children}
|
|
45
|
+
</BaseApp>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Reach for `ForceTheme` **only** when you need a single subtree in the opposite
|
|
49
|
+
theme of the rest of the page (e.g. a dark preview card on a light page) and you
|
|
50
|
+
cannot drive it through the router.
|
|
51
|
+
|
|
52
|
+
## ⚠️ The `ForceTheme` bare-triplet trap
|
|
53
|
+
|
|
54
|
+
`ForceTheme` re-declares tokens inline. Under the **Tailwind v4 token contract**,
|
|
55
|
+
base tokens are **full colors** and the utilities read them raw:
|
|
56
|
+
|
|
57
|
+
```css
|
|
58
|
+
.bg-muted { background-color: var(--muted); }
|
|
59
|
+
.border-border { border-color: var(--border); }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
So a token set to a **bare HSL triplet** is a broken color:
|
|
63
|
+
|
|
64
|
+
```css
|
|
65
|
+
--muted: 0 0% 10%; /* ❌ var(--muted) → "0 0% 10%" → not a color → declaration dropped */
|
|
66
|
+
--muted: hsl(0 0% 10%);/* ✅ valid color */
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The failure is **silent and confusing**: inside the `ForceTheme` subtree
|
|
70
|
+
`bg-muted` renders **transparent** and `border-border` renders the inherited
|
|
71
|
+
fallback (≈ white), while `border-divider` (a token `ForceTheme` doesn't
|
|
72
|
+
re-declare) still works — so it looks like "random borders are white and some
|
|
73
|
+
fills vanished." It is NOT a Tailwind content-scan problem; the utilities exist,
|
|
74
|
+
their *input* is invalid. Full write-up + how to diagnose:
|
|
75
|
+
[`TROUBLESHOOTING.md`](TROUBLESHOOTING.md).
|
|
76
|
+
|
|
77
|
+
`ForceTheme`'s token map is wrapped in `hsl(...)` as of this version, so the trap
|
|
78
|
+
is closed for the values it ships. But the lesson stands for **any** inline token
|
|
79
|
+
override you write: wrap in `hsl(...)`, or prefer `ThemeOverride` so the real
|
|
80
|
+
(already-valid) preset tokens cascade and you never hand-maintain a palette.
|
|
81
|
+
|
|
82
|
+
## Maintenance rule
|
|
83
|
+
|
|
84
|
+
After changing a component or the token map here, update this README and bump the
|
|
85
|
+
package patch version (consumers pin npm versions; this file is the changelog).
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Theme & token troubleshooting
|
|
2
|
+
|
|
3
|
+
Symptom-first. Each entry: what you see → why → the fix.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## "Random white borders + transparent fills on part of the page"
|
|
8
|
+
|
|
9
|
+
### What you see
|
|
10
|
+
- Some surfaces render with **no background** (`bg-muted` / `bg-card` / a custom
|
|
11
|
+
`bg-bubble-*` come out transparent).
|
|
12
|
+
- Some borders render **bright white / light grey** instead of the hairline token
|
|
13
|
+
— `border-border` looks white, but `border-divider` next to it looks correct.
|
|
14
|
+
- It only happens in **one region** of the page (often a marketing landing or a
|
|
15
|
+
card), not the whole app. A sibling app/route with the same components is fine.
|
|
16
|
+
|
|
17
|
+
### Why (the actual mechanism)
|
|
18
|
+
A subtree is wrapped in **`<ForceTheme>`** (or any hand-written element that sets
|
|
19
|
+
token CSS vars inline) where a token is declared as a **bare HSL triplet**:
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
--muted: 0 0% 10%; /* ❌ */
|
|
23
|
+
--border: 0 0% 15%; /* ❌ */
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
ui-core's utilities consume tokens **raw** (`@theme inline` maps
|
|
27
|
+
`--color-muted: var(--muted)`, and `.bg-muted { background-color: var(--muted) }`).
|
|
28
|
+
So `var(--muted)` resolves to the **string** `"0 0% 10%"`, which is not a valid
|
|
29
|
+
color → the browser **drops the declaration**:
|
|
30
|
+
|
|
31
|
+
- `background-color` drop → falls back to `transparent`.
|
|
32
|
+
- `border-color` drop → falls back to the inherited `currentColor` (≈ the light
|
|
33
|
+
text color on a dark UI → "white border").
|
|
34
|
+
|
|
35
|
+
`border-divider` keeps working because that subtree doesn't *re-declare*
|
|
36
|
+
`--divider`, so it still inherits the valid preset value.
|
|
37
|
+
|
|
38
|
+
**This is NOT a Tailwind content-scan / `@source` problem.** The `.bg-muted`
|
|
39
|
+
rule exists in the built CSS; its *input variable* is the broken thing.
|
|
40
|
+
|
|
41
|
+
### How to confirm (60 seconds in the browser console)
|
|
42
|
+
```js
|
|
43
|
+
// 1) is the utility rule transparent on a real element?
|
|
44
|
+
getComputedStyle($0).backgroundColor // rgba(0,0,0,0) on a bg-muted node = bug
|
|
45
|
+
|
|
46
|
+
// 2) is there a ForceTheme div with bare-triplet vars above it?
|
|
47
|
+
[...document.querySelectorAll('div[style*="--muted"]')]
|
|
48
|
+
.map(d => d.style.getPropertyValue('--muted')) // "0 0% 10%" (no hsl) = the culprit
|
|
49
|
+
|
|
50
|
+
// 3) sanity: a probe OUTSIDE the subtree works
|
|
51
|
+
const p = document.createElement('div'); p.className='bg-muted';
|
|
52
|
+
document.body.appendChild(p);
|
|
53
|
+
getComputedStyle(p).backgroundColor // rgb(57,57,60) → proves the utility is fine
|
|
54
|
+
p.remove();
|
|
55
|
+
```
|
|
56
|
+
If (1) is transparent but (3) is a real color, the input var is broken, not the
|
|
57
|
+
utility — look up the tree for an inline token override.
|
|
58
|
+
|
|
59
|
+
### Fix (in order of preference)
|
|
60
|
+
1. **Stop forcing the theme with `ForceTheme`.** If the goal was just "this route
|
|
61
|
+
is dark", drive it through the router with **`ThemeOverride`** in the app shell
|
|
62
|
+
(`rules={[{ path: '/', theme: 'dark' }]}`) and delete the wrapper. The real
|
|
63
|
+
preset tokens (already valid `hsl(...)`) cascade and the bug is gone. This is
|
|
64
|
+
the right fix 90% of the time.
|
|
65
|
+
2. **If you genuinely need `ForceTheme`** (a subtree in the opposite theme), make
|
|
66
|
+
sure every inline token value is a **full color**: `--muted: hsl(0 0% 10%)`,
|
|
67
|
+
not `0 0% 10%`. ui-core's shipped `ForceTheme` already does this.
|
|
68
|
+
3. **Never** "fix" it by adding plain `.bg-x { background: var(--x) }` overrides in
|
|
69
|
+
the app's `globals.css` — that masks the broken input and rots.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## "ALL utilities missing (only @theme vars load)" — a different bug
|
|
74
|
+
|
|
75
|
+
If `bg-*` / `border-*` are missing **everywhere** (not just one subtree) while the
|
|
76
|
+
`--token` variables are present, that IS a content-scan problem, not this one:
|
|
77
|
+
Tailwind v4 didn't scan your app source. See
|
|
78
|
+
[`../styles/README.md`](../styles/README.md) §"Why `@source` is required" — add
|
|
79
|
+
`@source "../../app"` (path relative to the CSS file) or check the app dir isn't
|
|
80
|
+
`.gitignore`d. Distinguish the two: **scan bug = global**, **token bug = one
|
|
81
|
+
subtree under an inline override.**
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## "`hsl(var(--x))` shows the default / no color"
|
|
86
|
+
|
|
87
|
+
Tokens are already full colors. `hsl(hsl(0 0% 10%))` is invalid. Use
|
|
88
|
+
`var(--x)` directly, or `color-mix(in oklab, var(--x) N%, transparent)` for
|
|
89
|
+
opacity. See `styles/README.md` §"Token format".
|