@navecss/core 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @navecss/core
2
2
 
3
- > Layer architecture, reset, atomic utilities, and PostCSS plugin for Nave.
3
+ > Layer architecture, reset, atomic utilities, the Vite and PostCSS plugins, a Lightning CSS adapter and the `navecss-core` command for Nave.
4
4
 
5
5
  ## Installation
6
6
 
@@ -15,23 +15,40 @@ pnpm add @navecss/core
15
15
  most of them are also derived with relative colour syntax. An older browser
16
16
  drops whichever of them it does not support, without an error: surfaces and
17
17
  text lose their colours. Your CSS build has to target the same floor, and
18
- [PostCSS plugin setup](#postcss-plugin-setup) has the line for Vite.
18
+ [Vite plugin setup](#vite-plugin-setup) has the line for Vite.
19
19
  [Why this floor](https://github.com/navecss/navecss/blob/main/docs/04-adr/0005-browser-floor.md).
20
20
  - **A resolver that reads `exports` maps.** Every entry point, the stylesheet
21
21
  included, is reachable only through the package's `exports` map: there is no
22
22
  `main` field to fall back on.
23
- - **ES modules.** `@navecss/core/cx`, `@navecss/core/atoms` and
24
- `@navecss/core/postcss` load through `import` only. A `require()` of any of
23
+ - **ES modules.** `@navecss/core/cx`, `@navecss/core/atoms`,
24
+ `@navecss/core/vite`, `@navecss/core/lightningcss` and `@navecss/core/check` load through `import` only. A `require()` of any of
25
25
  them fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`, which reads as though the
26
26
  entry point did not exist. In TypeScript, set `moduleResolution` to `bundler`,
27
27
  `node16` or `nodenext`; under `node10` their types are not found. A CommonJS
28
28
  file type-checks against them and then fails when it runs, so import them from
29
- an ES module.
29
+ an ES module. `@navecss/core/postcss` also loads through `require()` (Node
30
+ loads the ES module for you, which the Node version below includes), so a
31
+ CommonJS `postcss.config.js` can use it.
32
+ For TypeScript to check a `require()` of it, use `module` `nodenext`
33
+ (TypeScript 5.8 or later) or `node20` (5.9 or later) with `moduleResolution`
34
+ unset or `nodenext`, or `module` `preserve` with `moduleResolution` `bundler`,
35
+ or turn on `skipLibCheck`. Under `module` `node16`, or `nodenext` before 5.8,
36
+ TypeScript describes a Node that cannot `require()` an ES module and reports
37
+ an error in the package's types.
30
38
  - **Node 22.18 or later.**
31
- - **PostCSS 8, for `@nave` only.** `postcss` is an optional peer dependency, so
32
- nothing installs it for you: add it yourself if you use `@nave`. Without it,
33
- importing `@navecss/core/postcss` fails with `Cannot find package 'postcss'`.
34
- The stylesheets and `cx()` need no PostCSS.
39
+ - **A build step that resolves `@nave`: the Vite plugin, PostCSS 8, the Lightning
40
+ CSS adapter or the command.** On
41
+ Vite, `@navecss/core/vite` needs nothing installed beside Vite, and
42
+ [Vite plugin setup](#vite-plugin-setup) is its whole setup. On any other
43
+ pipeline that runs PostCSS plugins, use the PostCSS plugin: `postcss` is an
44
+ optional peer dependency, so nothing installs it for you. Add it yourself if
45
+ you use that route; without it, importing `@navecss/core/postcss` fails with
46
+ `Cannot find package 'postcss'`. [PostCSS plugin setup](#postcss-plugin-setup)
47
+ has it. A host that runs Lightning CSS itself uses
48
+ [the Lightning CSS adapter](#lightning-css-adapter-setup), which needs nothing
49
+ installed beside Lightning CSS, and a project with no bundler uses
50
+ [`navecss-core expand`](#navecss-core-expand). The stylesheets and `cx()` need
51
+ none of them.
35
52
 
36
53
  ## Setup
37
54
 
@@ -57,7 +74,10 @@ repeats the same statement, which changes nothing
57
74
 
58
75
  Your component CSS goes in `@layer components.consumer`, and a deliberate
59
76
  exception goes in `@layer overrides`, which beats every other layer. Left outside
60
- any layer, your CSS would beat all of them, `overrides` included. Do not write
77
+ any layer, your CSS would beat all of them, `overrides` included.
78
+ That is the order for normal declarations. `!important` reverses it: an `!important` in
79
+ `overrides` loses to one in any earlier layer, Nave's reset included, and one outside any
80
+ layer loses to every layered one. Do not write
61
81
  into a bare `@layer components` or `@layer tokens`: a rule written directly into a
62
82
  layer outranks everything in that layer's sublayers.
63
83
 
@@ -71,7 +91,7 @@ Every built-in atom, with the declarations it applies, is listed in
71
91
 
72
92
  ### Primary — @nave directives
73
93
 
74
- Requires the PostCSS plugin. Everything stays in CSS files.
94
+ Requires the Vite plugin or the PostCSS plugin. Everything stays in CSS files.
75
95
 
76
96
  ```css
77
97
  /* button.module.css */
@@ -94,7 +114,7 @@ floor under [Requirements](#requirements).
94
114
 
95
115
  ### Escape hatch — cx() utility
96
116
 
97
- No PostCSS required. Atom names move to JSX — autocompleted, and type-checked.
117
+ No build plugin required. Atom names move to JSX — autocompleted, and type-checked.
98
118
 
99
119
  ```tsx
100
120
  import { cx } from '@navecss/core/cx'
@@ -103,7 +123,10 @@ import styles from './button.module.css'
103
123
  ;<button className={`${cx('interactive', 'focusRing', 'transition')} ${styles.root}`} />
104
124
  ```
105
125
 
106
- Use this if you cannot or do not want to add a PostCSS plugin to your build.
126
+ Use this if you cannot or do not want to add a Vite or PostCSS plugin to your build.
127
+ With the Vite plugin, the build reads every `cx()` call to choose which atoms ship, and fails on one
128
+ whose atoms it cannot read, so a name chosen at run time goes through `cx.dynamic()`: see
129
+ [Which atoms the build ships](#which-atoms-the-build-ships).
107
130
 
108
131
  A `cx()` class is a default, not an override: it sits in the `atomic` layer,
109
132
  below your component CSS, so your own rule wins wherever the two set the same
@@ -133,8 +156,9 @@ What that buys you, and what it does not:
133
156
  _emits_ is `nave-sr-only`; neither spelling is the key), and any class that is
134
157
  not a Nave atom. **TypeScript only:
135
158
  a JavaScript consumer gets none of it.** For a build-level check that does not
136
- depend on types, use the `@nave` directive with `navePlugin()` — it fails the
137
- build on an unknown atom by default.
159
+ depend on types, use the Vite plugin, which by default fails the build on an
160
+ unknown atom in a `cx()` call, or the `@nave` directive, whose build steps all
161
+ fail on an unknown atom by default.
138
162
  - **`cx.raw()` cannot shadow one of your classes.** It never consults the atom
139
163
  map, so `cx.raw('container')` is the literal `container`. Inside `cx()` an
140
164
  atom name still resolves to that atom's global class — `cx('container')` is
@@ -142,16 +166,17 @@ What that buys you, and what it does not:
142
166
  `hidden`, `grid`, `flex`, `block`, `border`, `rounded`, `transition`,
143
167
  `relative`, `absolute`, `gap`, `truncate`, `interactive`) belong on the
144
168
  `cx.raw()` side when you mean your own.
145
- - **Nothing checks the rest of the `className` attribute.** A bare
146
- `'legacy-card'` sitting beside these calls is seen by nothing and still works.
147
- So `cx.raw()` is a **declared** escape channel, not an enforced one: its value
148
- today is that every deliberate step outside the system leaves a token you can
149
- find (`grep -r 'cx.raw'`). A lint that closes the attribute itself is not part
150
- of this release.
169
+ - **The rest of the `className` attribute is checked by
170
+ [`@navecss/eslint-plugin`](https://github.com/navecss/navecss/tree/main/packages/eslint-plugin#readme).**
171
+ A bare `'legacy-card'` sitting beside these calls is not seen by `tsc` or by
172
+ `@nave`, so `cx.raw()` is a **declared** escape channel — install that plugin
173
+ for an **enforced** one: it reports an undeclared literal class written in
174
+ `className`, and requires a reason on a `cx.raw()` call that carries one, so
175
+ a deliberate step outside the system stays a reasoned, countable call.
151
176
 
152
177
  ### Tokens only
153
178
 
154
- For library authors or teams building on top of Base UI or Radix UI.
179
+ For library authors or teams building on top of Radix UI or another headless library. On Base UI, [`@navecss/base-ui`](https://github.com/navecss/navecss/tree/main/packages/base-ui#readme) styles its components with these same tokens.
155
180
  CSS custom properties are always available — no atomic layer required.
156
181
 
157
182
  ```css
@@ -166,27 +191,366 @@ Every custom property Nave declares is in the stylesheet you already installed,
166
191
 
167
192
  ---
168
193
 
169
- ## PostCSS plugin setup
194
+ ## Setting up `@nave`
195
+
196
+ On Vite, use the Vite plugin: it is the one route this package teaches for Vite. On
197
+ Next.js, webpack, or any other pipeline that runs PostCSS plugins, use the PostCSS plugin. A host
198
+ that runs Lightning CSS directly uses [the Lightning CSS adapter](#lightning-css-adapter-setup),
199
+ and a project with no bundler uses [`navecss-core expand`](#navecss-core-expand).
200
+
201
+ ### Vite plugin setup
170
202
 
171
203
  ```ts
172
204
  // vite.config.ts
173
205
  import { defineConfig } from 'vite'
174
- import { navePlugin } from '@navecss/core/postcss'
206
+ import { navePlugin } from '@navecss/core/vite'
175
207
 
176
208
  export default defineConfig({
177
- // your existing options, plugins included, stay as they are
178
- css: { postcss: { plugins: [navePlugin()] } },
209
+ // your existing options stay as they are; add navePlugin() to your existing plugins
210
+ plugins: [navePlugin()],
179
211
  build: { cssTarget: ['chrome125', 'edge125', 'firefox128', 'safari18', 'ios18'] },
180
212
  })
181
213
  ```
182
214
 
215
+ `navePlugin()` takes the same `extend` and `onUnknown` options as the PostCSS plugin
216
+ ([Options](#options)) and returns two Vite plugins in an array, which `plugins` takes as one entry.
217
+ It has no dependency and no peer. It expands `@nave` after Vite's own CSS step has run, so a stylesheet reached only
218
+ through `@import`, a Sass file (including a `@mixin` that holds a directive), a CSS Module, an
219
+ `?inline` or `?url` import, a Vue or Svelte style block and a `.css` inside `node_modules` are all
220
+ read as the CSS they compile to, in `vite build` and in the dev server, under either
221
+ `css.transformer`. A `?raw` import returns the file's text as it is. Astro is not covered: it was
222
+ not measured at this release, and no fixture of it runs.
223
+
224
+ **It also chooses which atoms ship.** A `cx()` call in your code whose atoms the build cannot read,
225
+ such as `cx(variant)` with a prop, is an error in the dev server and fails `vite build`.
226
+ By default only the atoms Nave can read a use for are served, in the dev server as in the build. A `nave-*` class neither can see, such as one in a server template or in CMS text, or one a package listed in `keepFor` applies at run time, has no rule unless its atom is listed in `keep` or in a `keepFor` entry. Run your checks, keyboard, screen-reader and automated accessibility checks included, against a production build as well as in dev, because the two can still differ; [Which atoms the build ships](#which-atoms-the-build-ships) says what the build reads and where dev differs.
227
+
183
228
  `build.cssTarget` is the browser floor, in Vite's terms. Vite's default targets
184
229
  older browsers, and building for them gains you nothing, because the output
185
230
  still needs the floor. It does cost you something: Vite rewrites `light-dark()`
186
231
  in Nave's colours into an emulation that a `color-scheme` set from script, or on
187
- part of the page, does not switch. If your project already has a
188
- `postcss.config.js`, put `navePlugin()` there instead: Vite reads no PostCSS
189
- config file once `css.postcss` is set inline.
232
+ part of the page, does not switch. The plugin sets no floor of its own, because the floor is a
233
+ requirement of Nave's stylesheets, not of the directive: a project that never writes `@nave`
234
+ needs the same key.
235
+
236
+ **Migrating from the PostCSS plugin.** Move `navePlugin()` from `css.postcss` or
237
+ `postcss.config.js` to `plugins`, importing it from `@navecss/core/vite`. Leaving both is
238
+ harmless, because whichever pass runs second finds no directive left. Moving also changes which
239
+ atoms ship: the PostCSS plugin ships every atom, and this one chooses them from the uses it can
240
+ read, in the dev server too, so after the move a `cx()` call it cannot read fails the build, and a
241
+ Nave class it cannot see has no rule unless its atom is listed in `keep`.
242
+
243
+ **Under `css.transformer: 'lightningcss'`** the plugin expands exactly as it does under the default
244
+ transformer once both floor keys below are set. At Vite's default targets, which sit below the
245
+ floor, Lightning CSS lowers CSS nesting before any plugin runs, and two things change.
246
+ A directive written after a declaration that follows a nested rule can be moved ahead of that
247
+ declaration: in `.a { &:hover { color: red; } display: grid; @nave block; }`, `grid` wins with no
248
+ message, where the default transformer makes `block` win. A directive in a group rule nested in a
249
+ style rule (`.a { @media (...) { @nave flex; } }`) still fails the build, but the message no longer
250
+ names the `& { @nave ...; }` workaround, and its position is in the CSS as Lightning CSS rewrote
251
+ it, not in your file. The plugin also drops one message and only that one: Lightning CSS's
252
+ `Unknown at rule: @nave` warning, which Vite prints for every directive before any plugin runs.
253
+ It does so by wrapping the logger Vite resolved. Stated cost: that is a logger Vite owns, which is
254
+ your own object when you pass a `customLogger`. The filter matches case-insensitively, and an
255
+ escaped spelling (`@n\61ve`) may still warn. It fails open: if Vite changes its prefix or its
256
+ logging path the warnings come back and nothing is lost, and the end-of-build scan below catches
257
+ any directive that did survive. A warning about any other unknown at-rule is still printed. Set both
258
+ floor keys:
259
+
260
+ ```ts
261
+ // vite.config.ts, with css.transformer: 'lightningcss'
262
+ import { defineConfig } from 'vite'
263
+ import { navePlugin } from '@navecss/core/vite'
264
+
265
+ export default defineConfig({
266
+ plugins: [navePlugin()],
267
+ build: { cssTarget: ['chrome125', 'edge125', 'firefox128', 'safari18', 'ios18'] },
268
+ css: {
269
+ transformer: 'lightningcss',
270
+ lightningcss: {
271
+ targets: {
272
+ chrome: 125 << 16,
273
+ edge: 125 << 16,
274
+ firefox: 128 << 16,
275
+ safari: 18 << 16,
276
+ ios_saf: 18 << 16,
277
+ },
278
+ },
279
+ },
280
+ })
281
+ ```
282
+
283
+ `build.cssTarget` alone still rewrites `light-dark()` under that transformer, so the two keys go
284
+ together.
285
+
286
+ **The end of every build is checked.** The plugin reads the CSS files the build wrote and fails the
287
+ build if a `@nave` directive is left in one, with the same lines
288
+ [`navecss-core check`](#navecss-core-check) prints, under the plugin name `nave`. There is no
289
+ option to turn it off: a directive in shipped CSS is never wanted, and `onUnknown: 'ignore'`
290
+ does not change it. Four places are not covered. The dev server serves no bundle, so there is no
291
+ scan in dev. CSS that ends inside a JavaScript string (`?inline`) is not a CSS file the build wrote,
292
+ so it is not scanned. Files under Vite's `public/` directory are copied as they are and are not
293
+ scanned. A CSS file another plugin adds after the scan has run is not scanned either: the scan runs
294
+ in a post-ordered `generateBundle`, so it misses a file emitted from a post-ordered
295
+ `generateBundle` in a plugin listed after `navePlugin()`, and anything a plugin writes in
296
+ `writeBundle`. List `navePlugin()` after plugins that emit CSS files; a file written in
297
+ `writeBundle` stays outside the scan wherever the plugin is listed.
298
+
299
+ **A change to your atoms re-runs the stylesheets that use them.** Pass `extend` as a path to a
300
+ module (resolved from Vite's project root, whose default export is the atoms object), and the
301
+ plugin declares the file to Vite: edit it, and the dev server serves the new value with no restart,
302
+ and `vite build --watch` rebuilds. An object written inline in your config is not a file Vite can
303
+ watch.
304
+
305
+ The supported Vite range is measured, not declared: the fixtures run on Vite 8.2.1 and on the
306
+ newest 8.x at the time of each release.
307
+
308
+ #### Which atoms the build ships
309
+
310
+ The Vite plugin also chooses which of Nave's atoms reach your stylesheet. It reads the `cx()` calls
311
+ in the code the build compiles, dependencies included, and ships the rules for the atoms those calls
312
+ name, for the atom of each Nave class written as text (`nave-grid` in `index.html` or in a string in
313
+ your code, say), and for the atoms you list in `keep` and `keepFor`; `srOnlyFocusable` also ships
314
+ whenever `srOnly` does. The dev server serves the same atoms, from the code it has read. This is
315
+ what `navePlugin()` does with no options. On the CSS-first path, where `@nave` copies an atom's
316
+ declarations into your own rule, a project that never calls `cx()` and writes no Nave class ships no
317
+ rule from the atom layer.
318
+
319
+ So that nothing is dropped on a guess, a `cx()` call whose atoms the build cannot read fails the
320
+ build. `vite build` reads every module first and fails once, at the end, with a report that lists
321
+ each such use with its file, and its line and column when the source maps lead back to them, then
322
+ says how to fix them:
323
+
324
+ ```text
325
+ 2 problems in 2 files: the build cannot tell which atoms these apply, and it ships only the atoms it can read.
326
+ src/Card.tsx:7:26: cx(variant): the argument is not a literal atom name.
327
+ src/List.tsx:12:26: cx is passed to map() as a value instead of being called.
328
+ Name the atoms at the call: a string literal, a const in the same file, or a condition choosing between them, as in cx(on ? 'flex' : 'grid').
329
+ To choose by a value, call cx() once per case and pick between the results: ({ row: cx('flex'), grid: cx('grid') })[variant].
330
+ If the name comes from data the build never sees, write cx.dynamic(name) and list every atom it can take in keep in navePlugin().
331
+ Call cx() where the classes are applied, with the atom names as its arguments: the build reads calls, not a cx that is assigned, passed or spread.
332
+ ```
333
+
334
+ The dev server reports the problems in your own code one module at a time. It reads your
335
+ dependencies' `cx()` calls as well, so their atoms are served in dev as in the build, but their
336
+ problems show in `vite build` only: in dev, Vite pre-bundles dependencies into files that can mix
337
+ several packages, so a problem there cannot be traced to one package. To read those calls, the
338
+ plugin keeps `@navecss/core` itself out of Vite's dependency pre-bundling (`optimizeDeps.exclude`),
339
+ so in dev the browser loads it as a few separate modules.
340
+
341
+ A stylesheet you import with `?inline` or `?raw` becomes text in your JavaScript, which the plugin
342
+ does not filter: if it holds Nave's atom layer, every atom in that layer ships, and `vite build`
343
+ warns, naming the file. Import it without the query to have the layer filtered.
344
+
345
+ **What the build can read.** An argument whose possible values the build can list: a string
346
+ literal; `null`, `undefined`, `false` or `''`; a condition choosing between those
347
+ (`on && 'flex'`, `on ? 'flex' : 'grid'`, `a || b`, `a ?? b`); or a variable in the same file
348
+ bound to one of them and never assigned again, such as a `const`. A prop, a parameter, a variable
349
+ that is assigned again, an import from another file, an array element, an object property and a
350
+ spread are not, and `cx` itself must be called, never passed on (`list.map(cx)` fails). Types do
351
+ not count: a value typed `AtomName` is still a value the build cannot list, and a cast is gone
352
+ before the build reads the call. A string holding two names (`cx('flex gap')`) fails too: pass
353
+ each name as its own argument.
354
+
355
+ To choose by a value, such as a `variant` prop, call `cx()` once per case and pick between the
356
+ results:
357
+
358
+ ```tsx
359
+ import { cx } from '@navecss/core/cx'
360
+
361
+ const layouts = { row: cx('flex'), grid: cx('grid') }
362
+
363
+ export function List({ variant }: { variant: keyof typeof layouts }) {
364
+ return <ul className={layouts[variant]} />
365
+ }
366
+ ```
367
+
368
+ **Names chosen at run time.** When a name is known only at run time, because it comes from data,
369
+ pass it to `cx.dynamic()` and list every atom it can take in `keep`:
370
+
371
+ ```tsx
372
+ // vite.config.ts
373
+ import { defineConfig } from 'vite'
374
+ import { navePlugin } from '@navecss/core/vite'
375
+
376
+ export default defineConfig({
377
+ plugins: [navePlugin({ keep: ['flex', 'grid'] })],
378
+ })
379
+
380
+ // Block.tsx, whose layout comes from your CMS
381
+ import { cx, type AtomName } from '@navecss/core/cx'
382
+
383
+ export const Block = ({ layout }: { layout: AtomName }) => <div className={cx.dynamic(layout)} />
384
+ ```
385
+
386
+ `cx.dynamic()` takes one name. A name listed in `keep` (or in a `keepFor` list) gets its atom's
387
+ class, in dev and in the build; any other value gets none (`''`), and in dev the console names any
388
+ name it gave no class and says why. Every atom in `keep` ships, used or not. A `cx.dynamic()` call
389
+ in your code with `keep` empty is an error, in the dev server as in `vite build`, since every such
390
+ call would apply nothing. The plugin hands `cx.dynamic()` the atoms of `keep` and `keepFor` as static data, and
391
+ adds `@navecss/core` to every server environment's `resolve.noExternal`, so `cx.dynamic()` in a
392
+ server build reads them too.
393
+
394
+ **Code you import.** A module that re-exports `cx`, such as a design system's `index.ts` holding
395
+ `export { cx } from '@navecss/core/cx'`, is a build error until you list it in `cxModules`. The
396
+ build then reads a file that imports `cx` from it as if that file imported `cx` from
397
+ `@navecss/core/cx`. It follows a listed module one step: the listed module must re-export Nave's
398
+ `cx` from `@navecss/core/cx` itself, under the name `cx`. Write an entry as a path from Vite's
399
+ `root`, an alias your config defines, or a package name, which is how you list a published package
400
+ that re-exports `cx`. An import of a listed module through a spelling the build does not recognise
401
+ fails `vite build`, which names the file and says what to write instead.
402
+
403
+ ```ts
404
+ // vite.config.ts
405
+ import { defineConfig } from 'vite'
406
+ import { navePlugin } from '@navecss/core/vite'
407
+
408
+ export default defineConfig({
409
+ plugins: [navePlugin({ cxModules: ['./src/ui/index.ts'] })],
410
+ })
411
+ ```
412
+
413
+ `@navecss/eslint-plugin` has a `cxModules` setting with the same name and the same entries, so one
414
+ list can serve both, with two differences. The lint reads a relative entry from the directory it
415
+ runs in, and the build from Vite's `root`: where those differ, as in a monorepo, write the entries
416
+ as aliases or package names. And the lint resolves an import as Node's `require()` does, where the
417
+ build uses Vite's resolver: the build recognises spellings the lint cannot resolve, such as an alias
418
+ set only in your Vite config, and where the lint cannot resolve a `#` import it matches a `#` entry
419
+ written the same way, which the build never does.
420
+
421
+ A dependency whose `cx()` calls the build cannot read fails the build with the package named,
422
+ because its code is not yours to change. List the package in `keepFor`, under its name, with the
423
+ atoms its calls can produce, which its documentation should give you:
424
+
425
+ ```ts
426
+ // vite.config.ts
427
+ import { defineConfig } from 'vite'
428
+ import { navePlugin } from '@navecss/core/vite'
429
+
430
+ export default defineConfig({
431
+ plugins: [navePlugin({ keepFor: { '@acme/ui': ['flex', 'inlineFlex', 'itemsCenter'] } })],
432
+ })
433
+ ```
434
+
435
+ Those atoms ship as if they were in `keep`, beside the atoms of the package's calls the build does
436
+ read. The calls it cannot read are not changed: one that produces any other atom still puts that
437
+ atom's class on the element, and the class has no rule, in dev as in the build, unless the atom
438
+ ships for another reason. An empty list, `keepFor: { '@acme/ui': [] }`, says that the calls the
439
+ build cannot read produce no atom. A `cx.dynamic()` call in the package needs its atoms in the
440
+ list: with an empty list, or none, it fails the build. Each client build records, in the
441
+ `nave-used-atoms.json` file described below, the atoms it read from each listed package's calls,
442
+ where the calls it could not read are, and any `keepFor` key that names no package the build read.
443
+
444
+ `keepFor` stands in for the package's own calls, never for a call your code makes, so three problems
445
+ in a listed package still fail the build. A module of the package that re-exports `cx` under the
446
+ name `cx` still fails when a file of yours imports `cx` from that module itself, in an `import` or
447
+ `export ... from` statement, or a dependency that does more than re-export it imports it from there,
448
+ and the report gives the `cxModules` entry that lets the build read those calls. Through a module
449
+ that only re-exports it, such as the package's own entry file or another package, a listed module
450
+ would be two steps away, so there the report names each file of yours that takes it: import `cx`
451
+ from `@navecss/core/cx` there instead. A module that gives `cx` out under another name, as a default
452
+ or inside a namespace, still fails when your code takes that export, and the report names each file
453
+ of yours that does: import `cx` from `@navecss/core/cx` there instead. And a module of the package
454
+ that you list in `cxModules` but that does not re-export Nave's `cx` under that name still fails:
455
+ remove it from `cxModules`.
456
+
457
+ A package inside your own workspace is your code, not a dependency: Vite resolves it by its real
458
+ path, outside `node_modules`, so its calls are reported as yours, in the dev server too, and
459
+ `keepFor` does not cover it.
460
+
461
+ **What the build cannot see.**
462
+
463
+ A class the build does not read (in a server template or CMS text, assembled where the build cannot see it, or applied at run time by a package listed in `keepFor`) has no rule in the production stylesheet unless its atom is listed in `keep` or in a `keepFor` entry (`nave-sr-only-focusable` also has its rule whenever `nave-sr-only` has one). An element carrying such a class renders as if the class were absent: a `nave-sr-only` or `nave-sr-only-focusable` element is shown, a `nave-hidden` or `nave-hide-*` element is displayed, and a `nave-focus-ring` element falls back to the browser's default focus indicator. A `nave-hidden` or `nave-hide-*` element that other styles also keep out of sight (moved off-screen, made transparent or clipped) stays out of sight but can take keyboard focus and is exposed to screen readers. The dev server serves the atoms the build would ship, chosen from the code it has read, so a class the build does not read has no rule in dev either. The dev server can still keep a rule the build drops, for code it read and the build does not (such as a module the page stopped importing while the dev server ran), and lack one the build keeps, for a class rendered only on a server that runs outside the dev server's process, so a page can still look and behave differently in dev than in the build. Before you ship, run your checks, keyboard, screen-reader and automated accessibility checks included, against the production build: `vite build` then `vite preview` for a client-rendered app, or your production server for a server-rendered one.
464
+
465
+ A class your code takes at run time from `@navecss/core/atoms` (`atomClassMap[variant]`,
466
+ `toClassName('grid')`) is one the build does not read: it neither reads nor refuses it, so list its
467
+ atom in `keep`.
468
+
469
+ If your pages are rendered outside the build, by a backend that serves its own templates and uses
470
+ Vite for its assets only, the build reads none of their classes. While `keep` is empty, the plugin
471
+ warns when it has read no HTML page, no server render and no use of Nave at all: no `cx()` call, no
472
+ Nave class and no `@nave` directive. A backend whose scripts call `cx()`, or whose CSS uses `@nave`,
473
+ gets no warning, and the classes in its templates have no rule all the same. List the atoms your
474
+ templates use in `keep`, or, if they are many, ship every atom (below).
475
+
476
+ The PostCSS plugin and the Lightning CSS adapter read no `cx()` call and leave every atom in the
477
+ layer, on Next.js and webpack alike; so does a page with no bundler, which links the standalone
478
+ stylesheet.
479
+
480
+ A server build leaves packages in `node_modules` external by default, so it does not read their
481
+ code. For such a package that depends on `@navecss/core`, the build warns, naming it, unless it is
482
+ listed in `keepFor`. A listed package is still checked where a server file of yours imports from it:
483
+ taking a `cx` export the client build found in that module fails the build (with two commands, the
484
+ next client build) with the report the client would give, and where the build cannot tell what your
485
+ file takes, because no environment read the file the server loads or your file reaches the package
486
+ in a way the build cannot read (a `require()`, say, or a `#` import), it warns, naming your file and
487
+ the module. On the server, its `cx.dynamic()` calls also map through every atom rather than through
488
+ `keep`, so the class such a call renders can be an atom outside the set the build ships, its emitted
489
+ set. Where the build ships no rule for that atom, the element renders as if the class were absent;
490
+ where another use put that atom in the emitted set, the server-rendered element carries the atom's
491
+ styling, and can keep it after the page hydrates: React, for one, does not guarantee to correct an
492
+ attribute that differs between the server's markup and the client's render. Add such a package to
493
+ `ssr.noExternal` in your Vite config: the build then reads it, and its `cx.dynamic()` calls on the
494
+ server read `keep` too. Listing it in `keepFor` instead stops that warning and ships its listed
495
+ atoms, but its `cx.dynamic()` calls on the server still map through every atom, and your server
496
+ files that import from it are still checked as above.
497
+
498
+ If you build the client and the server in two commands, build the server first: `vite build --ssr`,
499
+ then `vite build`. The two share a file in Vite's cache directory, so the client build also ships
500
+ the atoms the server build read. The client build also checks what each server build recorded there
501
+ of your server files' imports from packages listed in `keepFor`, so after you change server code,
502
+ build the server again before the client. A record whose server file no longer exists is dropped;
503
+ one from a server build you no longer run stays until you delete `nave-used-server-takes.json` from
504
+ Vite's cache directory and run each server build you do. Built the other way round, a server atom
505
+ the client's CSS lacks fails the server build, which records the atom for the next client build, and
506
+ the report says how to clear it. A server build that finds the client's list already used cannot
507
+ check the CSS, so it warns instead, naming each atom the last client build did not ship, and records
508
+ them the same way.
509
+
510
+ That file is `nave-used-atoms.json` in Vite's cache directory (`node_modules/.vite/` unless you set
511
+ `cacheDir`). Each `vite build` writes its atoms there, sorted, as the `emitted` list; after a client
512
+ build, that list is the set of atoms its CSS holds, so you can diff it between builds.
513
+
514
+ **Shipping every atom.** `navePlugin({ atomic: 'all' })` reads no `cx()` call and ships every atom,
515
+ as the PostCSS plugin does. It suits a project whose Nave classes are mostly written where no build
516
+ reads them, such as a backend's own templates or HTML from a CMS, where listing them in `keep` is
517
+ not practical. It gives up what this section describes: no `cx()` call is read or checked, a name
518
+ that is no atom passes through `cx()` as text, as it does without the plugin, and `cx.dynamic()`
519
+ maps through every atom. The dev server serves every atom too, the plugin sets neither
520
+ `optimizeDeps.exclude` nor `resolve.noExternal`, no build writes `nave-used-atoms.json`, and
521
+ `keep`, `keepFor` and `cxModules` are still checked for well-formed values and otherwise do nothing.
522
+ It is also the way out that the plugin's own check names when a build's atom layer does not hold
523
+ exactly the atoms the build chose: removing the others relies on a part of Vite's CSS step that is
524
+ not a public Vite API, measured on Vite 8.2.1 and the newest 8.x at the time of each release, and on
525
+ a Vite where it does not take, every build fails that check until the plugin supports that version.
526
+
527
+ **If you publish components that call `cx()`.** Your users' builds read your published code. Write
528
+ every name your components choose at run time through `cx.dynamic()`, and every other call so the
529
+ build can read it. Then export the atoms those calls can take, so a user lists them in one line that
530
+ moves with your version:
531
+
532
+ ```ts
533
+ // in your package, published as @acme/ui/nave-atoms
534
+ import type { AtomName } from '@navecss/core/cx'
535
+
536
+ export const naveAtoms = ['flex', 'grid', 'itemsCenter'] as const satisfies readonly AtomName[]
537
+
538
+ // in your user's vite.config.ts
539
+ // import { naveAtoms } from '@acme/ui/nave-atoms'
540
+ // navePlugin({ keepFor: { '@acme/ui': naveAtoms } })
541
+ ```
542
+
543
+ A user whose app sits in the same repository as your package, as a workspace package, passes the
544
+ list to `keep` instead (`navePlugin({ keep: naveAtoms })`): the build reads a workspace package as
545
+ their own code, and `keepFor` does not reach it.
546
+
547
+ List every atom your components can apply at run time, `focusRing`, `srOnly`, `srOnlyFocusable`, `hidden` and the `hide*` atoms included where you use them: a consumer passes them to `keepFor` under your package's name, and one left out renders as if the class were absent in their production build.
548
+
549
+ ### PostCSS plugin setup
550
+
551
+ For Next.js, webpack under `postcss-loader`, and any other pipeline that runs PostCSS plugins.
552
+
553
+ Add it to your PostCSS config:
190
554
 
191
555
  ```js
192
556
  // postcss.config.js
@@ -197,31 +561,59 @@ export default {
197
561
  }
198
562
  ```
199
563
 
200
- ### Options
564
+ A CommonJS config works too. Name it `postcss.config.cjs`, or keep `.js` in a project
565
+ without `"type": "module"`:
201
566
 
202
- ```ts
203
- navePlugin({
204
- // 'error' (default) — throw, failing the build
205
- // 'warn' — log and skip
206
- // 'ignore' — silently skip
207
- onUnknown: 'error',
208
- })
567
+ ```js
568
+ // postcss.config.cjs
569
+ module.exports = {
570
+ plugins: [require('@navecss/core/postcss')()],
571
+ }
209
572
  ```
210
573
 
211
- To add atoms of your own to `@nave`, pass them as `extend`: see
212
- [CONSUMER-ATOMS.md](./CONSUMER-ATOMS.md).
213
-
214
574
  `postcss` is an optional peer dependency.
215
575
  If you are using only `cx()` or tokens, you do not need to install it.
216
576
 
577
+ #### Next.js
578
+
579
+ Next.js reads a PostCSS plugin by its package name, so give it the name as a key and do not
580
+ call `navePlugin()`:
581
+
582
+ ```js
583
+ // postcss.config.mjs
584
+ export default {
585
+ plugins: { '@navecss/core/postcss': {} },
586
+ }
587
+ ```
588
+
589
+ The `navePlugin()` form above fails on Next.js's webpack pipeline with
590
+ `An unknown PostCSS plugin was provided`: that is Next 15's default and `next build --webpack`
591
+ on Next 16. Next 16's default, Turbopack, accepts either form.
592
+
593
+ Next.js also needs your browser floor, in `package.json`:
594
+
595
+ ```json
596
+ {
597
+ "browserslist": ["chrome 125", "edge 125", "firefox 128", "safari 18", "ios_saf 18"]
598
+ }
599
+ ```
600
+
601
+ Without it, Turbopack compiles for older browsers and rewrites every `light-dark()` in your CSS,
602
+ Nave's colours included, into `--lightningcss-light` and `--lightningcss-dark` variables. Spell
603
+ the floor out as above: Next 15 rejects the shorter `baseline 2024` with `Unknown browser baseline`.
604
+
217
605
  **If your Vite config sets `css.transformer: 'lightningcss'`, this plugin never
218
606
  runs.** That option replaces Vite's CSS pipeline with Lightning CSS, which does
219
607
  not run PostCSS plugins at all: the build stays green, `@nave` reaches the
220
608
  browser as an unknown at-rule, and the browser drops it, so the rule renders
221
- with none of the declarations its atoms were going to give it. Leave the
222
- default transformer in place on a project using `@nave`.
609
+ with none of the declarations its atoms were going to give it. On Vite, use
610
+ [the Vite plugin](#vite-plugin-setup), which runs under that option; to stay on this plugin,
611
+ leave the default transformer in place. A host that runs Lightning CSS directly, not through
612
+ Vite, uses [the Lightning CSS adapter](#lightning-css-adapter-setup). Add
613
+ [`navecss-core check`](#navecss-core-check) to your build script as well, and a
614
+ build that skips the plugin this way fails instead of shipping.
223
615
 
224
- ### Plugin order
616
+ #### Plugin order
225
617
 
226
618
  Put `navePlugin()` before any autoprefixing or syntax-down-levelling plugin (`autoprefixer`,
227
619
  `postcss-preset-env`, and the like) in your `plugins` array. `navePlugin()` resolves `@nave`
@@ -231,6 +623,71 @@ ship the vendor-prefixed properties their declarations need (`interactive`'s
231
623
  `-webkit-user-select` alongside `user-select`, for one), so this is only a concern for your own
232
624
  CSS sharing the same pipeline.
233
625
 
626
+ ### Lightning CSS adapter setup
627
+
628
+ For a host that runs Lightning CSS directly: your own script, or a tool that calls its
629
+ `transform()` or `bundleAsync()`. It is not the route for Vite: on Vite, use
630
+ [the Vite plugin](#vite-plugin-setup), which also runs under `css.transformer: 'lightningcss'`.
631
+
632
+ ```js
633
+ import { readFile } from 'node:fs/promises'
634
+ import { bundleAsync, transform } from 'lightningcss'
635
+ import { navePlugin } from '@navecss/core/lightningcss'
636
+
637
+ const nave = navePlugin()
638
+
639
+ // One file, through transform(): expand it first, then pass the code and the map on.
640
+ const { code, map } = nave.expand(await readFile('src/app.css'), 'src/app.css')
641
+ const result = transform({ filename: 'src/app.css', code, inputSourceMap: map, sourceMap: true })
642
+
643
+ // An entry file and everything it @imports, through bundleAsync().
644
+ const bundled = await bundleAsync({ filename: 'src/app.css', resolver: nave.resolver })
645
+ ```
646
+
647
+ The adapter expands the text before Lightning CSS reads it, so Lightning CSS never sees a
648
+ directive: no `Unknown at rule` warning, no Lightning CSS parse error on a malformed directive, and
649
+ Nave's own messages, with the file's path and the line and column of each problem.
650
+ `expand(code, filename)` takes the text or its bytes and returns `{ code, map }`, the code as bytes
651
+ and the map as a string, ready for `transform()`'s `code` and `inputSourceMap`. `resolver.read` reads each file
652
+ `bundleAsync()` asks for and returns its expanded text, so a file reached through `@import` is
653
+ covered too. It takes `extend` (an object of your own atoms) and `onUnknown` as the other plugins
654
+ do ([Options](#options)); under `'warn'` it prints each problem with `console.warn`.
655
+
656
+ The adapter imports nothing from `lightningcss`, types included, and declares no peer: you bring
657
+ your own copy. The supported range is documented, not declared: `lightningcss` 1.24.1 and later.
658
+ Earlier releases can drop the message of an error a resolver throws during `bundleAsync()`, so a
659
+ malformed directive can fail a build with an empty error instead of Nave's message. The nested rules
660
+ the adapter writes also need CSS nesting, on by default only from 1.22 (1.20 and 1.21 cannot parse
661
+ them). The fixtures run on 1.24.1 and on the newest release at the time of each release of this
662
+ package. One cost, on the `bundleAsync()` path: `read` returns a string and no source
663
+ map, so Nave's insertions are not in the output map there. The inserted text adds no line breaks,
664
+ so line numbers hold. On the `transform()` path the map is chained through `inputSourceMap`.
665
+
666
+ ### Options
667
+
668
+ ```ts
669
+ navePlugin({
670
+ // 'error' (default) — throw, failing the build
671
+ // 'warn' — log and skip
672
+ // 'ignore' — silently skip
673
+ onUnknown: 'error',
674
+ })
675
+ ```
676
+
677
+ Under `'error'`, every problem this option covers in one stylesheet is reported
678
+ in one error, positioned at the first: its message, then
679
+ `N more in this stylesheet:` and one `line:column:` line for each of the
680
+ others. The report covers one stylesheet, so a bundler that stops at the first
681
+ failing file reports one file per build. Under `'warn'`, each problem is a
682
+ warning of its own.
683
+
684
+ To add atoms of your own to `@nave`, pass them as `extend`: see
685
+ [CONSUMER-ATOMS.md](./CONSUMER-ATOMS.md). An `extend` object written inline in
686
+ a cached host's config, or imported into it, does not invalidate that host's
687
+ cache; pass `extend` as a path to the module instead when that matters. Only
688
+ the named file is re-read and declared to the host as a dependency; a module
689
+ it imports is neither (same doc).
690
+
234
691
  ### Where `@nave` is valid
235
692
 
236
693
  `@nave` must be the direct child of a CSS rule selector block. Any rule will
@@ -250,15 +707,175 @@ failure, not a silently incomplete build):
250
707
 
251
708
  ---
252
709
 
710
+ ## `navecss-core check`
711
+
712
+ A survival check: it reads built CSS and reports every `@nave` directive that
713
+ reached it, so a missing PostCSS pipeline or a bypassed one (Vite's
714
+ `css.transformer: 'lightningcss'`, for one) fails the build instead of
715
+ shipping a page with none of the declarations its atoms were going to give
716
+ it.
717
+
718
+ ```json
719
+ {
720
+ "scripts": {
721
+ "build": "vite build && navecss-core check --source=dist"
722
+ }
723
+ }
724
+ ```
725
+
726
+ For Next.js, point it at the build's static output:
727
+
728
+ ```json
729
+ {
730
+ "scripts": {
731
+ "build": "next build && navecss-core check --source=.next/static"
732
+ }
733
+ }
734
+ ```
735
+
736
+ `--source=` is repeatable, and each value is a file or a directory read
737
+ recursively for `.css`. Exit codes:
738
+
739
+ - `0` — at least one stylesheet was read, and none held `@nave`.
740
+ - `1` — at least one stylesheet held `@nave`; the findings are printed, one
741
+ line each (file, line, column, the directive as written, and its enclosing
742
+ selector when known).
743
+ - `2` — a usage error, an unreadable `--source` path, or no stylesheet found
744
+ at all. An unreadable path gives `2` even when a stylesheet that was read
745
+ held `@nave`; those findings are still printed.
746
+
747
+ Run it from a `package.json` script, chained with `&&` after the build that
748
+ should have resolved every directive, so it reads that build's output. The
749
+ command comes with `@navecss/core`; it is not a package of its own.
750
+
751
+ ---
752
+
753
+ ## `navecss-core expand`
754
+
755
+ For a project with no bundler. It reads each stylesheet, expands every `@nave`, and writes the
756
+ result:
757
+
758
+ ```json
759
+ {
760
+ "scripts": {
761
+ "build": "navecss-core expand --source=src/app.css --out=app.css",
762
+ "watch": "navecss-core expand --source=src/app.css --out=app.css --watch"
763
+ }
764
+ }
765
+ ```
766
+
767
+ `--source=` and `--out=` are repeated as pairs, matched by order, one pair per file; unequal
768
+ counts are a usage error. `--extend=<module>` is the path to a module whose default export is
769
+ your own atoms, read the way the PostCSS plugin reads one. `--watch` rewrites each `--out` when
770
+ its source, or the `--extend` module, changes, and keeps running after a problem. Every problem
771
+ across every file is printed in one run, one report per stylesheet. Exit codes:
772
+
773
+ - `0` — every file was expanded and written.
774
+ - `1` — at least one stylesheet held a problem; the problems are printed and `--out` is not
775
+ written, for any of the pairs.
776
+ - `2` — the run could not be done as asked: a usage error (such as a missing or unmatched `--out`,
777
+ an unknown flag, or an `--out` that is also a `--source`), an `--extend` module that could not be
778
+ used, a `--source` that could not be read, an `--out` that could not be written, or, under
779
+ `--watch`, a directory that could not be watched. Nothing is written, except that files written
780
+ before an `--out` that failed stay written.
781
+
782
+ It does not resolve or inline `@import`; that would make it a CSS bundler. It
783
+ expands only the files you name with `--source`, so a stylesheet you import that
784
+ holds `@nave` needs a pair of its own. An `@import` of a relative or absolute URL
785
+ is kept as written and the file it names is not read, so its path has to work
786
+ from where the output is served, not from the source's directory. A bare import,
787
+ such as one of a package (`@import url('@navecss/core')`, which is how the
788
+ bundler route's `app.css` starts), is reported as a `bare-import` problem,
789
+ because a browser cannot load it. For Nave's stylesheets, link
790
+ `@navecss/core/standalone` instead, which has no `@import` in it, by path
791
+ (`node_modules/@navecss/core/dist/standalone.css`) or from a CDN
792
+ (`https://cdn.jsdelivr.net/npm/@navecss/core@<version>/dist/standalone.css`, with
793
+ `<version>` the version you installed), before your expanded stylesheet. For
794
+ `@navecss/core/no-tokens`, link that entry's own file, `dist/no-tokens.css`, the
795
+ same way: the standalone stylesheet holds Nave's token layer, which that entry
796
+ leaves out. [Without a bundler](https://github.com/navecss/navecss#without-a-bundler)
797
+ has the whole setup.
798
+
799
+ ---
800
+
801
+ ## Editor, linter and coding agent
802
+
803
+ `@nave` is new to these tools, so each one needs a step, and this package ships what each of
804
+ them reads.
805
+
806
+ **VS Code.** This package ships a data file that declares `@nave` to the CSS language service:
807
+
808
+ ```json
809
+ { "css.customData": ["./node_modules/@navecss/core/nave.css-data.json"] }
810
+ ```
811
+
812
+ In a monorepo, the path is relative to the folder you open, so it goes through the
813
+ `node_modules` of the package that depends on `@navecss/core`. VS Code reads the file at
814
+ startup, so reload the window once it is installed.
815
+
816
+ **Stylelint.** To have `@nave` accepted without disabling `at-rule-no-unknown` for anything
817
+ else, declare it under `languageOptions.syntax.atRules`:
818
+
819
+ ```json
820
+ { "languageOptions": { "syntax": { "atRules": { "nave": { "prelude": "<custom-ident>+" } } } } }
821
+ ```
822
+
823
+ This needs stylelint 16.17.0 or later. `@navecss/stylelint-config`'s own peer range is
824
+ `^17.0.0`; the two are stated separately because the config does more than this one line, and
825
+ this line works on its own, with no config installed, from 16.17.0 on. For a check that values
826
+ on a list of properties (colour, spacing and others) use a `var()` or an admitted keyword, extend
827
+ [`@navecss/stylelint-config`](https://github.com/navecss/navecss/tree/main/packages/stylelint-config#readme)
828
+ beside this line; it never replaces it.
829
+
830
+ **ESLint®.** For the JSX side of the same idea — a literal class in `className`, or a literal
831
+ value on a tokenized `style` property — install
832
+ [`@navecss/eslint-plugin`](https://github.com/navecss/navecss/tree/main/packages/eslint-plugin#readme):
833
+
834
+ ```js
835
+ // eslint.config.js
836
+ import nave from '@navecss/eslint-plugin'
837
+
838
+ export default [
839
+ // ...your existing config
840
+ nave.configs.recommended,
841
+ ]
842
+ ```
843
+
844
+ **Coding agents.** This package ships a guide for coding agents at `skills/navecss/SKILL.md`:
845
+ the built-in atoms, the custom properties this build emits, the layer order, and where consumer
846
+ CSS goes, for the version you have installed. No agent looks inside `node_modules` on its own,
847
+ so add a pointer to your own `AGENTS.md` (and to `CLAUDE.md` too if you have one, since Claude
848
+ Code reads `AGENTS.md` only where there is no `CLAUDE.md`):
849
+
850
+ ```md
851
+ ## Styling: NaveCSS
852
+
853
+ Before writing or changing CSS or a `className`, read
854
+ `node_modules/@navecss/core/skills/navecss/SKILL.md`, resolved from the
855
+ package that depends on `@navecss/core`. The Nave custom properties and
856
+ built-in atoms it lists are the only ones there are. Atoms this project
857
+ registers through `navePlugin({ extend })` are valid too, in `@nave` only,
858
+ never in `cx()`. If a name you need is in neither place, say so rather
859
+ than invent one.
860
+ ```
861
+
862
+ Consumers of `@navecss/tokens` alone get no guide: it ships only in this package.
863
+
864
+ ---
865
+
253
866
  ## Exports
254
867
 
255
- | Export | Description |
256
- | ------------------------- | ----------------------------------------------------------------------------------------------------- |
257
- | `@navecss/core` | CSS entry point — `@import url('@navecss/core')` |
258
- | `@navecss/core/layers` | The `@layer` order statement alone. Import it first, before any other stylesheet |
259
- | `@navecss/core/no-tokens` | Entry point for projects that generate their own Nave token layer; see the header comment in the file |
260
- | `@navecss/core/reset` | Reset stylesheet only. It reads Nave's custom properties, so it needs a token layer beside it |
261
- | `@navecss/core/atomic` | Generated atomic CSS (global `nave-` classes) |
262
- | `@navecss/core/cx` | `cx()` / `cx.raw()` utilities + `AtomName` type |
263
- | `@navecss/core/atoms` | Atom definitions + `atomClassMap` |
264
- | `@navecss/core/postcss` | PostCSS plugin — `navePlugin()` |
868
+ | Export | Description |
869
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------- |
870
+ | `@navecss/core` | CSS entry point — `@import url('@navecss/core')` |
871
+ | `@navecss/core/layers` | The `@layer` order statement alone. Import it first, before any other stylesheet |
872
+ | `@navecss/core/no-tokens` | Entry point for projects that generate their own Nave token layer; see the header comment in the file |
873
+ | `@navecss/core/reset` | Reset stylesheet only. It reads Nave's custom properties, so it needs a token layer beside it |
874
+ | `@navecss/core/atomic` | Generated atomic CSS (global `nave-` classes) |
875
+ | `@navecss/core/cx` | `cx()` / `cx.raw()` utilities + `AtomName` type |
876
+ | `@navecss/core/atoms` | Atom definitions + `atomClassMap` |
877
+ | `@navecss/core/vite` | Vite plugin — `navePlugin()` |
878
+ | `@navecss/core/lightningcss` | Lightning CSS adapter — `navePlugin()`, returning `expand` and `resolver` |
879
+ | `@navecss/core/standalone` | The whole stylesheet in one file, with no `@import`, for projects with no bundler |
880
+ | `@navecss/core/postcss` | PostCSS plugin — `navePlugin()` |
881
+ | `@navecss/core/check` | The survival check as a function — `check({ source })` |