@navecss/core 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CONSUMER-ATOMS.md CHANGED
@@ -122,20 +122,22 @@ Compiles to:
122
122
  user-select: none;
123
123
  -webkit-tap-highlight-color: transparent;
124
124
  outline: none;
125
- transition-property: color, background-color, border-color, opacity, box-shadow;
126
- transition-duration: var(--nave-motion-duration-base);
127
- transition-timing-function: var(--nave-motion-easing-standard);
128
- background: var(--nave-color-action-primary);
129
- color: var(--nave-color-on-action-primary);
130
- padding: var(--nave-spacing-control-md) var(--nave-spacing-control-lg);
131
- border-radius: var(--nave-radius-control);
132
- font-weight: var(--nave-font-weight-medium);
133
125
  &:focus-visible {
134
126
  outline-style: solid;
135
127
  outline-width: var(--nave-border-width-focus, 2px);
136
128
  outline-color: var(--nave-color-border-focus, currentColor);
137
129
  outline-offset: 2px;
138
130
  }
131
+ & {
132
+ transition-property: color, background-color, border-color, opacity, box-shadow;
133
+ transition-duration: var(--nave-motion-duration-base);
134
+ transition-timing-function: var(--nave-motion-easing-standard);
135
+ background: var(--nave-color-action-primary);
136
+ color: var(--nave-color-on-action-primary);
137
+ padding: var(--nave-spacing-control-md) var(--nave-spacing-control-lg);
138
+ border-radius: var(--nave-radius-control);
139
+ font-weight: var(--nave-font-weight-medium);
140
+ }
139
141
  &:hover {
140
142
  background: var(--nave-color-action-primary-hover);
141
143
  }
@@ -293,15 +295,17 @@ Compiles to:
293
295
  display: flex;
294
296
  flex-direction: column;
295
297
  gap: var(--nave-spacing-content-md);
296
- background: var(--nave-color-surface-raised);
297
- border-radius: var(--nave-radius-card);
298
- padding: var(--nave-spacing-content-md);
299
298
  @container (width >= 28rem) {
300
299
  & {
301
300
  flex-direction: row;
302
301
  align-items: center;
303
302
  }
304
303
  }
304
+ & {
305
+ background: var(--nave-color-surface-raised);
306
+ border-radius: var(--nave-radius-card);
307
+ padding: var(--nave-spacing-content-md);
308
+ }
305
309
  }
306
310
  }
307
311
  ```
package/LICENSE CHANGED
@@ -55,3 +55,25 @@ JavaScript under dist/: the member names of a version 3 source map, the
55
55
  layout of its mappings string, the base64 VLQ encoding of each field in it,
56
56
  and the base64 alphabet. The code implementing them is this package's own, and
57
57
  everything in this package is offered under the MIT licence above.
58
+
59
+ This package also includes material copied from or derived from mdn-data
60
+ (css/properties.json in the mdn/data repository, version 2.37.2,
61
+ https://github.com/mdn/data), which its authors dedicated to the public domain
62
+ under CC0 1.0 Universal (https://creativecommons.org/publicdomain/zero/1.0/),
63
+ and from CSS Logical Properties and Values Module Level 1, CSS Overflow Module
64
+ Level 3, CSS Overscroll Behavior Module Level 1, CSS Scroll Snap Module Level 1,
65
+ CSS Box Sizing Module Level 4, CSS Fonts Module Level 4, CSS Text Module Level
66
+ 4, CSS Gaps Module Level 1, CSS Fragmentation Module Level 4, CSS Backgrounds
67
+ and Borders Module Level 3, and CSS Cascading and Inheritance Module Level 5 (in
68
+ the w3c/csswg-drafts repository, at commit
69
+ 40527902c4d0840cdb7a808c8cd42302be1c3bc8). Copyright (c) 2026 World Wide Web
70
+ Consortium. Those specifications, and the repository carrying them, are
71
+ published under the W3C Software and Document License
72
+ (https://www.w3.org/copyright/software-license-2023/).
73
+
74
+ What is copied or derived is implemented in
75
+ src/directive/property-table.generated.ts, built into the JavaScript under
76
+ dist/: the names of CSS properties, the properties each shorthand sets, which
77
+ names are legacy name aliases of another property, and which logical and
78
+ physical properties belong to one group. The code using them is this package's
79
+ own, and everything in this package is offered under the MIT licence above.
package/README.md CHANGED
@@ -21,7 +21,7 @@ pnpm add @navecss/core
21
21
  included, is reachable only through the package's `exports` map: there is no
22
22
  `main` field to fall back on.
23
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
24
+ `@navecss/core/vite`, `@navecss/core/lightningcss`, `@navecss/core/check` and `@navecss/core/collect` 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
@@ -107,10 +107,141 @@ Requires the Vite plugin or the PostCSS plugin. Everything stays in CSS files.
107
107
  ```
108
108
 
109
109
  Declarations are inlined at build time, and nothing of `@nave` is left at run time.
110
- Pseudo-class rules (`:focus-visible` from `focusRing`), `@media` and
111
- `@container` blocks are emitted as native CSS nesting inside your rule, so they
112
- apply to every selector in a selector list. Native nesting is inside the browser
113
- floor under [Requirements](#requirements).
110
+ Pseudo-class rules (`:focus-visible` from `focusRing`), `@media` and `@container` blocks are
111
+ emitted as native CSS nesting inside your rule, each right after its own atom's declarations, so
112
+ they apply to every selector in a selector list. Native nesting is inside the browser floor under
113
+ [Requirements](#requirements), and [Order inside a rule](#order-inside-a-rule) says what wins when
114
+ your rule sets the same property.
115
+
116
+ #### Order inside a rule
117
+
118
+ `@nave a b;` expands where you write it, in the order CSS mixins will apply
119
+ `@apply --a; @apply --b;`: the declarations of `a`, then the pseudo-class,
120
+ `@media` and `@container` blocks of `a`, then the same for `b`. Whatever your rule
121
+ says after the directive comes after all of it. So of two declarations of one
122
+ property that both apply, the later one wins, unless the earlier one has the more
123
+ specific selector (an atom's pseudo-class block, such as the `:focus-visible` of
124
+ `focusRing`, does), as anywhere else in CSS.
125
+
126
+ ```css
127
+ .panel {
128
+ @nave flex hideDesktopUp;
129
+ gap: var(--nave-spacing-content-md);
130
+ }
131
+ ```
132
+
133
+ expands to:
134
+
135
+ ```css
136
+ .panel {
137
+ display: flex;
138
+ @media (width >= 75em) {
139
+ & {
140
+ display: none;
141
+ }
142
+ }
143
+ & {
144
+ gap: var(--nave-spacing-content-md);
145
+ }
146
+ }
147
+ ```
148
+
149
+ A declaration that follows a nested block is written inside `& { }`, the wrapper
150
+ Nave already uses for a directive written after a nested rule. Under a selector
151
+ list, `&` matches with the specificity of the list's most specific selector, so a
152
+ wrapped declaration can win over a rule elsewhere that a bare one would lose to;
153
+ write it above the directive if that matters.
154
+
155
+ The order you write is the order that wins:
156
+
157
+ | You write | From 75em up |
158
+ | ------------------------------------- | ---------------- |
159
+ | `@nave flex hideDesktopUp;` | hidden |
160
+ | `@nave hideDesktopUp flex;` | shown, as flex |
161
+ | `display: grid; @nave hideDesktopUp;` | hidden |
162
+ | `@nave hideDesktopUp; display: grid;` | shown, as a grid |
163
+
164
+ Name a responsive or state atom after the atoms it adjusts, and write a declaration
165
+ that should give way to an atom's variants above the directive. A declaration or a
166
+ nested block below the directive is how you customize an atom inside your own rule.
167
+
168
+ The same order applies to the focus atoms. After `@nave focusRing;`, a later
169
+ `&:focus-visible { outline-color: var(--brand) }` recolours the ring, and a later
170
+ `&:focus { outline: none }` or `&:focus-visible { outline: none }` removes it,
171
+ which leaves the element with no focus indicator from `focusRing` under keyboard
172
+ focus. After `@nave srOnlyFocusable;`, a later `&:focus-visible { position: fixed }`
173
+ places the revealed element, and a later `clip-path: inset(50%)` in the same state
174
+ hides it again, so a skip link stays hidden when it has focus. To keep the atom's
175
+ result, write the block above the directive. The order warning below reports each
176
+ of these, and says so when the ring or the reveal is lost.
177
+
178
+ **The order warning.** Where something later in the rule overrides one of an atom's
179
+ pseudo-class, `@media` or `@container` blocks with a different value, the build
180
+ prints a warning at the later content. Where moving something lets the atom win
181
+ without a new warning, it says what to move, and it always shows how to mark the
182
+ directive if the later value is meant to win:
183
+
184
+ ```text
185
+ src/panel.css:3:3: @nave: display: grid comes after "hideDesktopUp", so under @media (width >= 75em) display is grid, not none. Before @navecss/core 0.5.0 "hideDesktopUp" won, because an atom's blocks were placed last in the rule. To let "hideDesktopUp" win, move display: grid above the directive. If it is meant to win, mark the directive that names "hideDesktopUp": `@nave /* nave-allow-override: display */ hideDesktopUp;`
186
+ ```
187
+
188
+ Where no move clears the warning, as when the later value comes from another atom's
189
+ block and moving either only turns the pair round, it prints only the marker. Write
190
+ the atom that should win last, and mark the directive the warning then names.
191
+
192
+ It never fails the build, and `onUnknown` does not change it. The Vite plugin prints
193
+ it through Vite's logger, the PostCSS plugin as a PostCSS warning (and with `console.warn` on
194
+ Next.js under Turbopack, which does not print PostCSS warnings), the Lightning CSS
195
+ adapter with `console.warn`, and `navecss-core expand` on standard error, without
196
+ changing its exit code. No option turns it off for the whole project.
197
+
198
+ A directive written inside a nested rule that applies to the same element, such as
199
+ `.card { & { @nave hideDesktopUp } display: flex; }`, is checked the same way: its atoms' blocks
200
+ are compared with what follows the directive inside that nested rule, and with what each
201
+ enclosing rule holds after the nested rule. A warning of the second kind says nothing about
202
+ earlier releases, because that later content already won in them.
203
+
204
+ **Marking an override you mean.** Name the property in a comment inside the
205
+ directive:
206
+
207
+ ```css
208
+ .panel {
209
+ @nave /* nave-allow-override: display */ hideDesktopUp;
210
+ display: grid;
211
+ }
212
+ ```
213
+
214
+ The comment says that what comes after this directive's atoms (a later atom in it,
215
+ or your rule's own content) is meant to win for the properties it names. It
216
+ silences the warning for those properties on this directive and nothing else: an
217
+ override of another property, or of another directive's atoms, still warns. A
218
+ shorthand covers its longhands (`outline` covers `outline-color`). You can add a
219
+ reason after `--`: `/* nave-allow-override: display -- a grid on wide screens */`.
220
+ The head `nave-allow-override:` is matched in lower case only, as `eslint-disable`
221
+ is, so a comment that writes it in another case is not a marker: it allows nothing,
222
+ and the warning it was meant to silence still prints.
223
+ The comment never reaches your CSS. Only a comment inside the directive is read,
224
+ because some build steps remove the others before Nave sees the stylesheet (Vite
225
+ under `css.transformer: 'lightningcss'`, for one). A marker naming a property that
226
+ nothing overrides any more is reported, so remove it when you reorder. The Vite plugin under
227
+ `css.transformer: 'lightningcss'` does not report one, at any targets, because it cannot tell
228
+ whether Lightning CSS has already moved what follows the directive.
229
+
230
+ #### Output size
231
+
232
+ `@nave` copies an atom's declarations into every rule that uses it. That is the design: the directive is a build-time stand-in for native CSS mixins, whose `@apply` puts a mixin's declarations into the rule that applies it ([why](https://github.com/navecss/navecss/blob/main/docs/04-adr/0006-nave-directive-stands-in-for-css-mixins.md)). A browser that supports native mixins does that expansion itself, so such a stylesheet carries each mixin's body once, plus a short `@apply` where it is used; a build step has to write the copies out.
233
+
234
+ What the copies cost, in a model of a large app (10,000 component rules using a 25-atom design system and class names of about 20 characters, minified): about 70 percent of the raw CSS, 40 percent after gzip and 30 percent after brotli at level 11. Your app will differ, so measure your own bundle. What reduces it:
235
+
236
+ - **Compress CSS ahead of time with brotli at its highest level (11)**, instead of compressing on the fly at a lower one. In the model, level 11 sent 260 KB where level 5 sent 330 KB, which saves more than any way of removing the copies that was tried (below).
237
+ - **Split CSS by route**, so a page loads a shared part and its own rules rather than the whole app's. Split into one shared part and 40 routes, a page in the model loaded 65 KB at level 11, about a quarter, though all the parts together came to 348 KB, a third more than the single bundle.
238
+
239
+ What Nave does not do, and why:
240
+
241
+ - **It does not merge the copies into shared rules with combined selector lists.** In the model that saved 0.3 percent under brotli at level 11 and made gzip output 19 percent larger, because each selector then repeats once per atom, far apart. With 7-character class names the same merge saved 2 percent under gzip, 4 under brotli at level 11 and 12 under brotli at level 5, still less than level 11 saves over level 5. It also changed the computed style of 4 to 6 percent of the elements sampled: two atoms that set the same property stop resolving in the order you wrote them, and an atom's `@media` blocks move relative to your own declarations.
242
+ - **It does not turn your `@nave` atoms into shared classes for your markup to carry** (`cx()` does that for Nave's built-in atoms, where you choose it). That would move them into the `atomic` layer, below your component CSS, so your other rules and Nave's own component styles start beating them, and the class names it would add to your JavaScript and HTML cost about as much as the CSS it would remove under brotli, and about half as much under gzip.
243
+
244
+ The lasting fix is native CSS mixins. Once they ship in a browser and the specification settles, Nave adds a flag that writes `@mixin` and `@apply`, and once all three engines support them that becomes the default.
114
245
 
115
246
  ### Escape hatch — cx() utility
116
247
 
@@ -193,7 +324,8 @@ Every custom property Nave declares is in the stylesheet you already installed,
193
324
 
194
325
  ## Setting up `@nave`
195
326
 
196
- On Vite, use the Vite plugin: it is the one route this package teaches for Vite. On
327
+ On Vite, use the Vite plugin: it is the one route this package teaches for Vite, and for Astro,
328
+ which builds with Vite ([Astro](#astro) has its config). On
197
329
  Next.js, webpack, or any other pipeline that runs PostCSS plugins, use the PostCSS plugin. A host
198
330
  that runs Lightning CSS directly uses [the Lightning CSS adapter](#lightning-css-adapter-setup),
199
331
  and a project with no bundler uses [`navecss-core expand`](#navecss-core-expand).
@@ -218,8 +350,8 @@ It has no dependency and no peer. It expands `@nave` after Vite's own CSS step h
218
350
  through `@import`, a Sass file (including a `@mixin` that holds a directive), a CSS Module, an
219
351
  `?inline` or `?url` import, a Vue or Svelte style block and a `.css` inside `node_modules` are all
220
352
  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.
353
+ `css.transformer`. A `?raw` import returns the file's text as it is. Astro has its own setup and
354
+ its own measured list: [Astro](#astro).
223
355
 
224
356
  **It also chooses which atoms ship.** A `cx()` call in your code whose atoms the build cannot read,
225
357
  such as `cx(variant)` with a prop, is an error in the dev server and fails `vite build`.
@@ -242,10 +374,15 @@ Nave class it cannot see has no rule unless its atom is listed in `keep`.
242
374
 
243
375
  **Under `css.transformer: 'lightningcss'`** the plugin expands exactly as it does under the default
244
376
  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.
377
+ floor, Lightning CSS lowers CSS nesting before any plugin runs, and three things change.
246
378
  A directive written after a declaration that follows a nested rule can be moved ahead of that
247
379
  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
380
+ message, where the default transformer makes `block` win.
381
+ What a rule says after a directive becomes a rule of its own, after the directive's, so it
382
+ already resolves in the order [Order inside a rule](#order-inside-a-rule) describes, but no order
383
+ warning is printed for it: a later `&:focus { outline: none }` after `focusRing`, for one, removes
384
+ the ring there with no message.
385
+ A directive in a group rule nested in a
249
386
  style rule (`.a { @media (...) { @nave flex; } }`) still fails the build, but the message no longer
250
387
  names the `& { @nave ...; }` workaround, and its position is in the CSS as Lightning CSS rewrote
251
388
  it, not in your file. The plugin also drops one message and only that one: Lightning CSS's
@@ -310,12 +447,28 @@ newest 8.x at the time of each release.
310
447
  The Vite plugin also chooses which of Nave's atoms reach your stylesheet. It reads the `cx()` calls
311
448
  in the code the build compiles, dependencies included, and ships the rules for the atoms those calls
312
449
  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
450
+ your code, say) and each Nave class a CSS Module composes from global (`composes: nave-flex from global`),
451
+ and for the atoms you list in `keep` and `keepFor`; `srOnlyFocusable` also ships whenever `srOnly`
452
+ does. The dev server serves the same atoms, from the code it has read. This is
315
453
  what `navePlugin()` does with no options. On the CSS-first path, where `@nave` copies an atom's
316
454
  declarations into your own rule, a project that never calls `cx()` and writes no Nave class ships no
317
455
  rule from the atom layer.
318
456
 
457
+ In a build, the plugin also turns each `cx()` call it read into the class string the call returns, so
458
+ the map from atom names to classes does not ship: `cx('flex', 'grid')` becomes `"nave-flex nave-grid"`,
459
+ and `cx('flex', on && 'grid')` becomes `["nave-flex", on && "nave-grid"].filter(Boolean).join(" ")`,
460
+ with each argument evaluated once, in the order you wrote it. A direct `cx.raw()` call becomes the
461
+ same join, and a direct `cx.dynamic()` call a lookup in a map of the atoms in `keep` and `keepFor`.
462
+ The dev server keeps the run-time functions, which return the same strings. A bundle holds neither
463
+ the name map nor the atoms' declarations when every use of `cx` in it is one of those direct calls
464
+ and no module that imports `cx` calls `eval` directly. Two forms keep the map, since the build
465
+ cannot follow the alias: `const r = cx.raw` and `const { raw } = cx`. So does a package listed in
466
+ `keepFor` whose calls the build cannot read, because those calls stay calls to the run-time `cx()`.
467
+ A module that calls `eval` directly keeps its import of `cx`, and with it the map, whatever text it
468
+ runs (`eval('1')` too), because that text can read the module's names and the build cannot read the
469
+ text. An indirect call, `(0, eval)(text)` or `globalThis.eval(text)`, runs its text in the global
470
+ scope, where `cx` is not visible, and does not keep the import.
471
+
319
472
  So that nothing is dropped on a guess, a `cx()` call whose atoms the build cannot read fails the
320
473
  build. `vite build` reads every module first and fails once, at the end, with a report that lists
321
474
  each such use with its file, and its line and column when the source maps lead back to them, then
@@ -514,9 +667,10 @@ build, that list is the set of atoms its CSS holds, so you can diff it between b
514
667
  **Shipping every atom.** `navePlugin({ atomic: 'all' })` reads no `cx()` call and ships every atom,
515
668
  as the PostCSS plugin does. It suits a project whose Nave classes are mostly written where no build
516
669
  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
670
+ not practical. It gives up what this section describes: no `cx()` call is read, checked or turned
671
+ into its class string, so the name map ships, a name that is no atom passes through `cx()` as text,
672
+ as it does without the plugin, and `cx.dynamic()` maps through every atom. The dev server serves
673
+ every atom too, the plugin sets neither
520
674
  `optimizeDeps.exclude` nor `resolve.noExternal`, no build writes `nave-used-atoms.json`, and
521
675
  `keep`, `keepFor` and `cxModules` are still checked for well-formed values and otherwise do nothing.
522
676
  It is also the way out that the plugin's own check names when a build's atom layer does not hold
@@ -546,6 +700,82 @@ their own code, and `keepFor` does not reach it.
546
700
 
547
701
  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
702
 
703
+ #### Astro
704
+
705
+ Astro builds with Vite, so the Vite plugin is the route there too. Add it under `vite` in your
706
+ Astro config, which is where Astro takes Vite plugins, and put the floor key under `vite` as well:
707
+ Astro's own `build` has no `cssTarget`, and drops one written there without an error.
708
+
709
+ ```js
710
+ // astro.config.mjs
711
+ import { defineConfig } from 'astro/config'
712
+ import { navePlugin } from '@navecss/core/vite'
713
+
714
+ export default defineConfig({
715
+ vite: {
716
+ plugins: [navePlugin()],
717
+ build: { cssTarget: ['chrome125', 'edge125', 'firefox128', 'safari18', 'ios18'] },
718
+ },
719
+ })
720
+ ```
721
+
722
+ The build chooses which atoms ship, so run your checks, keyboard, screen-reader and automated
723
+ accessibility checks included, against a production build as well as in dev: `astro build`, then
724
+ `astro preview` or however you serve production.
725
+
726
+ Put Nave's order statement first in the `<head>` of every layout, as an inline style, ahead of
727
+ anything that can bring a style, `<ClientRouter />` included:
728
+
729
+ ```astro
730
+ ---
731
+ // src/layouts/Layout.astro
732
+ import '../styles/app.css'
733
+ ---
734
+ <html lang="en">
735
+ <head>
736
+ <style is:inline>@layer tokens.defaults, tokens.presets, reset, atomic, components.nave, components.consumer, overrides;</style>
737
+ <meta charset="utf-8" />
738
+ <!-- the rest of your head, <ClientRouter /> included -->
739
+ </head>
740
+ <body><slot /></body>
741
+ </html>
742
+ ```
743
+
744
+ Astro can place a page's or a component's own style ahead of your layout's stylesheet, and with
745
+ `<ClientRouter />` it does so whatever the page imports first. A `@layer` in that style then fixes
746
+ its layer's place before Nave's order statement does, and the order inverts with no error. Astro
747
+ leaves an `is:inline` style where you write it, so this one is the first `@layer` the page sees.
748
+ `astro dev` does not show the inversion a build makes, so check the order in a build. The statement
749
+ is the one `@navecss/core/layers` holds, and the two must stay identical. Write the rules of a
750
+ `<style>` block inside `@layer components.consumer`, as for any CSS of yours: Astro leaves them
751
+ outside every layer otherwise, where they beat all of them.
752
+
753
+ The build chooses atoms as [Which atoms the build ships](#which-atoms-the-build-ships) says, with
754
+ three things particular to Astro. Astro writes its CSS in its server build, before the browser's
755
+ build runs, so an atom named only by code the browser runs, such as a `client:only` component or a
756
+ `<script>` in an `.astro` file, fails the build: list it in `keep`. A dependency the server build
757
+ leaves external is read just as late: add it to `resolve.noExternal` under `vite`, which reaches
758
+ every environment Astro builds, where `ssr.noExternal` does not. And in `output: 'server'` with
759
+ prerendered pages, the stylesheet the on-demand pages share ships every atom.
760
+
761
+ Both end-of-build checks, the `@nave` scan and the check that the atom layer holds exactly the atoms
762
+ the build chose, also read the stylesheets Astro writes into the page instead of as files (those
763
+ under 4 kB, by default). The file a message names for one of those is the name the stylesheet had
764
+ before Astro inlined it, so no file of that name is in `dist`. In an `.astro` file, the line of a
765
+ `@nave` error counts from the start of the `<style>` block that holds it, not from the top of the
766
+ file, and its column can differ from your file's, because Astro rewrites the block's whitespace.
767
+
768
+ In `astro dev`, the first paint holds the stylesheet as Vite's own CSS step left it, every atom and
769
+ every `@nave` unexpanded, until the page's scripts load; after that the dev server serves what the
770
+ build would, as far as it has read. The build is unaffected. As on any Vite project, run your
771
+ checks, keyboard, screen-reader and automated accessibility checks included, against a production
772
+ build as well as in dev: `astro build`, then `astro preview` or however you serve production.
773
+
774
+ A fixture measures this on Astro 7.4.0, with Vite 8.3.5 underneath: `@nave` expanded in `.css`
775
+ files and in `<style>` and `<style is:global>` blocks, and `cx()` read in frontmatter, in templates
776
+ and in React islands, in `astro build` for a static site and for `output: 'server'`, and in
777
+ `astro dev`. Islands in Vue, Svelte or Solid were not measured.
778
+
549
779
  ### PostCSS plugin setup
550
780
 
551
781
  For Next.js, webpack under `postcss-loader`, and any other pipeline that runs PostCSS plugins.
@@ -602,6 +832,11 @@ Without it, Turbopack compiles for older browsers and rewrites every `light-dark
602
832
  Nave's colours included, into `--lightningcss-light` and `--lightningcss-dark` variables. Spell
603
833
  the floor out as above: Next 15 rejects the shorter `baseline 2024` with `Unknown browser baseline`.
604
834
 
835
+ **Where Nave's warnings show.** With webpack (`next build --webpack`), `next build` prints them under `Compiled with warnings`, once for each compilation that reads the stylesheet, so a CSS Module imported from a Server Component can show the same warning twice; `next dev --webpack` may not print a global stylesheet's, so read them from `next build`. Turbopack does not print PostCSS warnings, so under Turbopack the plugin prints them itself on standard error, with the file, line and column, in `next build` and `next dev` alike. Neither bundler shows them in the browser console or Next's error overlay.
836
+ A problem that [`onUnknown`](#options) covers is a warning only under `'warn'`; under the
837
+ default, `'error'`, it fails the build on either bundler. Where the warnings show was measured
838
+ on Next 16.4.1, not on Next 15.
839
+
605
840
  **If your Vite config sets `css.transformer: 'lightningcss'`, this plugin never
606
841
  runs.** That option replaces Vite's CSS pipeline with Lightning CSS, which does
607
842
  not run PostCSS plugins at all: the build stays green, `@nave` reaches the
@@ -651,7 +886,8 @@ Nave's own messages, with the file's path and the line and column of each proble
651
886
  and the map as a string, ready for `transform()`'s `code` and `inputSourceMap`. `resolver.read` reads each file
652
887
  `bundleAsync()` asks for and returns its expanded text, so a file reached through `@import` is
653
888
  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`.
889
+ do ([Options](#options)); under `'warn'` it prints each problem with `console.warn`, and prints
890
+ the [order warning](#order-inside-a-rule) the same way under every `onUnknown`.
655
891
 
656
892
  The adapter imports nothing from `lightningcss`, types included, and declares no peer: you bring
657
893
  your own copy. The supported range is documented, not declared: `lightningcss` 1.24.1 and later.
@@ -680,6 +916,21 @@ in one error, positioned at the first: its message, then
680
916
  others. The report covers one stylesheet, so a bundler that stops at the first
681
917
  failing file reports one file per build. Under `'warn'`, each problem is a
682
918
  warning of its own.
919
+ The [order warning](#order-inside-a-rule) is not one of these problems: it is printed as a
920
+ warning whatever `onUnknown` says. On Turbopack, Next 16's default bundler, which shows no
921
+ PostCSS warning, the PostCSS plugin prints each problem under `'warn'` and each order warning
922
+ to the terminal instead, as `file:line:column:` and the message
923
+ ([where Next.js shows them](#nextjs)). It knows Turbopack by the `TURBOPACK` environment
924
+ variable, which Next.js sets for it, so wherever that variable is set and not empty, under Vite
925
+ or the PostCSS CLI as well, it does the same and raises no PostCSS warning. The Vite plugin and
926
+ the Lightning CSS adapter do not read the variable. The PostCSS plugin also takes
927
+ `printWarnings` (default `true`): with `printWarnings: false` it raises each problem under
928
+ `'warn'` and each order warning as a PostCSS warning and prints none of them, whatever
929
+ `TURBOPACK` holds, for a tool that runs the plugin itself and reports the warnings it raises.
930
+ Such a tool can import `isOrderWarning(warning)` from `@navecss/core/postcss` to tell them
931
+ apart: it is `true` for an order warning and `false` for each of these problems, so the tool
932
+ can report an order warning as the plugin does, whatever `onUnknown` says, and never fail a
933
+ build on one.
683
934
 
684
935
  To add atoms of your own to `@nave`, pass them as `extend`: see
685
936
  [CONSUMER-ATOMS.md](./CONSUMER-ATOMS.md). An `extend` object written inline in
@@ -779,6 +1030,9 @@ across every file is printed in one run, one report per stylesheet. Exit codes:
779
1030
  `--watch`, a directory that could not be watched. Nothing is written, except that files written
780
1031
  before an `--out` that failed stay written.
781
1032
 
1033
+ An order warning is printed and is not a problem here: it changes neither the exit code nor what is
1034
+ written.
1035
+
782
1036
  It does not resolve or inline `@import`; that would make it a CSS bundler. It
783
1037
  expands only the files you name with `--source`, so a stylesheet you import that
784
1038
  holds `@nave` needs a pair of its own. An `@import` of a relative or absolute URL
@@ -865,17 +1119,18 @@ Consumers of `@navecss/tokens` alone get no guide: it ships only in this package
865
1119
 
866
1120
  ## Exports
867
1121
 
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 })` |
1122
+ | Export | Description |
1123
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------- |
1124
+ | `@navecss/core` | CSS entry point — `@import url('@navecss/core')` |
1125
+ | `@navecss/core/layers` | The `@layer` order statement alone. Import it first, before any other stylesheet |
1126
+ | `@navecss/core/no-tokens` | Entry point for projects that generate their own Nave token layer; see the header comment in the file |
1127
+ | `@navecss/core/reset` | Reset stylesheet only. It reads Nave's custom properties, so it needs a token layer beside it |
1128
+ | `@navecss/core/atomic` | Generated atomic CSS (global `nave-` classes) |
1129
+ | `@navecss/core/cx` | `cx()` / `cx.raw()` utilities + `AtomName` type |
1130
+ | `@navecss/core/atoms` | Atom definitions + `atomClassMap` |
1131
+ | `@navecss/core/vite` | Vite plugin — `navePlugin()` |
1132
+ | `@navecss/core/lightningcss` | Lightning CSS adapter — `navePlugin()`, returning `expand` and `resolver` |
1133
+ | `@navecss/core/standalone` | The whole stylesheet in one file, with no `@import`, for projects with no bundler |
1134
+ | `@navecss/core/postcss` | PostCSS plugin — `navePlugin()` |
1135
+ | `@navecss/core/check` | The survival check as a function — `check({ source })` |
1136
+ | `@navecss/core/collect` | Reads which atoms a module uses and trims the atom layer to them, for build tools; not meant for application code |
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The names of Nave's built-in atoms, and the class each one gets.
3
+ *
4
+ * cx() needs this and nothing else, so it lives apart from the declaration data in atoms.ts:
5
+ * a module that imports atoms.ts hands a consumer's bundler every atom's CSS declarations,
6
+ * not every bundler can drop them, and cx() would pay that for a name map.
7
+ *
8
+ * atoms.ts is checked against this list (`satisfies Record<AtomName, AtomDefinition>`), so
9
+ * an atom added there and missing here, or the other way round, fails the typecheck. Keep the
10
+ * two in the same order as well: atomic.css is written in the order of atoms.ts, the Vite
11
+ * plugin lists the atoms in the order of this list, and a test holds the two together.
12
+ */
13
+ declare const atomNames: readonly ["flex", "inlineFlex", "grid", "block", "inlineBlock", "hidden", "flexCol", "flexWrap", "itemsCenter", "itemsStart", "itemsEnd", "justifyCenter", "justifyBetween", "justifyEnd", "flexGrow", "flexShrink0", "gap", "relative", "absolute", "insetFull", "wFull", "hFull", "minW0", "truncate", "srOnly", "srOnlyFocusable", "noWrap", "breakWord", "textLeft", "textCenter", "textRight", "interactive", "focusRing", "disabledState", "rounded", "roundedCard", "roundedFull", "border", "overflowHidden", "overflowAuto", "transition", "container", "hidePhoneOnly", "hideTabletPortraitUp", "hideTabletLandscapeUp", "hideDesktopUp", "stackPhoneOnly", "wFullPhoneOnly"];
14
+ type AtomName = (typeof atomNames)[number];
15
+ /**
16
+ * Converts a camelCase atom name to its nave-kebab-case CSS class name.
17
+ * focusRing → nave-focus-ring
18
+ */
19
+ declare const toClassName: (name: AtomName) => string;
20
+ /**
21
+ * Map of atom name → global CSS class name.
22
+ * Used by cx() and the directive core's diagnostic hints.
23
+ * Only covers Nave built-in atoms — consumer atoms are @nave-directive only.
24
+ */
25
+ declare const atomClassMap: Record<AtomName, string>;
26
+
27
+ export { atomClassMap as a, toClassName as t };
28
+ export type { AtomName as A };
package/dist/atoms.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { A as AtomName, a as atomClassMap, t as toClassName } from './atom-names-B8Ud-7zD.js';
2
+
1
3
  type CSSDeclarations = Record<string, string>;
2
4
  type PseudoMap = Record<string, CSSDeclarations>;
3
5
  interface MediaBlock {
@@ -403,17 +405,6 @@ declare const atoms: {
403
405
  };
404
406
  };
405
407
  };
406
- type AtomName = keyof typeof atoms;
407
- /**
408
- * Converts a camelCase atom name to its nave-kebab-case CSS class name.
409
- * focusRing → nave-focus-ring
410
- */
411
- declare const toClassName: (name: AtomName) => string;
412
- /**
413
- * Map of atom name → global CSS class name.
414
- * Used by cx() and the directive core's diagnostic hints.
415
- * Only covers Nave built-in atoms — consumer atoms are @nave-directive only.
416
- */
417
- declare const atomClassMap: Record<AtomName, string>;
418
408
 
419
- export { type AtomDefinition, type AtomName, atomClassMap, atoms, toClassName };
409
+ export { atoms };
410
+ export type { AtomDefinition };
package/dist/atoms.js CHANGED
@@ -1 +1,2 @@
1
- export { atomClassMap, atoms, toClassName } from './chunk-LIUB4EES.js';
1
+ export { atoms } from './chunk-CUL5PLPI.js';
2
+ export { atomClassMap, toClassName } from './chunk-GZL2VW7C.js';