@navecss/core 0.1.1 → 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/ATOMS.md +23 -23
- package/README.md +76 -7
- package/dist/cx.d.ts +5 -4
- package/dist/postcss.d.ts +7 -0
- package/dist/postcss.js +145 -59
- package/nave.css-data.json +12 -0
- package/package.json +8 -5
- package/skills/navecss/SKILL.md +314 -0
package/ATOMS.md
CHANGED
|
@@ -51,24 +51,24 @@ variants, if any.
|
|
|
51
51
|
|
|
52
52
|
## Typography
|
|
53
53
|
|
|
54
|
-
| Atom | Class | Declarations | Variants
|
|
55
|
-
| ----------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
-
| `truncate` | `nave-truncate` | `overflow: hidden;`<br>`text-overflow: ellipsis;`<br>`white-space: nowrap;` | —
|
|
57
|
-
| `srOnly` | `nave-sr-only` | `position: absolute;`<br>`width: 1px;`<br>`height: 1px;`<br>`padding: 0;`<br>`margin: -1px;`<br>`overflow: hidden;`<br>`clip-path: inset(50%);`<br>`white-space: nowrap;`<br>`border-width: 0;` | —
|
|
58
|
-
| `srOnlyFocusable` | `nave-sr-only-focusable` | `position: absolute;`<br>`width: 1px;`<br>`height: 1px;`<br>`padding: 0;`<br>`margin: -1px;`<br>`overflow: hidden;`<br>`clip-path: inset(50%);`<br>`white-space: nowrap;`<br>`border-width: 0;` | `:focus-visible
|
|
59
|
-
| `noWrap` | `nave-no-wrap` | `white-space: nowrap;` | —
|
|
60
|
-
| `breakWord` | `nave-break-word` | `overflow-wrap: break-word;` | —
|
|
61
|
-
| `textLeft` | `nave-text-left` | `text-align: left;` | —
|
|
62
|
-
| `textCenter` | `nave-text-center` | `text-align: center;` | —
|
|
63
|
-
| `textRight` | `nave-text-right` | `text-align: right;` | —
|
|
54
|
+
| Atom | Class | Declarations | Variants |
|
|
55
|
+
| ----------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
+
| `truncate` | `nave-truncate` | `overflow: hidden;`<br>`text-overflow: ellipsis;`<br>`white-space: nowrap;` | — |
|
|
57
|
+
| `srOnly` | `nave-sr-only` | `position: absolute;`<br>`width: 1px;`<br>`height: 1px;`<br>`padding: 0;`<br>`margin: -1px;`<br>`overflow: hidden;`<br>`clip-path: inset(50%);`<br>`white-space: nowrap;`<br>`border-width: 0;` | — |
|
|
58
|
+
| `srOnlyFocusable` | `nave-sr-only-focusable` | `position: absolute;`<br>`width: 1px;`<br>`height: 1px;`<br>`padding: 0;`<br>`margin: -1px;`<br>`overflow: hidden;`<br>`clip-path: inset(50%);`<br>`white-space: nowrap;`<br>`border-width: 0;` | `:focus-visible` — `position: static;`<br>`width: auto;`<br>`height: auto;`<br>`margin: 0;`<br>`overflow: visible;`<br>`clip-path: none;`<br>`white-space: normal;`<br>`:focus-within` — `position: static;`<br>`width: auto;`<br>`height: auto;`<br>`margin: 0;`<br>`overflow: visible;`<br>`clip-path: none;`<br>`white-space: normal;` |
|
|
59
|
+
| `noWrap` | `nave-no-wrap` | `white-space: nowrap;` | — |
|
|
60
|
+
| `breakWord` | `nave-break-word` | `overflow-wrap: break-word;` | — |
|
|
61
|
+
| `textLeft` | `nave-text-left` | `text-align: left;` | — |
|
|
62
|
+
| `textCenter` | `nave-text-center` | `text-align: center;` | — |
|
|
63
|
+
| `textRight` | `nave-text-right` | `text-align: right;` | — |
|
|
64
64
|
|
|
65
65
|
## Interaction
|
|
66
66
|
|
|
67
|
-
| Atom | Class | Declarations | Variants
|
|
68
|
-
| --------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
69
|
-
| `interactive` | `nave-interactive` | `cursor: pointer;`<br>`-webkit-user-select: none;`<br>`user-select: none;`<br>`-webkit-tap-highlight-color: transparent;` | —
|
|
70
|
-
| `focusRing` | `nave-focus-ring` | `outline: none;` | `:focus-visible`
|
|
71
|
-
| `disabledState` | `nave-disabled-state` | — | `:disabled, [aria-disabled="true"]` |
|
|
67
|
+
| Atom | Class | Declarations | Variants |
|
|
68
|
+
| --------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
69
|
+
| `interactive` | `nave-interactive` | `cursor: pointer;`<br>`-webkit-user-select: none;`<br>`user-select: none;`<br>`-webkit-tap-highlight-color: transparent;` | — |
|
|
70
|
+
| `focusRing` | `nave-focus-ring` | `outline: none;` | `:focus-visible` — `outline: var(--nave-border-width-focus) solid var(--nave-color-border-focus);`<br>`outline-offset: 2px;` |
|
|
71
|
+
| `disabledState` | `nave-disabled-state` | — | `:disabled, [aria-disabled="true"]` — `color: var(--nave-color-content-disabled);`<br>`border-color: var(--nave-color-border-disabled);`<br>`pointer-events: none;`<br>On the aria-disabled branch the element stays focusable by design: this atom only blocks pointer activation (pointer-events: none), so the component's own activation handler must also check the attribute and no-op on Enter and Space, since CSS cannot prevent keyboard activation. |
|
|
72
72
|
|
|
73
73
|
## Visual
|
|
74
74
|
|
|
@@ -95,14 +95,14 @@ variants, if any.
|
|
|
95
95
|
|
|
96
96
|
## Responsive
|
|
97
97
|
|
|
98
|
-
| Atom | Class | Declarations | Variants
|
|
99
|
-
| ----------------------- | ------------------------------- | ------------ |
|
|
100
|
-
| `hidePhoneOnly` | `nave-hide-phone-only` | — | `@media (width < 37.5em)`
|
|
101
|
-
| `hideTabletPortraitUp` | `nave-hide-tablet-portrait-up` | — | `@media (width >= 37.5em)`
|
|
102
|
-
| `hideTabletLandscapeUp` | `nave-hide-tablet-landscape-up` | — | `@media (width >= 56.25em)` |
|
|
103
|
-
| `hideDesktopUp` | `nave-hide-desktop-up` | — | `@media (width >= 75em)`
|
|
104
|
-
| `stackPhoneOnly` | `nave-stack-phone-only` | — | `@media (width < 37.5em)`
|
|
105
|
-
| `wFullPhoneOnly` | `nave-w-full-phone-only` | — | `@media (width < 37.5em)`
|
|
98
|
+
| Atom | Class | Declarations | Variants |
|
|
99
|
+
| ----------------------- | ------------------------------- | ------------ | ----------------------------------------------------- |
|
|
100
|
+
| `hidePhoneOnly` | `nave-hide-phone-only` | — | `@media (width < 37.5em)` — `display: none;` |
|
|
101
|
+
| `hideTabletPortraitUp` | `nave-hide-tablet-portrait-up` | — | `@media (width >= 37.5em)` — `display: none;` |
|
|
102
|
+
| `hideTabletLandscapeUp` | `nave-hide-tablet-landscape-up` | — | `@media (width >= 56.25em)` — `display: none;` |
|
|
103
|
+
| `hideDesktopUp` | `nave-hide-desktop-up` | — | `@media (width >= 75em)` — `display: none;` |
|
|
104
|
+
| `stackPhoneOnly` | `nave-stack-phone-only` | — | `@media (width < 37.5em)` — `flex-direction: column;` |
|
|
105
|
+
| `wFullPhoneOnly` | `nave-w-full-phone-only` | — | `@media (width < 37.5em)` — `width: 100%;` |
|
|
106
106
|
|
|
107
107
|
## Pairing notes
|
|
108
108
|
|
package/README.md
CHANGED
|
@@ -57,7 +57,10 @@ repeats the same statement, which changes nothing
|
|
|
57
57
|
|
|
58
58
|
Your component CSS goes in `@layer components.consumer`, and a deliberate
|
|
59
59
|
exception goes in `@layer overrides`, which beats every other layer. Left outside
|
|
60
|
-
any layer, your CSS would beat all of them, `overrides` included.
|
|
60
|
+
any layer, your CSS would beat all of them, `overrides` included.
|
|
61
|
+
That is the order for normal declarations. `!important` reverses it: an `!important` in
|
|
62
|
+
`overrides` loses to one in any earlier layer, Nave's reset included, and one outside any
|
|
63
|
+
layer loses to every layered one. Do not write
|
|
61
64
|
into a bare `@layer components` or `@layer tokens`: a rule written directly into a
|
|
62
65
|
layer outranks everything in that layer's sublayers.
|
|
63
66
|
|
|
@@ -142,12 +145,13 @@ What that buys you, and what it does not:
|
|
|
142
145
|
`hidden`, `grid`, `flex`, `block`, `border`, `rounded`, `transition`,
|
|
143
146
|
`relative`, `absolute`, `gap`, `truncate`, `interactive`) belong on the
|
|
144
147
|
`cx.raw()` side when you mean your own.
|
|
145
|
-
- **
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
- **The rest of the `className` attribute is checked by
|
|
149
|
+
[`@navecss/eslint-plugin`](https://github.com/navecss/navecss/tree/main/packages/eslint-plugin#readme).**
|
|
150
|
+
A bare `'legacy-card'` sitting beside these calls is not seen by `tsc` or by
|
|
151
|
+
`@nave`, so `cx.raw()` is a **declared** escape channel — install that plugin
|
|
152
|
+
for an **enforced** one: it reports an undeclared literal class written in
|
|
153
|
+
`className`, and requires a reason on a `cx.raw()` call that carries one, so
|
|
154
|
+
a deliberate step outside the system stays a reasoned, countable call.
|
|
151
155
|
|
|
152
156
|
### Tokens only
|
|
153
157
|
|
|
@@ -250,6 +254,71 @@ failure, not a silently incomplete build):
|
|
|
250
254
|
|
|
251
255
|
---
|
|
252
256
|
|
|
257
|
+
## Editor, linter and coding agent
|
|
258
|
+
|
|
259
|
+
`@nave` is new to these tools, so each one needs a step, and this package ships what each of
|
|
260
|
+
them reads.
|
|
261
|
+
|
|
262
|
+
**VS Code.** This package ships a data file that declares `@nave` to the CSS language service:
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{ "css.customData": ["./node_modules/@navecss/core/nave.css-data.json"] }
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
In a monorepo, the path is relative to the folder you open, so it goes through the
|
|
269
|
+
`node_modules` of the package that depends on `@navecss/core`. VS Code reads the file at
|
|
270
|
+
startup, so reload the window once it is installed.
|
|
271
|
+
|
|
272
|
+
**Stylelint.** To have `@nave` accepted without disabling `at-rule-no-unknown` for anything
|
|
273
|
+
else, declare it under `languageOptions.syntax.atRules`:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{ "languageOptions": { "syntax": { "atRules": { "nave": { "prelude": "<custom-ident>+" } } } } }
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
This needs stylelint 16.17.0 or later. `@navecss/stylelint-config`'s own peer range is
|
|
280
|
+
`^17.0.0`; the two are stated separately because the config does more than this one line, and
|
|
281
|
+
this line works on its own, with no config installed, from 16.17.0 on. For a check that values
|
|
282
|
+
on a list of properties (colour, spacing and others) use a `var()` or an admitted keyword, extend
|
|
283
|
+
[`@navecss/stylelint-config`](https://github.com/navecss/navecss/tree/main/packages/stylelint-config#readme)
|
|
284
|
+
beside this line; it never replaces it.
|
|
285
|
+
|
|
286
|
+
**ESLint®.** For the JSX side of the same idea — a literal class in `className`, or a literal
|
|
287
|
+
value on a tokenized `style` property — install
|
|
288
|
+
[`@navecss/eslint-plugin`](https://github.com/navecss/navecss/tree/main/packages/eslint-plugin#readme):
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
// eslint.config.js
|
|
292
|
+
import nave from '@navecss/eslint-plugin'
|
|
293
|
+
|
|
294
|
+
export default [
|
|
295
|
+
// ...your existing config
|
|
296
|
+
nave.configs.recommended,
|
|
297
|
+
]
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Coding agents.** This package ships a guide for coding agents at `skills/navecss/SKILL.md`:
|
|
301
|
+
the built-in atoms, the custom properties this build emits, the layer order, and where consumer
|
|
302
|
+
CSS goes, for the version you have installed. No agent looks inside `node_modules` on its own,
|
|
303
|
+
so add a pointer to your own `AGENTS.md` (and to `CLAUDE.md` too if you have one, since Claude
|
|
304
|
+
Code reads `AGENTS.md` only where there is no `CLAUDE.md`):
|
|
305
|
+
|
|
306
|
+
```md
|
|
307
|
+
## Styling: NaveCSS
|
|
308
|
+
|
|
309
|
+
Before writing or changing CSS or a `className`, read
|
|
310
|
+
`node_modules/@navecss/core/skills/navecss/SKILL.md`, resolved from the
|
|
311
|
+
package that depends on `@navecss/core`. The Nave custom properties and
|
|
312
|
+
built-in atoms it lists are the only ones there are. Atoms this project
|
|
313
|
+
registers through `navePlugin({ extend })` are valid too, in `@nave` only,
|
|
314
|
+
never in `cx()`. If a name you need is in neither place, say so rather
|
|
315
|
+
than invent one.
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Consumers of `@navecss/tokens` alone get no guide: it ships only in this package.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
253
322
|
## Exports
|
|
254
323
|
|
|
255
324
|
| Export | Description |
|
package/dist/cx.d.ts
CHANGED
|
@@ -51,10 +51,11 @@ import { AtomName } from './atoms.js';
|
|
|
51
51
|
* relative, absolute, gap, truncate, interactive.
|
|
52
52
|
*
|
|
53
53
|
* What is NOT checked:
|
|
54
|
-
* Nothing here looks at the rest of the className attribute.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
54
|
+
* Nothing here looks at the rest of the className attribute. @navecss/eslint-plugin does:
|
|
55
|
+
* install it for an ENFORCED escape channel, reporting an undeclared literal class written
|
|
56
|
+
* in className and requiring a reason on a cx.raw() call that carries one (a class the
|
|
57
|
+
* project declares as its own passes by design). Without it, cx.raw() is only a DECLARED
|
|
58
|
+
* escape channel (greppable: `grep -r 'cx.raw'`).
|
|
58
59
|
*
|
|
59
60
|
* Note on consumer atoms:
|
|
60
61
|
* Atoms defined via navePlugin({ extend }) are @nave-directive only.
|
package/dist/postcss.d.ts
CHANGED
|
@@ -16,6 +16,13 @@ import { AtomDefinition } from './atoms.js';
|
|
|
16
16
|
* navePlugin({ extend: myAtoms }) // Nave + consumer atoms
|
|
17
17
|
* navePlugin({ onUnknown: 'warn' }) // Log and skip instead of failing the build
|
|
18
18
|
*
|
|
19
|
+
* `extend` is trusted, consumer-authored code, run at build time in the same file that could
|
|
20
|
+
* already run arbitrary JavaScript — it is not sanitised input. `validateExtendAtoms` still
|
|
21
|
+
* parses every declaration, pseudo selector key, and media/container condition an atom carries,
|
|
22
|
+
* and rejects any that does not parse as exactly that one construct, since a typo there is
|
|
23
|
+
* otherwise silent CSS injection into the generated output rather than a build error at the
|
|
24
|
+
* point of the mistake.
|
|
25
|
+
*
|
|
19
26
|
* Consumer atoms:
|
|
20
27
|
* import type { AtomDefinition } from '@navecss/core/postcss'
|
|
21
28
|
* import { media } from '@navecss/tokens/breakpoints'
|
package/dist/postcss.js
CHANGED
|
@@ -2,69 +2,62 @@ import { atoms } from './chunk-BVON7XKC.js';
|
|
|
2
2
|
import postcss from 'postcss';
|
|
3
3
|
|
|
4
4
|
// src/selector-utils.ts
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (char === ")" || char === "]") return -1;
|
|
13
|
-
return 0;
|
|
14
|
-
}
|
|
15
|
-
function consumeSplitChar(state, char) {
|
|
16
|
-
const { branches, depth, quote, isEscaped, current } = state;
|
|
17
|
-
if (isEscaped) {
|
|
18
|
-
return { branches, depth, quote, isEscaped: false, current: current + char };
|
|
19
|
-
}
|
|
20
|
-
if (char === "\\") {
|
|
21
|
-
return { branches, depth, quote, isEscaped: true, current: current + char };
|
|
22
|
-
}
|
|
23
|
-
const nextDepth = depth + depthDelta(quote, char);
|
|
24
|
-
if (!quote && char === "," && nextDepth === 0) {
|
|
25
|
-
return {
|
|
26
|
-
branches: [...branches, current],
|
|
27
|
-
depth: nextDepth,
|
|
28
|
-
quote,
|
|
29
|
-
isEscaped: false,
|
|
30
|
-
current: ""
|
|
31
|
-
};
|
|
5
|
+
function isCommentClose(text, index, commentStart) {
|
|
6
|
+
return text[index] === "/" && text[index - 1] === "*" && index - 1 >= commentStart + 2;
|
|
7
|
+
}
|
|
8
|
+
function isConsumedAsLiteral(state, text, index) {
|
|
9
|
+
if (state.commentStart !== void 0) {
|
|
10
|
+
if (isCommentClose(text, index, state.commentStart)) state.commentStart = void 0;
|
|
11
|
+
return true;
|
|
32
12
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
13
|
+
if (state.isEscaped) {
|
|
14
|
+
state.isEscaped = false;
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (text[index] === "\\") {
|
|
18
|
+
state.isEscaped = true;
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (!state.quote) return false;
|
|
22
|
+
if (text[index] === state.quote) state.quote = void 0;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
function isCodeChar(state, text, index) {
|
|
26
|
+
if (isConsumedAsLiteral(state, text, index)) return false;
|
|
27
|
+
const char = text[index];
|
|
28
|
+
if (char === '"' || char === "'") {
|
|
29
|
+
state.quote = char;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (char === "/" && text[index + 1] === "*") {
|
|
33
|
+
state.commentStart = index;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
function codeMask(text) {
|
|
39
|
+
const state = { quote: void 0, isEscaped: false, commentStart: void 0 };
|
|
40
|
+
return Array.from({ length: text.length }, (_, index) => isCodeChar(state, text, index));
|
|
40
41
|
}
|
|
41
42
|
function splitTopLevel(selectorList) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
let isEscaped = false;
|
|
55
|
-
for (const char of branch) {
|
|
56
|
-
if (isEscaped) {
|
|
57
|
-
isEscaped = false;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (char === "\\") {
|
|
61
|
-
isEscaped = true;
|
|
62
|
-
continue;
|
|
43
|
+
const mask = codeMask(selectorList);
|
|
44
|
+
const branches = [];
|
|
45
|
+
let depth = 0;
|
|
46
|
+
let start = 0;
|
|
47
|
+
for (const [index, isCode] of mask.entries()) {
|
|
48
|
+
if (!isCode) continue;
|
|
49
|
+
const char = selectorList[index];
|
|
50
|
+
if (char === "(" || char === "[") depth += 1;
|
|
51
|
+
else if (char === ")" || char === "]") depth -= 1;
|
|
52
|
+
else if (char === "," && depth === 0) {
|
|
53
|
+
branches.push(selectorList.slice(start, index));
|
|
54
|
+
start = index + 1;
|
|
63
55
|
}
|
|
64
|
-
if (!quote && char === "&") return true;
|
|
65
|
-
quote = nextQuote(quote, char);
|
|
66
56
|
}
|
|
67
|
-
return
|
|
57
|
+
return [...branches, selectorList.slice(start)];
|
|
58
|
+
}
|
|
59
|
+
function hasNestingSelector(branch) {
|
|
60
|
+
return codeMask(branch).some((isCode, index) => isCode && branch[index] === "&");
|
|
68
61
|
}
|
|
69
62
|
function anchorSelectorList(selectorList) {
|
|
70
63
|
return splitTopLevel(selectorList).map((branch) => {
|
|
@@ -74,7 +67,7 @@ function anchorSelectorList(selectorList) {
|
|
|
74
67
|
`anchorSelectorList: empty selector branch in pseudo key "${selectorList}". Remove the stray or trailing comma: an empty branch would otherwise compile to a bare "&", matching the atom unconditionally.`
|
|
75
68
|
);
|
|
76
69
|
}
|
|
77
|
-
return
|
|
70
|
+
return hasNestingSelector(trimmed) ? trimmed : `&${trimmed}`;
|
|
78
71
|
}).join(", ");
|
|
79
72
|
}
|
|
80
73
|
|
|
@@ -140,6 +133,98 @@ function stampSource(node, source) {
|
|
|
140
133
|
child.source = source;
|
|
141
134
|
});
|
|
142
135
|
}
|
|
136
|
+
function isPlainObject(value) {
|
|
137
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
138
|
+
}
|
|
139
|
+
function isDeclarationValid(prop, value) {
|
|
140
|
+
let root;
|
|
141
|
+
try {
|
|
142
|
+
root = postcss.parse(`a{${prop}:${value}}`);
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
if (root.nodes.length !== 1) return false;
|
|
147
|
+
const rule = root.nodes[0];
|
|
148
|
+
if (rule.type !== "rule" || rule.nodes.length !== 1) return false;
|
|
149
|
+
const decl = rule.nodes[0];
|
|
150
|
+
if (decl.type !== "decl") return false;
|
|
151
|
+
return decl.prop === prop && decl.toString() === `${prop}:${value}`.trimEnd();
|
|
152
|
+
}
|
|
153
|
+
function isPropValid(prop) {
|
|
154
|
+
return isDeclarationValid(prop, "0");
|
|
155
|
+
}
|
|
156
|
+
function isSelectorValid(key) {
|
|
157
|
+
let selector;
|
|
158
|
+
try {
|
|
159
|
+
selector = anchorSelectorList(key);
|
|
160
|
+
} catch {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
let root;
|
|
164
|
+
try {
|
|
165
|
+
root = postcss.parse(`${selector}{}`);
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
if (root.nodes.length !== 1) return false;
|
|
170
|
+
const rule = root.nodes[0];
|
|
171
|
+
return rule.type === "rule" && rule.selector === selector && rule.nodes.length === 0;
|
|
172
|
+
}
|
|
173
|
+
function isConditionValid(atName, condition) {
|
|
174
|
+
let root;
|
|
175
|
+
try {
|
|
176
|
+
root = postcss.parse(`@${atName} ${condition}{}`);
|
|
177
|
+
} catch {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (root.nodes.length !== 1) return false;
|
|
181
|
+
const node = root.nodes[0];
|
|
182
|
+
return node.type === "atrule" && node.name === atName && node.params === condition.trim() && (node.nodes?.length ?? 0) === 0;
|
|
183
|
+
}
|
|
184
|
+
function fail(describedAs, value) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`navePlugin({ extend }): ${describedAs} does not parse as a single CSS declaration, selector or condition, and would break out of the rule it is spliced into: ${JSON.stringify(value)}. extend is trusted, consumer-authored code, not sanitised input \u2014 fix the atom definition.`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
function assertDeclarationsSafe(declarations, where) {
|
|
190
|
+
if (!isPlainObject(declarations)) return;
|
|
191
|
+
for (const [prop, value] of Object.entries(declarations)) {
|
|
192
|
+
if (typeof prop === "string" && !isPropValid(prop)) {
|
|
193
|
+
fail(`${where}'s declaration property "${prop}"`, prop);
|
|
194
|
+
}
|
|
195
|
+
if (typeof value === "string" && !isDeclarationValid(prop, value)) {
|
|
196
|
+
fail(`${where}'s declaration value for "${prop}"`, value);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function assertPseudosSafe(pseudos, where) {
|
|
201
|
+
if (!isPlainObject(pseudos)) return;
|
|
202
|
+
for (const [pseudo, declarations] of Object.entries(pseudos)) {
|
|
203
|
+
if (!isSelectorValid(pseudo)) fail(`${where}'s pseudo "${pseudo}"`, pseudo);
|
|
204
|
+
assertDeclarationsSafe(declarations, `${where}'s pseudo "${pseudo}"`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function assertAtBlocksSafe(blocks, atName, where) {
|
|
208
|
+
if (!isPlainObject(blocks)) return;
|
|
209
|
+
for (const [condition, block] of Object.entries(blocks)) {
|
|
210
|
+
if (!isConditionValid(atName, condition))
|
|
211
|
+
fail(`${where}'s ${atName} condition "${condition}"`, condition);
|
|
212
|
+
if (!isPlainObject(block)) continue;
|
|
213
|
+
const blockWhere = `${where}'s ${atName} "${condition}"`;
|
|
214
|
+
assertDeclarationsSafe(block.declarations, blockWhere);
|
|
215
|
+
assertPseudosSafe(block.pseudos, blockWhere);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function validateExtendAtoms(extend) {
|
|
219
|
+
for (const [name, atom] of Object.entries(extend)) {
|
|
220
|
+
if (!atom) continue;
|
|
221
|
+
const where = `atom "${name}"`;
|
|
222
|
+
assertDeclarationsSafe(atom.declarations, where);
|
|
223
|
+
assertPseudosSafe(atom.pseudos, where);
|
|
224
|
+
assertAtBlocksSafe(atom.media, "media", where);
|
|
225
|
+
assertAtBlocksSafe(atom.container, "container", where);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
143
228
|
|
|
144
229
|
// src/postcss.ts
|
|
145
230
|
function reportUnknown(msg, ctx) {
|
|
@@ -177,6 +262,7 @@ function insertDeclarations(atRule, declNodes, isFollowingNested) {
|
|
|
177
262
|
}
|
|
178
263
|
var navePlugin = (options = {}) => {
|
|
179
264
|
const { onUnknown = "error", extend = {} } = options;
|
|
265
|
+
validateExtendAtoms(extend);
|
|
180
266
|
const allAtoms = { ...atoms, ...extend };
|
|
181
267
|
const validAtomNames = new Set(
|
|
182
268
|
Object.entries(allAtoms).filter(([, definition]) => definition).map(([name]) => name)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1.1,
|
|
3
|
+
"atDirectives": [
|
|
4
|
+
{
|
|
5
|
+
"name": "@nave",
|
|
6
|
+
"description": {
|
|
7
|
+
"kind": "markdown",
|
|
8
|
+
"value": "The `@nave` at-rule applies one or more of Nave’s built-in atomic utilities to the CSS rule it appears in. Loading this file stops an editor from reporting `@nave` as an unknown at-rule and shows this description on hover; it does not complete atom names.\n\n- **Display:** `flex`, `inlineFlex`, `grid`, `block`, `inlineBlock`, `hidden`\n- **Flex:** `flexCol`, `flexWrap`, `itemsCenter`, `itemsStart`, `itemsEnd`, `justifyCenter`, `justifyBetween`, `justifyEnd`, `flexGrow`, `flexShrink0`, `gap`\n- **Position:** `relative`, `absolute`, `insetFull`\n- **Sizing:** `wFull`, `hFull`, `minW0`\n- **Typography:** `truncate`, `srOnly`, `srOnlyFocusable`, `noWrap`, `breakWord`, `textLeft`, `textCenter`, `textRight`\n- **Interaction:** `interactive`, `focusRing`, `disabledState`\n- **Visual:** `rounded`, `roundedCard`, `roundedFull`, `border`, `overflowHidden`, `overflowAuto`\n- **Transition:** `transition`\n- **Containment:** `container`\n- **Responsive:** `hidePhoneOnly`, `hideTabletPortraitUp`, `hideTabletLandscapeUp`, `hideDesktopUp`, `stackPhoneOnly`, `wFullPhoneOnly`\n\nAtoms registered through `extend` are valid and are not listed here."
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navecss/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Nave design system — layer architecture, reset, atomic utilities, PostCSS plugin",
|
|
@@ -39,7 +39,9 @@
|
|
|
39
39
|
"files": [
|
|
40
40
|
"dist",
|
|
41
41
|
"CONSUMER-ATOMS.md",
|
|
42
|
-
"ATOMS.md"
|
|
42
|
+
"ATOMS.md",
|
|
43
|
+
"nave.css-data.json",
|
|
44
|
+
"skills"
|
|
43
45
|
],
|
|
44
46
|
"peerDependencies": {
|
|
45
47
|
"postcss": "^8"
|
|
@@ -50,7 +52,7 @@
|
|
|
50
52
|
}
|
|
51
53
|
},
|
|
52
54
|
"dependencies": {
|
|
53
|
-
"@navecss/tokens": "^0.
|
|
55
|
+
"@navecss/tokens": "^0.2.0"
|
|
54
56
|
},
|
|
55
57
|
"devDependencies": {
|
|
56
58
|
"@types/node": "^26.6.2",
|
|
@@ -59,10 +61,11 @@
|
|
|
59
61
|
"@vitest/coverage-v8": "5.0.1",
|
|
60
62
|
"playwright": "1.63.0",
|
|
61
63
|
"postcss": "^8.5.28",
|
|
62
|
-
"prettier": "3.9.
|
|
64
|
+
"prettier": "3.9.9",
|
|
63
65
|
"tsup": "^8.5.1",
|
|
64
66
|
"typescript": "6.0.3",
|
|
65
|
-
"vitest": "5.0.1"
|
|
67
|
+
"vitest": "5.0.1",
|
|
68
|
+
"vscode-css-languageservice": "6.3.10"
|
|
66
69
|
},
|
|
67
70
|
"engines": {
|
|
68
71
|
"node": ">=22.18"
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: navecss
|
|
3
|
+
description: 'Reference for Nave — the @nave at-rule, cx()/cx.raw(), --nave-* custom
|
|
4
|
+
properties and the @layer order — so an agent applies existing atoms and tokens instead
|
|
5
|
+
of inventing new CSS.'
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# navecss
|
|
9
|
+
|
|
10
|
+
An index of Nave’s vocabulary for a coding agent: every built-in atom, every `--nave-*`
|
|
11
|
+
custom property, the layer order, and the idiom that applies them. See
|
|
12
|
+
[ATOMS.md](../../ATOMS.md) for the same atom table alone, and
|
|
13
|
+
[Theming](https://github.com/navecss/navecss/blob/main/README.md#theming) for how a project
|
|
14
|
+
changes the seed colour and everything derived from it.
|
|
15
|
+
|
|
16
|
+
## Using `@nave` and `var(--nave-*)`
|
|
17
|
+
|
|
18
|
+
Apply built-in atoms with the `@nave` at-rule inside a CSS rule; read values through
|
|
19
|
+
`var(--nave-*)`. Both vocabularies are closed sets: the built-in atom names and the
|
|
20
|
+
`--nave-*` names are exactly the ones on this page.
|
|
21
|
+
An atom name that is neither on this page nor in the project’s own `navePlugin({ extend })`
|
|
22
|
+
configuration is not used, and a `--nave-*` name not on this page is not used either:
|
|
23
|
+
when the name you need does not exist, say so rather than invent one.
|
|
24
|
+
An undeclared `--nave-*` name renders nothing; `@navecss/stylelint-config` reports it.
|
|
25
|
+
|
|
26
|
+
Atoms a project registers through `navePlugin({ extend })` are not listed on this page.
|
|
27
|
+
They are valid in `@nave` only, never in `cx()`, and they live in that project’s own
|
|
28
|
+
`navePlugin({ extend })` configuration, so look for them there
|
|
29
|
+
(their shape is in [CONSUMER-ATOMS.md](../../CONSUMER-ATOMS.md)).
|
|
30
|
+
Where one shares a built-in atom’s name,
|
|
31
|
+
`@nave` applies the project’s atom and `cx()` still returns the built-in,
|
|
32
|
+
so the declarations this page shows for that name are not what `@nave` applies there.
|
|
33
|
+
|
|
34
|
+
```css
|
|
35
|
+
/* button.module.css */
|
|
36
|
+
@layer components.consumer {
|
|
37
|
+
.root {
|
|
38
|
+
@nave interactive focusRing transition;
|
|
39
|
+
background: var(--nave-color-action-primary);
|
|
40
|
+
color: var(--nave-color-on-action-primary);
|
|
41
|
+
padding: var(--nave-spacing-control-md) var(--nave-spacing-control-lg);
|
|
42
|
+
border-radius: var(--nave-radius-control);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Consumer rules go in `@layer components.consumer`, and a deliberate exception in
|
|
48
|
+
`@layer overrides`; never write an unlayered rule. Atom names are the camelCase keys listed
|
|
49
|
+
under Atoms below (`focusRing`, `justifyBetween`), never the `nave-` class they emit.
|
|
50
|
+
|
|
51
|
+
See where `@nave` is valid (nesting depth, `@media`/`@container`, `@keyframes`) in
|
|
52
|
+
[the package README](../../README.md#where-nave-is-valid).
|
|
53
|
+
|
|
54
|
+
## `cx()`
|
|
55
|
+
|
|
56
|
+
The JavaScript escape hatch: `cx('interactive', 'focusRing')` returns the class of each
|
|
57
|
+
built-in atom it names, and `cx()` takes built-in atoms and nothing else.
|
|
58
|
+
Its type check is TypeScript only: a JavaScript consumer gets none of it. A type error from
|
|
59
|
+
`cx()` means the name is wrong: it is never a reason to reach for `cx.raw()` or a cast.
|
|
60
|
+
|
|
61
|
+
## `cx.raw()`
|
|
62
|
+
|
|
63
|
+
`cx.raw()` is for a class from outside any system Nave sees, the project’s own classes
|
|
64
|
+
included. Where a project class shares an atom’s name, `cx('container')` is Nave’s atom and
|
|
65
|
+
`cx.raw('container')` is the project’s class.
|
|
66
|
+
|
|
67
|
+
`cx.raw()` is also for a CSS Module class behind a condition, shown by the code span
|
|
68
|
+
`cx.raw(isActive && styles.active)`, because a template-literal slot interpolates a falsy
|
|
69
|
+
condition’s own value — naming `false`, `undefined` and `0` — as a class.
|
|
70
|
+
|
|
71
|
+
If the project runs `@navecss/eslint-plugin`, its reports name their remedies, most preferred first: take the first one that fits. Never answer a report with a disable comment, and never write a `cx.raw()` reason to make a report go away.
|
|
72
|
+
|
|
73
|
+
## Atoms
|
|
74
|
+
|
|
75
|
+
Every built-in atom: the name as written in `@nave` and `cx()`, the declarations it
|
|
76
|
+
applies, and its pseudo-class, `@media` or `@container` variants.
|
|
77
|
+
A name not on this list is not a built-in atom.
|
|
78
|
+
A project’s own `navePlugin({ extend })` atoms are not listed here (see Using `@nave` above);
|
|
79
|
+
a name that is in neither is not used, so do not invent one.
|
|
80
|
+
|
|
81
|
+
### Display
|
|
82
|
+
|
|
83
|
+
| Atom | Declarations | Variants |
|
|
84
|
+
| ------------- | ------------------------ | -------- |
|
|
85
|
+
| `flex` | `display: flex;` | — |
|
|
86
|
+
| `inlineFlex` | `display: inline-flex;` | — |
|
|
87
|
+
| `grid` | `display: grid;` | — |
|
|
88
|
+
| `block` | `display: block;` | — |
|
|
89
|
+
| `inlineBlock` | `display: inline-block;` | — |
|
|
90
|
+
| `hidden` | `display: none;` | — |
|
|
91
|
+
|
|
92
|
+
### Flex
|
|
93
|
+
|
|
94
|
+
| Atom | Declarations | Variants |
|
|
95
|
+
| ---------------- | -------------------------------------- | -------- |
|
|
96
|
+
| `flexCol` | `flex-direction: column;` | — |
|
|
97
|
+
| `flexWrap` | `flex-wrap: wrap;` | — |
|
|
98
|
+
| `itemsCenter` | `align-items: center;` | — |
|
|
99
|
+
| `itemsStart` | `align-items: flex-start;` | — |
|
|
100
|
+
| `itemsEnd` | `align-items: flex-end;` | — |
|
|
101
|
+
| `justifyCenter` | `justify-content: center;` | — |
|
|
102
|
+
| `justifyBetween` | `justify-content: space-between;` | — |
|
|
103
|
+
| `justifyEnd` | `justify-content: flex-end;` | — |
|
|
104
|
+
| `flexGrow` | `flex-grow: 1;` | — |
|
|
105
|
+
| `flexShrink0` | `flex-shrink: 0;` | — |
|
|
106
|
+
| `gap` | `gap: var(--nave-spacing-content-md);` | — |
|
|
107
|
+
|
|
108
|
+
### Position
|
|
109
|
+
|
|
110
|
+
| Atom | Declarations | Variants |
|
|
111
|
+
| ----------- | --------------------- | -------- |
|
|
112
|
+
| `relative` | `position: relative;` | — |
|
|
113
|
+
| `absolute` | `position: absolute;` | — |
|
|
114
|
+
| `insetFull` | `inset: 0;` | — |
|
|
115
|
+
|
|
116
|
+
### Sizing
|
|
117
|
+
|
|
118
|
+
| Atom | Declarations | Variants |
|
|
119
|
+
| ------- | --------------- | -------- |
|
|
120
|
+
| `wFull` | `width: 100%;` | — |
|
|
121
|
+
| `hFull` | `height: 100%;` | — |
|
|
122
|
+
| `minW0` | `min-width: 0;` | — |
|
|
123
|
+
|
|
124
|
+
### Typography
|
|
125
|
+
|
|
126
|
+
| Atom | Declarations | Variants |
|
|
127
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
128
|
+
| `truncate` | `overflow: hidden;`<br>`text-overflow: ellipsis;`<br>`white-space: nowrap;` | — |
|
|
129
|
+
| `srOnly` | `position: absolute;`<br>`width: 1px;`<br>`height: 1px;`<br>`padding: 0;`<br>`margin: -1px;`<br>`overflow: hidden;`<br>`clip-path: inset(50%);`<br>`white-space: nowrap;`<br>`border-width: 0;` | — |
|
|
130
|
+
| `srOnlyFocusable` | `position: absolute;`<br>`width: 1px;`<br>`height: 1px;`<br>`padding: 0;`<br>`margin: -1px;`<br>`overflow: hidden;`<br>`clip-path: inset(50%);`<br>`white-space: nowrap;`<br>`border-width: 0;` | `:focus-visible` — `position: static;`<br>`width: auto;`<br>`height: auto;`<br>`margin: 0;`<br>`overflow: visible;`<br>`clip-path: none;`<br>`white-space: normal;`<br>`:focus-within` — `position: static;`<br>`width: auto;`<br>`height: auto;`<br>`margin: 0;`<br>`overflow: visible;`<br>`clip-path: none;`<br>`white-space: normal;` |
|
|
131
|
+
| `noWrap` | `white-space: nowrap;` | — |
|
|
132
|
+
| `breakWord` | `overflow-wrap: break-word;` | — |
|
|
133
|
+
| `textLeft` | `text-align: left;` | — |
|
|
134
|
+
| `textCenter` | `text-align: center;` | — |
|
|
135
|
+
| `textRight` | `text-align: right;` | — |
|
|
136
|
+
|
|
137
|
+
### Interaction
|
|
138
|
+
|
|
139
|
+
| Atom | Declarations | Variants |
|
|
140
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
141
|
+
| `interactive` | `cursor: pointer;`<br>`-webkit-user-select: none;`<br>`user-select: none;`<br>`-webkit-tap-highlight-color: transparent;` | — |
|
|
142
|
+
| `focusRing` | `outline: none;` | `:focus-visible` — `outline: var(--nave-border-width-focus) solid var(--nave-color-border-focus);`<br>`outline-offset: 2px;` |
|
|
143
|
+
| `disabledState` | — | `:disabled, [aria-disabled="true"]` — `color: var(--nave-color-content-disabled);`<br>`border-color: var(--nave-color-border-disabled);`<br>`pointer-events: none;`<br>On the aria-disabled branch the element stays focusable by design: this atom only blocks pointer activation (pointer-events: none), so the component's own activation handler must also check the attribute and no-op on Enter and Space, since CSS cannot prevent keyboard activation. |
|
|
144
|
+
|
|
145
|
+
### Visual
|
|
146
|
+
|
|
147
|
+
| Atom | Declarations | Variants |
|
|
148
|
+
| ---------------- | ----------------------------------------------------------------------------- | -------- |
|
|
149
|
+
| `rounded` | `border-radius: var(--nave-radius-control);` | — |
|
|
150
|
+
| `roundedCard` | `border-radius: var(--nave-radius-card);` | — |
|
|
151
|
+
| `roundedFull` | `border-radius: var(--nave-radius-full);` | — |
|
|
152
|
+
| `border` | `border: var(--nave-border-width-sm) solid var(--nave-color-border-default);` | — |
|
|
153
|
+
| `overflowHidden` | `overflow: hidden;` | — |
|
|
154
|
+
| `overflowAuto` | `overflow: auto;` | — |
|
|
155
|
+
|
|
156
|
+
### Transition
|
|
157
|
+
|
|
158
|
+
| Atom | Declarations | Variants |
|
|
159
|
+
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
|
160
|
+
| `transition` | `transition-property: color, background-color, border-color, opacity, box-shadow;`<br>`transition-duration: var(--nave-motion-duration-base);`<br>`transition-timing-function: var(--nave-motion-easing-standard);` | — |
|
|
161
|
+
|
|
162
|
+
### Containment
|
|
163
|
+
|
|
164
|
+
| Atom | Declarations | Variants |
|
|
165
|
+
| ----------- | ------------------------------ | -------- |
|
|
166
|
+
| `container` | `container-type: inline-size;` | — |
|
|
167
|
+
|
|
168
|
+
### Responsive
|
|
169
|
+
|
|
170
|
+
| Atom | Declarations | Variants |
|
|
171
|
+
| ----------------------- | ------------ | ----------------------------------------------------- |
|
|
172
|
+
| `hidePhoneOnly` | — | `@media (width < 37.5em)` — `display: none;` |
|
|
173
|
+
| `hideTabletPortraitUp` | — | `@media (width >= 37.5em)` — `display: none;` |
|
|
174
|
+
| `hideTabletLandscapeUp` | — | `@media (width >= 56.25em)` — `display: none;` |
|
|
175
|
+
| `hideDesktopUp` | — | `@media (width >= 75em)` — `display: none;` |
|
|
176
|
+
| `stackPhoneOnly` | — | `@media (width < 37.5em)` — `flex-direction: column;` |
|
|
177
|
+
| `wFullPhoneOnly` | — | `@media (width < 37.5em)` — `width: 100%;` |
|
|
178
|
+
|
|
179
|
+
## Custom properties
|
|
180
|
+
|
|
181
|
+
Every `--nave-*` custom property Nave declares, with its description where one exists. A
|
|
182
|
+
name not on this list is not declared — an undeclared `--nave-*` name passes the build and
|
|
183
|
+
renders nothing.
|
|
184
|
+
|
|
185
|
+
- `--nave-border-width-focus`: Focus ring indicator outline width
|
|
186
|
+
- `--nave-border-width-lg`
|
|
187
|
+
- `--nave-border-width-none`
|
|
188
|
+
- `--nave-border-width-sm`: Default border / divider
|
|
189
|
+
- `--nave-color-action-primary`
|
|
190
|
+
- `--nave-color-action-primary-active`
|
|
191
|
+
- `--nave-color-action-primary-hover`
|
|
192
|
+
- `--nave-color-action-secondary`
|
|
193
|
+
- `--nave-color-border-control`: Identifying boundary for a control (inputs, form boundaries, and the secondary action control).
|
|
194
|
+
- `--nave-color-border-default`: Decorative dividers and card edges.
|
|
195
|
+
- `--nave-color-border-disabled`
|
|
196
|
+
- `--nave-color-border-focus`
|
|
197
|
+
- `--nave-color-border-strong`
|
|
198
|
+
- `--nave-color-content-disabled`: Text inside a disabled interactive component.
|
|
199
|
+
- `--nave-color-content-inverse`: Declared foreground for surface.inverse.
|
|
200
|
+
- `--nave-color-content-link`: Do not distinguish a link by colour alone. Nave's own examples keep a non-colour distinction wherever this token is shown.
|
|
201
|
+
- `--nave-color-content-primary`
|
|
202
|
+
- `--nave-color-content-secondary`
|
|
203
|
+
- `--nave-color-content-tertiary`: Placeholder and hint text only.
|
|
204
|
+
- `--nave-color-feedback-danger`: Solid-background role for a destructive control or error state.
|
|
205
|
+
- `--nave-color-feedback-danger-foreground`: Foreground role for danger text and icons.
|
|
206
|
+
- `--nave-color-feedback-info`: Alias of feedback.warning (shared tinted-neutral family).
|
|
207
|
+
- `--nave-color-feedback-info-foreground`
|
|
208
|
+
- `--nave-color-feedback-success`: Alias of feedback.warning (shared tinted-neutral family).
|
|
209
|
+
- `--nave-color-feedback-success-foreground`
|
|
210
|
+
- `--nave-color-feedback-warning`: Solid-background role for a warning state (shared tinted-neutral family).
|
|
211
|
+
- `--nave-color-feedback-warning-foreground`: Foreground role for warning text and icons.
|
|
212
|
+
- `--nave-color-on-action-primary`
|
|
213
|
+
- `--nave-color-on-feedback-danger`: Declared foreground for the solid feedback.danger background.
|
|
214
|
+
- `--nave-color-on-feedback-info`
|
|
215
|
+
- `--nave-color-on-feedback-success`
|
|
216
|
+
- `--nave-color-on-feedback-warning`
|
|
217
|
+
- `--nave-color-surface-base`
|
|
218
|
+
- `--nave-color-surface-inverse`
|
|
219
|
+
- `--nave-color-surface-overlay`
|
|
220
|
+
- `--nave-color-surface-raised`
|
|
221
|
+
- `--nave-color-surface-sunken`
|
|
222
|
+
- `--nave-color-tint`
|
|
223
|
+
- `--nave-font-family-base`: Body / UI typeface
|
|
224
|
+
- `--nave-font-family-display`: Heading typeface — swap for a display font when ready
|
|
225
|
+
- `--nave-font-family-mono`: Code / monospace typeface
|
|
226
|
+
- `--nave-font-size-2xl`
|
|
227
|
+
- `--nave-font-size-3xl`
|
|
228
|
+
- `--nave-font-size-4xl`
|
|
229
|
+
- `--nave-font-size-lg`
|
|
230
|
+
- `--nave-font-size-md`: Base body size (16px default)
|
|
231
|
+
- `--nave-font-size-sm`
|
|
232
|
+
- `--nave-font-size-xl`
|
|
233
|
+
- `--nave-font-size-xs`
|
|
234
|
+
- `--nave-font-weight-bold`
|
|
235
|
+
- `--nave-font-weight-medium`
|
|
236
|
+
- `--nave-font-weight-regular`
|
|
237
|
+
- `--nave-font-weight-semibold`
|
|
238
|
+
- `--nave-layer-base`: Default document flow
|
|
239
|
+
- `--nave-layer-dropdown`: Dropdowns, context menus, select panels
|
|
240
|
+
- `--nave-layer-modal`: Modal dialogs and drawers
|
|
241
|
+
- `--nave-layer-overlay`: Backdrop overlays behind modals
|
|
242
|
+
- `--nave-layer-sticky`: Sticky headers, floating toolbars
|
|
243
|
+
- `--nave-layer-toast`: Toast notifications and snackbars
|
|
244
|
+
- `--nave-layer-tooltip`: Tooltips — always on top of everything
|
|
245
|
+
- `--nave-letter-spacing-normal`: Default — no adjustment
|
|
246
|
+
- `--nave-letter-spacing-tight`: Large headings and display text
|
|
247
|
+
- `--nave-letter-spacing-wide`: Uppercase labels, small caps
|
|
248
|
+
- `--nave-line-height-base`: Default body line height
|
|
249
|
+
- `--nave-line-height-loose`
|
|
250
|
+
- `--nave-line-height-none`: Single-line / icon buttons — no extra leading
|
|
251
|
+
- `--nave-line-height-relaxed`
|
|
252
|
+
- `--nave-line-height-snug`
|
|
253
|
+
- `--nave-line-height-tight`: Headings
|
|
254
|
+
- `--nave-motion-duration-base`: Default state transitions: hover, focus
|
|
255
|
+
- `--nave-motion-duration-fast`: Micro-interactions: tooltips, badges
|
|
256
|
+
- `--nave-motion-duration-instant`: No animation — kept at 0ms even under prefers-reduced-motion
|
|
257
|
+
- `--nave-motion-duration-slow`: Layout shifts: accordion, drawer
|
|
258
|
+
- `--nave-motion-easing-accelerate`: Elements leaving the screen
|
|
259
|
+
- `--nave-motion-easing-decelerate`: Elements entering the screen
|
|
260
|
+
- `--nave-motion-easing-standard`: General-purpose — enters and exits
|
|
261
|
+
- `--nave-opacity-disabled`: Opacity multiplier for muting content that is inert and contains nothing focusable (e.g. a disabled icon or illustration). Nave's own interactive-disabled state is expressed through content.disabled and border.disabled instead, not this token: opacity composites everything an element paints, including a focus indicator inside it.
|
|
262
|
+
- `--nave-radius-card`: Cards, panels, dialogs
|
|
263
|
+
- `--nave-radius-control`: Inputs, buttons, tags
|
|
264
|
+
- `--nave-radius-full`: Pill / badge shape
|
|
265
|
+
- `--nave-radius-none`
|
|
266
|
+
- `--nave-radius-overlay`: Modals, drawers, sheets
|
|
267
|
+
- `--nave-radius-sm`
|
|
268
|
+
- `--nave-shadow-modal`: Dialogs and modals — highest elevation
|
|
269
|
+
- `--nave-shadow-none`: No elevation
|
|
270
|
+
- `--nave-shadow-overlay`: Popovers, dropdowns, floating elements
|
|
271
|
+
- `--nave-shadow-raised`: Cards, slight elevation above base
|
|
272
|
+
- `--nave-size-avatar-lg`
|
|
273
|
+
- `--nave-size-avatar-md`
|
|
274
|
+
- `--nave-size-avatar-sm`
|
|
275
|
+
- `--nave-size-control-lg`: Large controls
|
|
276
|
+
- `--nave-size-control-md`: Default control height
|
|
277
|
+
- `--nave-size-control-sm`: Small controls
|
|
278
|
+
- `--nave-size-control-xs`: Compact/dense controls
|
|
279
|
+
- `--nave-size-icon-lg`
|
|
280
|
+
- `--nave-size-icon-md`: Default inline icon
|
|
281
|
+
- `--nave-size-icon-sm`
|
|
282
|
+
- `--nave-size-icon-xl`
|
|
283
|
+
- `--nave-size-icon-xs`
|
|
284
|
+
- `--nave-spacing-content-lg`
|
|
285
|
+
- `--nave-spacing-content-md`: Default component gap and padding
|
|
286
|
+
- `--nave-spacing-content-sm`
|
|
287
|
+
- `--nave-spacing-content-xl`
|
|
288
|
+
- `--nave-spacing-content-xs`
|
|
289
|
+
- `--nave-spacing-control-lg`
|
|
290
|
+
- `--nave-spacing-control-md`
|
|
291
|
+
- `--nave-spacing-control-sm`: No matching _primitive.space rung; authored directly
|
|
292
|
+
- `--nave-spacing-control-xs`
|
|
293
|
+
- `--nave-spacing-layout-lg`
|
|
294
|
+
- `--nave-spacing-layout-md`
|
|
295
|
+
- `--nave-spacing-layout-sm`
|
|
296
|
+
- `--nave-spacing-layout-xs`
|
|
297
|
+
|
|
298
|
+
## Layers
|
|
299
|
+
|
|
300
|
+
Nave’s layer order: `@layer tokens.defaults, tokens.presets, reset, atomic, components.nave, components.consumer, overrides;`
|
|
301
|
+
|
|
302
|
+
The order holds only if it is the first `@layer` declaration the page sees:
|
|
303
|
+
a stylesheet that declares a layer and loads earlier fixes that layer’s position first,
|
|
304
|
+
and the order inverts with no error.
|
|
305
|
+
So keep `@navecss/core/layers`, the order statement alone, as the first import of the
|
|
306
|
+
entry stylesheet, and load that stylesheet before anything that brings its own stylesheet,
|
|
307
|
+
components included.
|
|
308
|
+
|
|
309
|
+
Your own component CSS goes in `@layer components.consumer`; a deliberate exception goes in
|
|
310
|
+
`@layer overrides`, which beats every other layer. A rule left outside any layer beats them
|
|
311
|
+
all, `overrides` included, so never write unlayered CSS.
|
|
312
|
+
That is the order for normal declarations. `!important` reverses it: an `!important` in
|
|
313
|
+
`overrides` loses to one in any earlier layer, Nave’s reset included, and one outside any
|
|
314
|
+
layer loses to every layered one.
|