@navecss/tokens 0.2.0 → 0.4.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/README.md CHANGED
@@ -22,11 +22,11 @@ pnpm add @navecss/tokens
22
22
  @import url('@navecss/tokens/css');
23
23
  ```
24
24
 
25
- This emits the token layer's custom properties (`--nave-*`) inside `@layer tokens.defaults`. Keep the `/css`: unlike `@navecss/core`, whose bare package name is its stylesheet, the bare `@navecss/tokens` is the JavaScript entry point. `@navecss/core` imports this automatically; install `@navecss/tokens` on its own only if you are building without `@navecss/core` (for example, on top of Base UI or Radix UI directly).
25
+ This emits the token layer's custom properties (`--nave-*`) inside `@layer tokens.defaults`. Keep the `/css`: unlike `@navecss/core`, whose bare package name is its stylesheet, the bare `@navecss/tokens` is the JavaScript entry point. `@navecss/core` imports this automatically; install `@navecss/tokens` on its own only if you are building without `@navecss/core` (for example, with Radix UI, or with [`@navecss/base-ui`](https://github.com/navecss/navecss/tree/main/packages/base-ui#readme), which styles Base UI's components from these tokens and does not need core).
26
26
 
27
27
  ## What ships
28
28
 
29
- - **Colour** — the semantic colours consumers use (`action`, `content`, `surface`, `border`, `feedback`, and foregrounds for the primary action and the feedback roles), each with a light and a dark value through `light-dark()`. The `neutral`, `primary` and `danger` scales they are derived from stay inside the build: no step of them is emitted as a custom property. The neutral colours re-hue live from one custom property, under Theming below.
29
+ - **Colour** — the semantic colours consumers use (`action`, `content`, `surface`, `border`, `feedback`, a scrim for behind a modal panel (`backdrop`), and foregrounds for the primary action and the feedback roles), each with a light and a dark value through `light-dark()`. The `neutral`, `primary` and `danger` scales they are derived from stay inside the build: no step of them is emitted as a custom property. The neutral colours re-hue live from one custom property, under Theming below.
30
30
  - **Typography, spacing, size, shape, shadow, motion, z-index, opacity** — the rest of the token taxonomy, authored once in `tokens.json` and compiled by the first-party DTCG 2025.10 reader.
31
31
  - **Breakpoints** — a JS-only export (`@navecss/tokens/breakpoints`): custom properties cannot hold media-query ranges, so these ship as typed constants and prebuilt Level 4 media-query strings instead.
32
32
 
@@ -99,7 +99,7 @@ Write `--flag=value` and not `--flag value`. A `#rrggbb` seed passed as a
99
99
  separate word begins a shell comment, so the rest of the command is discarded
100
100
  and you get a usage error rather than a wrong colour.
101
101
 
102
- 0.1.0 ships no consumer contrast-threshold input. No flag of this entry point sets one, and the build reads none. This is the absence of an input, not a limit on the consumer: a consumer may hold whatever contrast target they choose and check their palette against it with their own tooling. Where a check Nave ships runs in a consumer's build over the consumer's values, it reports and does not fail. A consumer-settable target is deliberately out of 0.1.0 rather than overlooked.
102
+ 0.1.0 ships no consumer contrast-threshold input. No flag of this entry point sets one, and the build reads none. This is the absence of an input, not a limit on the consumer: a consumer may hold whatever contrast target they choose and check their palette against it with their own tooling. No contrast check this entry point runs over a consumer's values can fail the build, and none prints or returns a result. A consumer-settable target is deliberately out of 0.1.0 rather than overlooked.
103
103
 
104
104
  ```bash
105
105
  navecss-tokens validate --source=src/styles/nave/tokens.css
package/TOKENS.md CHANGED
@@ -88,6 +88,7 @@ non-colour value is exactly what its name says.
88
88
  | `--nave-border-width-none` |
89
89
  | `--nave-border-width-sm` |
90
90
  | `--nave-border-width-focus` |
91
+ | `--nave-border-width-mark` |
91
92
  | `--nave-border-width-lg` |
92
93
 
93
94
  ## Radius
@@ -166,6 +167,7 @@ non-colour value is exactly what its name says.
166
167
  | `--nave-color-action-primary-active` | no |
167
168
  | `--nave-color-on-action-primary` | yes |
168
169
  | `--nave-color-action-secondary` | yes |
170
+ | `--nave-color-action-highlight` | yes |
169
171
  | `--nave-color-feedback-danger` | no |
170
172
  | `--nave-color-feedback-danger-foreground` | no |
171
173
  | `--nave-color-on-feedback-danger` | yes |
@@ -178,3 +180,4 @@ non-colour value is exactly what its name says.
178
180
  | `--nave-color-feedback-info` | yes |
179
181
  | `--nave-color-feedback-info-foreground` | yes |
180
182
  | `--nave-color-on-feedback-info` | yes |
183
+ | `--nave-color-backdrop-modal` | yes |
@@ -2,7 +2,7 @@
2
2
  "formatVersion": 1,
3
3
  "producer": {
4
4
  "name": "@navecss/core",
5
- "version": "0.2.0"
5
+ "version": "0.4.0"
6
6
  },
7
7
  "tokens": [
8
8
  "--nave-border-width-focus",
@@ -46,6 +46,11 @@ export const ADJACENCY_EXCLUSIONS = {
46
46
  'and is not answered here.',
47
47
  'border.default': 'decorative divider: excluded as a decorative divider or card edge, and the ' +
48
48
  'exclusion is void the moment such a border is applied to a control.',
49
+ 'backdrop.modal': 'scrim: excluded from the checked pair set because it is translucent, so the colour ' +
50
+ 'it renders is a blend with whatever the page shows beneath it, which no pair in this ' +
51
+ 'list can describe. The exclusion holds only while the scrim is painted behind a fully ' +
52
+ 'modal panel, over a page the reader cannot reach; it is void the moment text, a ' +
53
+ 'control or a focus indicator the reader can reach is drawn on it.',
49
54
  };
50
55
  export const ADJACENCY_OPEN = {
51
56
  'border.strong': 'open: whether this border can identify a control, and therefore whether its ' +
@@ -1,4 +1,5 @@
1
1
  import { type Adjacency } from './adjacency-source.ts';
2
+ import { type SlotMapping } from './semantics.ts';
2
3
  export type { Adjacency, MeasuredOpenSlots, OrphanCheckResult } from './adjacency-source.ts';
3
4
  export { ADJACENCY, ADJACENCY_EXCLUSIONS, ADJACENCY_OPEN, assertNoOrphanedSemanticSlot, } from './adjacency-source.ts';
4
5
  declare const MINIMUM_COVERAGE_CATEGORIES: readonly ["contentOnSurface", "inversePair", "onActionOnAction", "textOnFeedback", "sunken", "borderControl", "borderFocus", "filledAction", "filledFeedback"];
@@ -15,3 +16,4 @@ export interface ForbiddenAdjacencyRule {
15
16
  export declare function findForbiddenAdjacencyRule(subject: string, against: string): ForbiddenAdjacencyRule | undefined;
16
17
  export declare function assertNoForbiddenAdjacency(adjacency?: readonly Adjacency[]): void;
17
18
  export declare function assertNoFocusableAdjacentToActionFill(adjacency?: readonly Adjacency[]): void;
19
+ export declare function assertNoTranslucentSlotInDeclaredPair(adjacency?: readonly Adjacency[], mapping?: Readonly<Record<string, SlotMapping>>, achromaticMapping?: Readonly<Record<string, SlotMapping>>): void;
@@ -1,5 +1,6 @@
1
1
  import { ADJACENCY } from "./adjacency-source.js";
2
- import { SEMANTIC_SLOTS } from "./semantics.js";
2
+ import { roundedAlpha } from "./neutral.js";
3
+ import { ACHROMATIC_MAPPING, CHROMATIC_MAPPING, SEMANTIC_SLOTS, } from "./semantics.js";
3
4
  export { ADJACENCY, ADJACENCY_EXCLUSIONS, ADJACENCY_OPEN, assertNoOrphanedSemanticSlot, } from "./adjacency-source.js";
4
5
  const MINIMUM_COVERAGE_CATEGORIES = [
5
6
  'contentOnSurface',
@@ -131,3 +132,39 @@ export function assertNoFocusableAdjacentToActionFill(adjacency = ADJACENCY) {
131
132
  }
132
133
  }
133
134
  }
135
+ const TRANSLUCENT_SLOT_IN_PAIR_REASON = 'The contrast harness reads opaque colours only, and a translucent slot renders as a ' +
136
+ 'blend with whatever lies beneath it, which no pair can describe. This rule records a ' +
137
+ 'decision about which colour pairs this design system will declare and test; it is not ' +
138
+ 'a check to silence or narrow to make a build pass. Remove the declaration, or, if you ' +
139
+ 'need this pair, open an issue describing it and why, and leave it out of the pull ' +
140
+ 'request until that issue is answered.';
141
+ const MAX_ALIAS_DEPTH = 10;
142
+ function isTranslucentWhenResolved(ref, scheme, lookup) {
143
+ let current = ref;
144
+ for (let depth = 0; depth < MAX_ALIAS_DEPTH; depth++) {
145
+ if (!('alias' in current))
146
+ return current.alpha !== undefined && roundedAlpha(current.alpha) < 1;
147
+ current = lookup(current.alias)[scheme];
148
+ }
149
+ throw new Error('Alias chain too deep — likely a cycle in the semantic mapping.');
150
+ }
151
+ function translucentSlots(mapping, achromaticMapping) {
152
+ const lookup = (isAchromaticBranch) => (slot) => {
153
+ const resolved = (isAchromaticBranch ? achromaticMapping[slot] : undefined) ?? mapping[slot];
154
+ if (!resolved)
155
+ throw new Error(`Unknown semantic slot: ${slot}`);
156
+ return resolved;
157
+ };
158
+ return new Set(Object.keys(mapping).filter((slot) => [false, true].some((isAchromaticBranch) => ['light', 'dark'].some((scheme) => isTranslucentWhenResolved(lookup(isAchromaticBranch)(slot)[scheme], scheme, lookup(isAchromaticBranch))))));
159
+ }
160
+ export function assertNoTranslucentSlotInDeclaredPair(adjacency = ADJACENCY, mapping = CHROMATIC_MAPPING, achromaticMapping = ACHROMATIC_MAPPING) {
161
+ const translucent = translucentSlots(mapping, achromaticMapping);
162
+ for (const pair of adjacency) {
163
+ for (const slot of [pair.subject, pair.against]) {
164
+ if (translucent.has(slot)) {
165
+ throw new Error(`Adjacency rule violation: the translucent slot "${slot}" is declared in the pair ` +
166
+ `(${pair.subject}, ${pair.against}). ${TRANSLUCENT_SLOT_IN_PAIR_REASON}`);
167
+ }
168
+ }
169
+ }
170
+ }
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { collectDescriptionsFromSources } from "../dtcg-descriptions.js";
3
- import { assertCoverageFloor, assertNoFocusableAdjacentToActionFill, assertNoForbiddenAdjacency, assertNoOrphanedSemanticSlot, assertShippedSurfaceCountProvenance, } from "./adjacency.js";
3
+ import { assertCoverageFloor, assertNoFocusableAdjacentToActionFill, assertNoForbiddenAdjacency, assertNoOrphanedSemanticSlot, assertNoTranslucentSlotInDeclaredPair, assertShippedSurfaceCountProvenance, } from "./adjacency.js";
4
4
  import { composeBuildRecord } from "./build-record.js";
5
5
  import { composeContactSheet } from "./contact-sheet.js";
6
6
  import { assertContrastFloors, assertFloorProvenance, assertNoSameStepViolations, checkSameStepLint, runContrastHarness, } from "./contrast.js";
@@ -20,6 +20,7 @@ import { TOKENS_JSON_PATH } from "./tokens-source.js";
20
20
  function runSourceGuards(tokensSourcePaths) {
21
21
  assertNoForbiddenAdjacency();
22
22
  assertNoFocusableAdjacentToActionFill();
23
+ assertNoTranslucentSlotInDeclaredPair();
23
24
  assertCoverageFloor();
24
25
  const orphanCheck = assertNoOrphanedSemanticSlot(SEMANTIC_SLOTS);
25
26
  assertOnStarShape();
@@ -8,6 +8,6 @@ export declare function assertDescriptionsAreClean(...descriptionMaps: readonly
8
8
  export declare const RETHEMING_NOTICE = "Changing this changes everything Nave derives from it. The contrast of the resulting palette follows from what you set, and checking it is yours.";
9
9
  export declare function assertNoticeIsClean(notice?: string, noticeLabel?: string): void;
10
10
  export declare const FEEDBACK_SHARED_IDENTITY_NOTICE = "warning, success and info, and their -foreground variants, resolve to one shared value here; only danger differs. Colour alone therefore cannot tell those three states apart, so wherever a feedback token carries meaning, give it a text label naming the state and a distinct icon or shape that differs per state.";
11
- export declare const NO_CONSUMER_CONTRAST_THRESHOLD_INPUT = "0.1.0 ships no consumer contrast-threshold input. No flag of this entry point sets one, and the build reads none. This is the absence of an input, not a limit on the consumer: a consumer may hold whatever contrast target they choose and check their palette against it with their own tooling. Where a check Nave ships runs in a consumer's build over the consumer's values, it reports and does not fail. A consumer-settable target is deliberately out of 0.1.0 rather than overlooked.";
11
+ export declare const NO_CONSUMER_CONTRAST_THRESHOLD_INPUT = "0.1.0 ships no consumer contrast-threshold input. No flag of this entry point sets one, and the build reads none. This is the absence of an input, not a limit on the consumer: a consumer may hold whatever contrast target they choose and check their palette against it with their own tooling. No contrast check this entry point runs over a consumer's values can fail the build, and none prints or returns a result. A consumer-settable target is deliberately out of 0.1.0 rather than overlooked.";
12
12
  export declare function assertNoticeIsEmitted(css: string, notice: string, noticeLabel: string): void;
13
13
  export declare function assertHarnessFramingIsClean(): void;
@@ -47,7 +47,7 @@ export function assertNoticeIsClean(notice = RETHEMING_NOTICE, noticeLabel = 'Re
47
47
  }
48
48
  }
49
49
  export const FEEDBACK_SHARED_IDENTITY_NOTICE = 'warning, success and info, and their -foreground variants, resolve to one shared value here; only danger differs. Colour alone therefore cannot tell those three states apart, so wherever a feedback token carries meaning, give it a text label naming the state and a distinct icon or shape that differs per state.';
50
- export const NO_CONSUMER_CONTRAST_THRESHOLD_INPUT = "0.1.0 ships no consumer contrast-threshold input. No flag of this entry point sets one, and the build reads none. This is the absence of an input, not a limit on the consumer: a consumer may hold whatever contrast target they choose and check their palette against it with their own tooling. Where a check Nave ships runs in a consumer's build over the consumer's values, it reports and does not fail. A consumer-settable target is deliberately out of 0.1.0 rather than overlooked.";
50
+ export const NO_CONSUMER_CONTRAST_THRESHOLD_INPUT = "0.1.0 ships no consumer contrast-threshold input. No flag of this entry point sets one, and the build reads none. This is the absence of an input, not a limit on the consumer: a consumer may hold whatever contrast target they choose and check their palette against it with their own tooling. No contrast check this entry point runs over a consumer's values can fail the build, and none prints or returns a result. A consumer-settable target is deliberately out of 0.1.0 rather than overlooked.";
51
51
  export function assertNoticeIsEmitted(css, notice, noticeLabel) {
52
52
  if (!css.includes(notice)) {
53
53
  throw new Error(`${noticeLabel} violation: the notice is not present, verbatim, in the emitted CSS. Restore it exactly as authored.`);
@@ -1,4 +1,12 @@
1
1
  export const SLOT_DESCRIPTIONS = new Map([
2
+ [
3
+ 'action.highlight',
4
+ 'Fill for a control under the pointer, the keyboard or a press: a highlighted list or menu item, or a ghost button. Never the only cue for focus or for a state such as selected, checked, open or current, and not for disabled controls.',
5
+ ],
6
+ [
7
+ 'backdrop.modal',
8
+ 'Translucent layer between a modal panel and the page, which it recedes. Only behind a fully modal panel; never under text, a control or a focus indicator the reader can reach.',
9
+ ],
2
10
  [
3
11
  'border.control',
4
12
  'Identifying boundary for a control (inputs, form boundaries, and the secondary action control).',
@@ -34,7 +34,7 @@ export function emitCss(result, layer = 'tokens.defaults') {
34
34
  h: result.records.primary.usedSeed.h,
35
35
  });
36
36
  const lines = [
37
- '/* Nave theming — generated, do not edit. See this package\'s README ("Theming"). */',
37
+ '/* Nave theming — generated, do not edit. See the @navecss/tokens README ("Theming"). */',
38
38
  '',
39
39
  `@property ${TINT_PROPERTY} {`,
40
40
  ` syntax: '<color>';`,
@@ -1,4 +1,4 @@
1
- import { ADJACENCY, assertCoverageFloor, assertNoFocusableAdjacentToActionFill, assertNoForbiddenAdjacency, assertNoOrphanedSemanticSlot, } from "./adjacency.js";
1
+ import { ADJACENCY, assertCoverageFloor, assertNoFocusableAdjacentToActionFill, assertNoForbiddenAdjacency, assertNoOrphanedSemanticSlot, assertNoTranslucentSlotInDeclaredPair, } from "./adjacency.js";
2
2
  import { assertContrastFloors, assertFloorProvenance, assertNoSameStepViolations, checkSameStepLint, runContrastHarness, } from "./contrast.js";
3
3
  import { assertNoticeIsEmitted, RETHEMING_NOTICE } from "./copy-lint.js";
4
4
  import { TINT_SEED_COMMENT_STEM } from "./emit.js";
@@ -76,6 +76,13 @@ export const ACCESSIBILITY_GUARD_MESSAGE_PROBES = [
76
76
  { subject: 'border.focus', against: 'action.primary', class: 'non-text' },
77
77
  ])),
78
78
  },
79
+ {
80
+ label: 'the translucent-slot-in-a-declared-pair output',
81
+ capture: () => capturedFailureMessage(() => assertNoTranslucentSlotInDeclaredPair([
82
+ ...ADJACENCY,
83
+ { subject: 'content.primary', against: 'backdrop.modal', class: 'text' },
84
+ ])),
85
+ },
79
86
  {
80
87
  label: "the same-step lint's real violation output (build-step.ts's runResultGuards)",
81
88
  capture: () => {
@@ -9,4 +9,5 @@ export interface NeutralStep {
9
9
  }
10
10
  export declare function generateNeutralRampLiterals(ceiling?: number): readonly NeutralStep[];
11
11
  export declare function resolveNeutralStep(step: number, hue: number, literals?: readonly NeutralStep[]): Oklch;
12
- export declare function neutralRcsFormula(step: number, literals: readonly NeutralStep[]): string;
12
+ export declare function neutralRcsFormula(step: number, literals: readonly NeutralStep[], alpha?: number): string;
13
+ export declare function roundedAlpha(alpha: number): number;
@@ -64,11 +64,17 @@ export function resolveNeutralStep(step, hue, literals = generateNeutralRampLite
64
64
  throw new Error(`Neutral step ${step} is not in the shared step table.`);
65
65
  return { l: found.l, c: found.c, h: hue };
66
66
  }
67
- export function neutralRcsFormula(step, literals) {
67
+ export function neutralRcsFormula(step, literals, alpha = 1) {
68
68
  const found = literals.find((s) => s.step === step);
69
69
  if (!found)
70
70
  throw new Error(`Neutral step ${step} is not in the shared step table.`);
71
- return `oklch(from var(--nave-color-tint) ${roundTo(found.l, 4)} ${roundTo(found.c, 4)} h / 1)`;
71
+ if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
72
+ throw new Error(`The alpha for neutral step ${step} must be a finite number from 0 to 1, got ${alpha}.`);
73
+ }
74
+ return `oklch(from var(--nave-color-tint) ${roundTo(found.l, 4)} ${roundTo(found.c, 4)} h / ${roundedAlpha(alpha)})`;
75
+ }
76
+ export function roundedAlpha(alpha) {
77
+ return roundTo(alpha, 4);
72
78
  }
73
79
  function roundTo(n, d) {
74
80
  const f = 10 ** d;
@@ -14,13 +14,15 @@ interface PipelineRecords {
14
14
  achromaticBranch: boolean;
15
15
  tintHue: number;
16
16
  }
17
+ interface ResolvedStep {
18
+ scale: 'danger' | 'neutral' | 'primary';
19
+ step: number;
20
+ alpha?: number;
21
+ }
17
22
  export interface ResolvedSlot {
18
23
  slot: string;
19
24
  branch: 'dark' | 'light';
20
- resolved: {
21
- scale: 'danger' | 'neutral' | 'primary';
22
- step: number;
23
- };
25
+ resolved: ResolvedStep;
24
26
  literal: Oklch;
25
27
  css: string;
26
28
  }
@@ -16,7 +16,13 @@ function resolveStepRef(ref, branch, isAchromaticBranch) {
16
16
  function cssForResolved(resolved, ctx) {
17
17
  if (resolved.scale === 'neutral') {
18
18
  const colour = resolveNeutralStep(resolved.step, ctx.tintHue, ctx.neutralLiterals);
19
- return { css: neutralRcsFormula(resolved.step, ctx.neutralLiterals), literal: colour };
19
+ return {
20
+ css: neutralRcsFormula(resolved.step, ctx.neutralLiterals, resolved.alpha),
21
+ literal: colour,
22
+ };
23
+ }
24
+ if (resolved.alpha !== undefined) {
25
+ throw new Error(`The ${resolved.scale} scale has no translucent form: alpha is written on neutral steps only.`);
20
26
  }
21
27
  const ramp = resolved.scale === 'primary' ? ctx.primaryRamp : ctx.dangerRamp;
22
28
  const colour = rampStep(ramp, resolved.step);
@@ -1,5 +1,6 @@
1
1
  type Scale = 'neutral' | 'primary' | 'danger';
2
2
  export type StepRef = {
3
+ alpha?: number;
3
4
  scale: Scale;
4
5
  step: number;
5
6
  } | {
@@ -7,6 +7,11 @@ export const ACHROMATIC_BRANCH_SLOTS = [
7
7
  'border.focus',
8
8
  ];
9
9
  const ref = (scale, step) => ({ scale, step });
10
+ const translucentRef = (step, alpha) => ({
11
+ scale: 'neutral',
12
+ step,
13
+ alpha,
14
+ });
10
15
  const aliasRef = (slot) => ({ alias: slot });
11
16
  export const CHROMATIC_MAPPING = {
12
17
  'surface.base': { light: ref('neutral', 0), dark: ref('neutral', 900) },
@@ -30,6 +35,7 @@ export const CHROMATIC_MAPPING = {
30
35
  'action.primary.active': { light: ref('primary', 800), dark: ref('primary', 150) },
31
36
  'on-action.primary': { light: ref('neutral', 0), dark: ref('neutral', 900) },
32
37
  'action.secondary': { light: ref('neutral', 100), dark: ref('neutral', 800) },
38
+ 'action.highlight': { light: ref('neutral', 100), dark: ref('neutral', 700) },
33
39
  'feedback.danger': { light: ref('danger', 600), dark: ref('danger', 300) },
34
40
  'feedback.danger.foreground': { light: ref('danger', 600), dark: ref('danger', 300) },
35
41
  'on-feedback.danger': { light: ref('neutral', 0), dark: ref('neutral', 900) },
@@ -48,6 +54,7 @@ export const CHROMATIC_MAPPING = {
48
54
  dark: aliasRef('feedback.warning.foreground'),
49
55
  },
50
56
  'on-feedback.info': { light: ref('neutral', 0), dark: ref('neutral', 900) },
57
+ 'backdrop.modal': { light: translucentRef(950, 0.3), dark: translucentRef(950, 0.6) },
51
58
  };
52
59
  export const ACHROMATIC_MAPPING = {
53
60
  'action.primary': { light: ref('neutral', 800), dark: ref('neutral', 200) },
@@ -1,4 +1,4 @@
1
- export declare const R6_CONSUMER_ARTIFACTS: readonly ["tokens.css", "tokens.js", "tokens.d.ts", "breakpoints.js", "breakpoints.d.ts", "palette-record.json", "build-record.json"];
1
+ export declare const CONSUMER_ARTIFACTS: readonly ["tokens.css", "tokens.js", "tokens.d.ts", "breakpoints.js", "breakpoints.d.ts", "palette-record.json", "build-record.json"];
2
2
  export interface ProvenanceArtifact {
3
3
  path: string;
4
4
  content: string;
@@ -1,4 +1,4 @@
1
- export const R6_CONSUMER_ARTIFACTS = [
1
+ export const CONSUMER_ARTIFACTS = [
2
2
  'tokens.css',
3
3
  'tokens.js',
4
4
  'tokens.d.ts',
@@ -180,6 +180,16 @@
180
180
  "$type": "color",
181
181
  "$value": "oklch(from var(--nave-color-tint) 0.27 0.0067 h / 1)"
182
182
  },
183
+ "color.action.highlight.light": {
184
+ "$type": "color",
185
+ "$value": "oklch(from var(--nave-color-tint) 0.965 0.0067 h / 1)",
186
+ "$description": "Fill for a control under the pointer, the keyboard or a press: a highlighted list or menu item, or a ghost button. Never the only cue for focus or for a state such as selected, checked, open or current, and not for disabled controls."
187
+ },
188
+ "color.action.highlight.dark": {
189
+ "$type": "color",
190
+ "$value": "oklch(from var(--nave-color-tint) 0.37 0.0067 h / 1)",
191
+ "$description": "Fill for a control under the pointer, the keyboard or a press: a highlighted list or menu item, or a ghost button. Never the only cue for focus or for a state such as selected, checked, open or current, and not for disabled controls."
192
+ },
183
193
  "color.feedback.danger.light": {
184
194
  "$type": "color",
185
195
  "$value": "oklch(0.47 0.1455 15.02)",
@@ -289,5 +299,15 @@
289
299
  "color.on-feedback.info.dark": {
290
300
  "$type": "color",
291
301
  "$value": "oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1)"
302
+ },
303
+ "color.backdrop.modal.light": {
304
+ "$type": "color",
305
+ "$value": "oklch(from var(--nave-color-tint) 0.12 0.0067 h / 0.3)",
306
+ "$description": "Translucent layer between a modal panel and the page, which it recedes. Only behind a fully modal panel; never under text, a control or a focus indicator the reader can reach."
307
+ },
308
+ "color.backdrop.modal.dark": {
309
+ "$type": "color",
310
+ "$value": "oklch(from var(--nave-color-tint) 0.12 0.0067 h / 0.6)",
311
+ "$description": "Translucent layer between a modal panel and the page, which it recedes. Only behind a fully modal panel; never under text, a control or a focus indicator the reader can reach."
292
312
  }
293
313
  }
package/dist/tokens.css CHANGED
@@ -167,6 +167,12 @@
167
167
  initial-value: 2px;
168
168
  }
169
169
 
170
+ @property --nave-border-width-mark {
171
+ syntax: '<length>';
172
+ inherits: true;
173
+ initial-value: 2px;
174
+ }
175
+
170
176
  @property --nave-border-width-lg {
171
177
  syntax: '<length>';
172
178
  inherits: true;
@@ -339,6 +345,7 @@
339
345
  --nave-border-width-none: 0px;
340
346
  --nave-border-width-sm: 1px;
341
347
  --nave-border-width-focus: 2px;
348
+ --nave-border-width-mark: 2px;
342
349
  --nave-border-width-lg: 4px;
343
350
  --nave-radius-none: 0px;
344
351
  --nave-radius-sm: 4px;
@@ -380,7 +387,7 @@
380
387
  }
381
388
  }
382
389
 
383
- /* Nave theming — generated, do not edit. See this package's README ("Theming"). */
390
+ /* Nave theming — generated, do not edit. See the @navecss/tokens README ("Theming"). */
384
391
 
385
392
  @property --nave-color-tint {
386
393
  syntax: '<color>';
@@ -416,6 +423,7 @@
416
423
  --nave-color-action-primary-active: light-dark(oklch(0.27 0.0398 186.17), oklch(0.94 0.0574 186.17));
417
424
  --nave-color-on-action-primary: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1));
418
425
  --nave-color-action-secondary: light-dark(oklch(from var(--nave-color-tint) 0.965 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.27 0.0067 h / 1));
426
+ --nave-color-action-highlight: light-dark(oklch(from var(--nave-color-tint) 0.965 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.37 0.0067 h / 1));
419
427
  --nave-color-feedback-danger: light-dark(oklch(0.47 0.1455 15.02), oklch(0.835 0.0661 15.02));
420
428
  --nave-color-feedback-danger-foreground: light-dark(oklch(0.47 0.1455 15.02), oklch(0.835 0.0661 15.02));
421
429
  --nave-color-on-feedback-danger: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1));
@@ -429,5 +437,6 @@
429
437
  --nave-color-feedback-info: light-dark(oklch(from var(--nave-color-tint) 0.47 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.835 0.0067 h / 1));
430
438
  --nave-color-feedback-info-foreground: light-dark(oklch(from var(--nave-color-tint) 0.47 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.835 0.0067 h / 1));
431
439
  --nave-color-on-feedback-info: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1));
440
+ --nave-color-backdrop-modal: light-dark(oklch(from var(--nave-color-tint) 0.12 0.0067 h / 0.3), oklch(from var(--nave-color-tint) 0.12 0.0067 h / 0.6));
432
441
  }
433
442
  }
package/dist/tokens.d.ts CHANGED
@@ -61,6 +61,7 @@ export declare const tokens: {
61
61
  '--nave-border-width-none': string
62
62
  '--nave-border-width-sm': string
63
63
  '--nave-border-width-focus': string
64
+ '--nave-border-width-mark': string
64
65
  '--nave-border-width-lg': string
65
66
  '--nave-radius-none': string
66
67
  '--nave-radius-sm': string
@@ -98,10 +99,12 @@ export declare type TokenValue = typeof tokens[TokenName]
98
99
  * `TokenName | ColorPropertyName`.
99
100
  */
100
101
  export declare type ColorPropertyName =
102
+ | '--nave-color-action-highlight'
101
103
  | '--nave-color-action-primary'
102
104
  | '--nave-color-action-primary-active'
103
105
  | '--nave-color-action-primary-hover'
104
106
  | '--nave-color-action-secondary'
107
+ | '--nave-color-backdrop-modal'
105
108
  | '--nave-color-border-control'
106
109
  | '--nave-color-border-default'
107
110
  | '--nave-color-border-disabled'
package/dist/tokens.js CHANGED
@@ -62,6 +62,7 @@ export const tokens = {
62
62
  '--nave-border-width-none': "0px",
63
63
  '--nave-border-width-sm': "1px",
64
64
  '--nave-border-width-focus': "2px",
65
+ '--nave-border-width-mark': "2px",
65
66
  '--nave-border-width-lg': "4px",
66
67
  '--nave-radius-none': "0px",
67
68
  '--nave-radius-sm': "4px",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navecss/tokens",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "DTCG 2025.10 design tokens for Nave: seed-derived semantic colours, typography, spacing, and the rest of the token layer, built by a first-party DTCG 2025.10 reader.",
5
5
  "keywords": [
6
6
  "css",
@@ -51,11 +51,11 @@
51
51
  "TOKENS.md"
52
52
  ],
53
53
  "devDependencies": {
54
- "@types/node": "^26.6.2",
55
- "@vitest/coverage-v8": "5.0.1",
54
+ "@types/node": "^26.6.4",
55
+ "@vitest/coverage-v8": "5.0.3",
56
56
  "prettier": "3.9.9",
57
57
  "typescript": "6.0.3",
58
- "vitest": "5.0.1"
58
+ "vitest": "5.0.3"
59
59
  },
60
60
  "engines": {
61
61
  "node": ">=22.18"
@@ -79,7 +79,6 @@
79
79
  "lint": "eslint build.ts generate-tokens-doc.ts src test --max-warnings 0",
80
80
  "lint:fix": "eslint build.ts generate-tokens-doc.ts src test --fix",
81
81
  "test": "vitest run",
82
- "test:coverage": "vitest run --coverage",
83
82
  "check:pack": "node ../../scripts/without-dry-run.mjs publint && node ../../scripts/without-dry-run.mjs attw --pack . --profile esm-only --exclude-entrypoints css"
84
83
  }
85
84
  }
package/tokens.json CHANGED
@@ -33,12 +33,32 @@
33
33
  {
34
34
  "against": "surface.sunken",
35
35
  "class": "text"
36
+ },
37
+ {
38
+ "against": "surface.overlay",
39
+ "class": "text"
40
+ },
41
+ {
42
+ "against": "action.highlight",
43
+ "class": "text"
36
44
  }
37
45
  ],
38
46
  "content.secondary": [
39
47
  {
40
48
  "against": "surface.base",
41
49
  "class": "text"
50
+ },
51
+ {
52
+ "against": "surface.raised",
53
+ "class": "text"
54
+ },
55
+ {
56
+ "against": "surface.overlay",
57
+ "class": "text"
58
+ },
59
+ {
60
+ "against": "action.highlight",
61
+ "class": "text"
42
62
  }
43
63
  ],
44
64
  "content.tertiary": [
@@ -49,6 +69,14 @@
49
69
  {
50
70
  "against": "surface.sunken",
51
71
  "class": "text"
72
+ },
73
+ {
74
+ "against": "surface.raised",
75
+ "class": "text"
76
+ },
77
+ {
78
+ "against": "surface.overlay",
79
+ "class": "text"
52
80
  }
53
81
  ],
54
82
  "content.inverse": [
@@ -67,6 +95,14 @@
67
95
  {
68
96
  "against": "surface.base",
69
97
  "class": "text"
98
+ },
99
+ {
100
+ "against": "surface.raised",
101
+ "class": "text"
102
+ },
103
+ {
104
+ "against": "surface.overlay",
105
+ "class": "text"
70
106
  }
71
107
  ],
72
108
  "on-action.primary": [
@@ -155,12 +191,28 @@
155
191
  {
156
192
  "against": "surface.base",
157
193
  "class": "non-text"
194
+ },
195
+ {
196
+ "against": "surface.raised",
197
+ "class": "non-text"
198
+ },
199
+ {
200
+ "against": "surface.overlay",
201
+ "class": "non-text"
158
202
  }
159
203
  ],
160
204
  "feedback.danger": [
161
205
  {
162
206
  "against": "surface.base",
163
207
  "class": "non-text"
208
+ },
209
+ {
210
+ "against": "surface.raised",
211
+ "class": "non-text"
212
+ },
213
+ {
214
+ "against": "surface.overlay",
215
+ "class": "non-text"
164
216
  }
165
217
  ],
166
218
  "feedback.warning": [
@@ -173,6 +225,18 @@
173
225
  {
174
226
  "against": "surface.base",
175
227
  "class": "non-text"
228
+ },
229
+ {
230
+ "against": "surface.base",
231
+ "class": "text"
232
+ },
233
+ {
234
+ "against": "surface.raised",
235
+ "class": "text"
236
+ },
237
+ {
238
+ "against": "surface.overlay",
239
+ "class": "text"
176
240
  }
177
241
  ]
178
242
  }
@@ -597,6 +661,14 @@
597
661
  "$type": "dimension",
598
662
  "$description": "Focus ring indicator outline width"
599
663
  },
664
+ "mark": {
665
+ "$value": {
666
+ "value": 2,
667
+ "unit": "px"
668
+ },
669
+ "$type": "dimension",
670
+ "$description": "Strokes of glyphs drawn in CSS: check marks, indeterminate bars"
671
+ },
600
672
  "lg": {
601
673
  "$value": {
602
674
  "value": 4,
@@ -626,7 +698,7 @@
626
698
  "unit": "px"
627
699
  },
628
700
  "$type": "dimension",
629
- "$description": "Inputs, buttons, tags"
701
+ "$description": "Inputs, buttons, tags, and the popovers, menus and select lists anchored to a control"
630
702
  },
631
703
  "card": {
632
704
  "$value": {