@navecss/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ATOMS.md +109 -0
- package/CONSUMER-ATOMS.md +332 -0
- package/LICENSE +21 -0
- package/README.md +264 -0
- package/dist/atomic.css +288 -0
- package/dist/atoms.d.ts +413 -0
- package/dist/atoms.js +1 -0
- package/dist/chunk-BVON7XKC.js +394 -0
- package/dist/cx.d.ts +72 -0
- package/dist/cx.js +9 -0
- package/dist/index.css +48 -0
- package/dist/layers.css +15 -0
- package/dist/no-tokens.css +52 -0
- package/dist/postcss.d.ts +64 -0
- package/dist/postcss.js +228 -0
- package/dist/reset.css +346 -0
- package/package.json +93 -0
package/ATOMS.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# ATOMS.md
|
|
2
|
+
|
|
3
|
+
Generated from `src/atoms.ts`; do not edit by hand.
|
|
4
|
+
|
|
5
|
+
Every built-in atom Nave ships: the name as written in `@nave` and `cx()`, the global class
|
|
6
|
+
it emits, the declarations it applies, and its pseudo-class, `@media` or `@container`
|
|
7
|
+
variants, if any.
|
|
8
|
+
|
|
9
|
+
## Display
|
|
10
|
+
|
|
11
|
+
| Atom | Class | Declarations | Variants |
|
|
12
|
+
| ------------- | ------------------- | ------------------------ | -------- |
|
|
13
|
+
| `flex` | `nave-flex` | `display: flex;` | — |
|
|
14
|
+
| `inlineFlex` | `nave-inline-flex` | `display: inline-flex;` | — |
|
|
15
|
+
| `grid` | `nave-grid` | `display: grid;` | — |
|
|
16
|
+
| `block` | `nave-block` | `display: block;` | — |
|
|
17
|
+
| `inlineBlock` | `nave-inline-block` | `display: inline-block;` | — |
|
|
18
|
+
| `hidden` | `nave-hidden` | `display: none;` | — |
|
|
19
|
+
|
|
20
|
+
## Flex
|
|
21
|
+
|
|
22
|
+
| Atom | Class | Declarations | Variants |
|
|
23
|
+
| ---------------- | ---------------------- | -------------------------------------- | -------- |
|
|
24
|
+
| `flexCol` | `nave-flex-col` | `flex-direction: column;` | — |
|
|
25
|
+
| `flexWrap` | `nave-flex-wrap` | `flex-wrap: wrap;` | — |
|
|
26
|
+
| `itemsCenter` | `nave-items-center` | `align-items: center;` | — |
|
|
27
|
+
| `itemsStart` | `nave-items-start` | `align-items: flex-start;` | — |
|
|
28
|
+
| `itemsEnd` | `nave-items-end` | `align-items: flex-end;` | — |
|
|
29
|
+
| `justifyCenter` | `nave-justify-center` | `justify-content: center;` | — |
|
|
30
|
+
| `justifyBetween` | `nave-justify-between` | `justify-content: space-between;` | — |
|
|
31
|
+
| `justifyEnd` | `nave-justify-end` | `justify-content: flex-end;` | — |
|
|
32
|
+
| `flexGrow` | `nave-flex-grow` | `flex-grow: 1;` | — |
|
|
33
|
+
| `flexShrink0` | `nave-flex-shrink0` | `flex-shrink: 0;` | — |
|
|
34
|
+
| `gap` | `nave-gap` | `gap: var(--nave-spacing-content-md);` | — |
|
|
35
|
+
|
|
36
|
+
## Position
|
|
37
|
+
|
|
38
|
+
| Atom | Class | Declarations | Variants |
|
|
39
|
+
| ----------- | ----------------- | --------------------- | -------- |
|
|
40
|
+
| `relative` | `nave-relative` | `position: relative;` | — |
|
|
41
|
+
| `absolute` | `nave-absolute` | `position: absolute;` | — |
|
|
42
|
+
| `insetFull` | `nave-inset-full` | `inset: 0;` | — |
|
|
43
|
+
|
|
44
|
+
## Sizing
|
|
45
|
+
|
|
46
|
+
| Atom | Class | Declarations | Variants |
|
|
47
|
+
| ------- | ------------- | --------------- | -------- |
|
|
48
|
+
| `wFull` | `nave-w-full` | `width: 100%;` | — |
|
|
49
|
+
| `hFull` | `nave-h-full` | `height: 100%;` | — |
|
|
50
|
+
| `minW0` | `nave-min-w0` | `min-width: 0;` | — |
|
|
51
|
+
|
|
52
|
+
## Typography
|
|
53
|
+
|
|
54
|
+
| Atom | Class | Declarations | Variants |
|
|
55
|
+
| ----------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
56
|
+
| `truncate` | `nave-truncate` | `overflow: hidden;`<br>`text-overflow: ellipsis;`<br>`white-space: nowrap;` | — |
|
|
57
|
+
| `srOnly` | `nave-sr-only` | `position: absolute;`<br>`width: 1px;`<br>`height: 1px;`<br>`padding: 0;`<br>`margin: -1px;`<br>`overflow: hidden;`<br>`clip-path: inset(50%);`<br>`white-space: nowrap;`<br>`border-width: 0;` | — |
|
|
58
|
+
| `srOnlyFocusable` | `nave-sr-only-focusable` | `position: absolute;`<br>`width: 1px;`<br>`height: 1px;`<br>`padding: 0;`<br>`margin: -1px;`<br>`overflow: hidden;`<br>`clip-path: inset(50%);`<br>`white-space: nowrap;`<br>`border-width: 0;` | `:focus-visible`<br>`:focus-within` |
|
|
59
|
+
| `noWrap` | `nave-no-wrap` | `white-space: nowrap;` | — |
|
|
60
|
+
| `breakWord` | `nave-break-word` | `overflow-wrap: break-word;` | — |
|
|
61
|
+
| `textLeft` | `nave-text-left` | `text-align: left;` | — |
|
|
62
|
+
| `textCenter` | `nave-text-center` | `text-align: center;` | — |
|
|
63
|
+
| `textRight` | `nave-text-right` | `text-align: right;` | — |
|
|
64
|
+
|
|
65
|
+
## Interaction
|
|
66
|
+
|
|
67
|
+
| Atom | Class | Declarations | Variants |
|
|
68
|
+
| --------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
69
|
+
| `interactive` | `nave-interactive` | `cursor: pointer;`<br>`-webkit-user-select: none;`<br>`user-select: none;`<br>`-webkit-tap-highlight-color: transparent;` | — |
|
|
70
|
+
| `focusRing` | `nave-focus-ring` | `outline: none;` | `:focus-visible` |
|
|
71
|
+
| `disabledState` | `nave-disabled-state` | — | `:disabled, [aria-disabled="true"]` |
|
|
72
|
+
|
|
73
|
+
## Visual
|
|
74
|
+
|
|
75
|
+
| Atom | Class | Declarations | Variants |
|
|
76
|
+
| ---------------- | ---------------------- | ----------------------------------------------------------------------------- | -------- |
|
|
77
|
+
| `rounded` | `nave-rounded` | `border-radius: var(--nave-radius-control);` | — |
|
|
78
|
+
| `roundedCard` | `nave-rounded-card` | `border-radius: var(--nave-radius-card);` | — |
|
|
79
|
+
| `roundedFull` | `nave-rounded-full` | `border-radius: var(--nave-radius-full);` | — |
|
|
80
|
+
| `border` | `nave-border` | `border: var(--nave-border-width-sm) solid var(--nave-color-border-default);` | — |
|
|
81
|
+
| `overflowHidden` | `nave-overflow-hidden` | `overflow: hidden;` | — |
|
|
82
|
+
| `overflowAuto` | `nave-overflow-auto` | `overflow: auto;` | — |
|
|
83
|
+
|
|
84
|
+
## Transition
|
|
85
|
+
|
|
86
|
+
| Atom | Class | Declarations | Variants |
|
|
87
|
+
| ------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
|
|
88
|
+
| `transition` | `nave-transition` | `transition-property: color, background-color, border-color, opacity, box-shadow;`<br>`transition-duration: var(--nave-motion-duration-base);`<br>`transition-timing-function: var(--nave-motion-easing-standard);` | — |
|
|
89
|
+
|
|
90
|
+
## Containment
|
|
91
|
+
|
|
92
|
+
| Atom | Class | Declarations | Variants |
|
|
93
|
+
| ----------- | ---------------- | ------------------------------ | -------- |
|
|
94
|
+
| `container` | `nave-container` | `container-type: inline-size;` | — |
|
|
95
|
+
|
|
96
|
+
## Responsive
|
|
97
|
+
|
|
98
|
+
| Atom | Class | Declarations | Variants |
|
|
99
|
+
| ----------------------- | ------------------------------- | ------------ | --------------------------- |
|
|
100
|
+
| `hidePhoneOnly` | `nave-hide-phone-only` | — | `@media (width < 37.5em)` |
|
|
101
|
+
| `hideTabletPortraitUp` | `nave-hide-tablet-portrait-up` | — | `@media (width >= 37.5em)` |
|
|
102
|
+
| `hideTabletLandscapeUp` | `nave-hide-tablet-landscape-up` | — | `@media (width >= 56.25em)` |
|
|
103
|
+
| `hideDesktopUp` | `nave-hide-desktop-up` | — | `@media (width >= 75em)` |
|
|
104
|
+
| `stackPhoneOnly` | `nave-stack-phone-only` | — | `@media (width < 37.5em)` |
|
|
105
|
+
| `wFullPhoneOnly` | `nave-w-full-phone-only` | — | `@media (width < 37.5em)` |
|
|
106
|
+
|
|
107
|
+
## Pairing notes
|
|
108
|
+
|
|
109
|
+
- `truncate`: pairs with `minW0` on a flex or grid child, or the text never has a width to truncate against
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# Consumer atoms
|
|
2
|
+
|
|
3
|
+
Nave's built-in atoms ([ATOMS.md](./ATOMS.md)) are a small set of universal utilities.
|
|
4
|
+
Your design system will accumulate patterns specific to your brand —
|
|
5
|
+
interactive behaviors, component conventions, responsive rules — that
|
|
6
|
+
don't belong in Nave's core but should be reusable across your codebase.
|
|
7
|
+
|
|
8
|
+
Consumer atoms are the answer. They extend the @nave directive with your
|
|
9
|
+
own vocabulary, follow the same AtomDefinition shape, and grow with your system.
|
|
10
|
+
|
|
11
|
+
## Defining consumer atoms
|
|
12
|
+
|
|
13
|
+
The example below imports from `@navecss/tokens`, so add it as a direct dependency
|
|
14
|
+
of your own (`pnpm add -D @navecss/tokens`). Installed only as a dependency of
|
|
15
|
+
`@navecss/core`, it may not be importable from your own config: under pnpm it is
|
|
16
|
+
not.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// src/design-system/atoms.ts
|
|
20
|
+
import type { AtomDefinition } from '@navecss/core/postcss'
|
|
21
|
+
import { media } from '@navecss/tokens/breakpoints'
|
|
22
|
+
|
|
23
|
+
export const myAtoms: Record<string, AtomDefinition> = {
|
|
24
|
+
primaryButton: {
|
|
25
|
+
declarations: {
|
|
26
|
+
background: 'var(--nave-color-action-primary)',
|
|
27
|
+
color: 'var(--nave-color-on-action-primary)',
|
|
28
|
+
padding: 'var(--nave-spacing-control-md) var(--nave-spacing-control-lg)',
|
|
29
|
+
'border-radius': 'var(--nave-radius-control)',
|
|
30
|
+
'font-weight': 'var(--nave-font-weight-medium)',
|
|
31
|
+
},
|
|
32
|
+
pseudos: {
|
|
33
|
+
':hover': {
|
|
34
|
+
background: 'var(--nave-color-action-primary-hover)',
|
|
35
|
+
},
|
|
36
|
+
':active': {
|
|
37
|
+
background: 'var(--nave-color-action-primary-active)',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
media: {
|
|
41
|
+
// Always use media strings from @navecss/tokens/breakpoints.
|
|
42
|
+
// Never write breakpoint values as magic numbers.
|
|
43
|
+
[media.phoneOnly]: {
|
|
44
|
+
declarations: { width: '100%' },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Registering with the plugin
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// vite.config.ts
|
|
55
|
+
import { defineConfig } from 'vite'
|
|
56
|
+
import { navePlugin } from '@navecss/core/postcss'
|
|
57
|
+
import { myAtoms } from './src/design-system/atoms'
|
|
58
|
+
|
|
59
|
+
export default defineConfig({
|
|
60
|
+
// your existing options, plugins included, stay as they are
|
|
61
|
+
css: { postcss: { plugins: [navePlugin({ extend: myAtoms })] } },
|
|
62
|
+
build: { cssTarget: ['chrome125', 'edge125', 'firefox128', 'safari18', 'ios18'] },
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`build.cssTarget` is the browser floor, in Vite's terms:
|
|
67
|
+
[PostCSS plugin setup](./README.md#postcss-plugin-setup) says what goes wrong
|
|
68
|
+
without it.
|
|
69
|
+
|
|
70
|
+
## Using consumer atoms
|
|
71
|
+
|
|
72
|
+
Like the rest of your component CSS, the rule goes in `@layer components.consumer`,
|
|
73
|
+
and the directive expands inside it:
|
|
74
|
+
|
|
75
|
+
```css
|
|
76
|
+
/* button.module.css */
|
|
77
|
+
@layer components.consumer {
|
|
78
|
+
.root {
|
|
79
|
+
@nave interactive focusRing transition primaryButton;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Compiles to:
|
|
85
|
+
|
|
86
|
+
```css
|
|
87
|
+
@layer components.consumer {
|
|
88
|
+
.root {
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
-webkit-user-select: none;
|
|
91
|
+
user-select: none;
|
|
92
|
+
-webkit-tap-highlight-color: transparent;
|
|
93
|
+
outline: none;
|
|
94
|
+
transition-property: color, background-color, border-color, opacity, box-shadow;
|
|
95
|
+
transition-duration: var(--nave-motion-duration-base);
|
|
96
|
+
transition-timing-function: var(--nave-motion-easing-standard);
|
|
97
|
+
background: var(--nave-color-action-primary);
|
|
98
|
+
color: var(--nave-color-on-action-primary);
|
|
99
|
+
padding: var(--nave-spacing-control-md) var(--nave-spacing-control-lg);
|
|
100
|
+
border-radius: var(--nave-radius-control);
|
|
101
|
+
font-weight: var(--nave-font-weight-medium);
|
|
102
|
+
&:focus-visible {
|
|
103
|
+
outline: var(--nave-border-width-focus) solid var(--nave-color-border-focus);
|
|
104
|
+
outline-offset: 2px;
|
|
105
|
+
}
|
|
106
|
+
&:hover {
|
|
107
|
+
background: var(--nave-color-action-primary-hover);
|
|
108
|
+
}
|
|
109
|
+
&:active {
|
|
110
|
+
background: var(--nave-color-action-primary-active);
|
|
111
|
+
}
|
|
112
|
+
@media (width < 37.5em) {
|
|
113
|
+
& {
|
|
114
|
+
width: 100%;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Rules and conventions
|
|
122
|
+
|
|
123
|
+
**Always use media strings from @navecss/tokens/breakpoints.**
|
|
124
|
+
Never write breakpoint values as magic numbers.
|
|
125
|
+
One source of truth — the token file — for every breakpoint in your system.
|
|
126
|
+
|
|
127
|
+
**Consumer atoms win on name collision with Nave atoms.**
|
|
128
|
+
Your system owns its vocabulary. If you need to override a Nave atom's
|
|
129
|
+
behaviour, name your atom the same key. Use this power deliberately.
|
|
130
|
+
|
|
131
|
+
**Consumer atoms are @nave-directive only.**
|
|
132
|
+
They do not generate global CSS classes and are not available in cx().
|
|
133
|
+
cx() is for Nave's built-in atoms: passing a name that is not one of them
|
|
134
|
+
is a TypeScript error rather than the silent pass-through it used to be.
|
|
135
|
+
The exception is a key you deliberately collided with a built-in (above):
|
|
136
|
+
cx() still accepts it and still returns the BUILT-IN atom's global class,
|
|
137
|
+
never your override, so compose that one with @nave in the rule body.
|
|
138
|
+
|
|
139
|
+
**A `pseudos` key compounds onto the element, not a descendant of it.**
|
|
140
|
+
`':hover'` compiles to `&:hover`, matching the element
|
|
141
|
+
itself. A key can also be a comma-separated selector list
|
|
142
|
+
(`':disabled, [aria-disabled="true"]'`), and every branch is anchored with
|
|
143
|
+
`&` automatically, so each branch still matches the element itself rather
|
|
144
|
+
than a descendant of it. A branch that already contains `&`
|
|
145
|
+
(`'&[aria-disabled="true"]'`, or an author-written relative branch like
|
|
146
|
+
`'.foo &'`) is left exactly as authored, since it is already relative to the
|
|
147
|
+
parent selector by construction. One deliberate consequence: **leading
|
|
148
|
+
whitespace in a key is not honoured as a descendant combinator.** `' .icon'`
|
|
149
|
+
still compounds onto the element (`&.icon`, once trimmed), it does not
|
|
150
|
+
select a `.icon` descendant — write a real descendant rule directly in your
|
|
151
|
+
CSS Module if that is what you need, not as a `pseudos` key.
|
|
152
|
+
|
|
153
|
+
**Curate your atoms.ts deliberately.**
|
|
154
|
+
Over time this file becomes the precise record of every reusable interactive
|
|
155
|
+
pattern in your design system. New team members read it to understand the
|
|
156
|
+
system's conventions. Designers reference atom names in handoff. Treat it
|
|
157
|
+
with the same care as your tokens.json.
|
|
158
|
+
|
|
159
|
+
**Mobile-first always.**
|
|
160
|
+
Follow the same convention as Nave's built-in responsive atoms.
|
|
161
|
+
Start from the smallest context and expand upward.
|
|
162
|
+
The only legitimate max-width query is phoneOnly.
|
|
163
|
+
|
|
164
|
+
## Container queries
|
|
165
|
+
|
|
166
|
+
Container queries let components respond to their container's available space
|
|
167
|
+
rather than the viewport. This is the correct tool for component-level
|
|
168
|
+
responsiveness — use media queries for page-level layout decisions.
|
|
169
|
+
|
|
170
|
+
### Establishing a containment context
|
|
171
|
+
|
|
172
|
+
Apply the built-in `container` atom to any wrapper element whose children
|
|
173
|
+
should use `@container` queries:
|
|
174
|
+
|
|
175
|
+
```css
|
|
176
|
+
/* card-wrapper.module.css */
|
|
177
|
+
@layer components.consumer {
|
|
178
|
+
.wrapper {
|
|
179
|
+
@nave container;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
<div className={styles.wrapper}>
|
|
186
|
+
<Card />
|
|
187
|
+
</div>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Consumer atoms with container queries
|
|
191
|
+
|
|
192
|
+
Define component-specific container behavior in your consumer atoms.
|
|
193
|
+
Container thresholds are component-specific — there is no universal
|
|
194
|
+
"compact" width. Choose thresholds that match your component's content.
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
// src/design-system/atoms.ts
|
|
198
|
+
import type { AtomDefinition } from '@navecss/core/postcss'
|
|
199
|
+
|
|
200
|
+
export const myAtoms: Record<string, AtomDefinition> = {
|
|
201
|
+
adaptiveCard: {
|
|
202
|
+
// Default: stacked layout (mobile-first, smallest context)
|
|
203
|
+
declarations: {
|
|
204
|
+
display: 'flex',
|
|
205
|
+
'flex-direction': 'column',
|
|
206
|
+
gap: 'var(--nave-spacing-content-md)',
|
|
207
|
+
},
|
|
208
|
+
// When container has room: switch to horizontal
|
|
209
|
+
container: {
|
|
210
|
+
'(width >= 28rem)': {
|
|
211
|
+
declarations: {
|
|
212
|
+
'flex-direction': 'row',
|
|
213
|
+
'align-items': 'center',
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
compactDataRow: {
|
|
220
|
+
declarations: {
|
|
221
|
+
display: 'grid',
|
|
222
|
+
'grid-template-columns': '1fr',
|
|
223
|
+
gap: 'var(--nave-spacing-content-sm)',
|
|
224
|
+
},
|
|
225
|
+
container: {
|
|
226
|
+
'(width >= 40rem)': {
|
|
227
|
+
declarations: {
|
|
228
|
+
'grid-template-columns': 'repeat(3, 1fr)',
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
'(width >= 60rem)': {
|
|
232
|
+
declarations: {
|
|
233
|
+
'grid-template-columns': 'repeat(5, 1fr)',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Usage
|
|
242
|
+
|
|
243
|
+
```css
|
|
244
|
+
/* card.module.css */
|
|
245
|
+
@layer components.consumer {
|
|
246
|
+
.root {
|
|
247
|
+
@nave adaptiveCard;
|
|
248
|
+
background: var(--nave-color-surface-raised);
|
|
249
|
+
border-radius: var(--nave-radius-card);
|
|
250
|
+
padding: var(--nave-spacing-content-md);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Compiles to:
|
|
256
|
+
|
|
257
|
+
```css
|
|
258
|
+
@layer components.consumer {
|
|
259
|
+
.root {
|
|
260
|
+
display: flex;
|
|
261
|
+
flex-direction: column;
|
|
262
|
+
gap: var(--nave-spacing-content-md);
|
|
263
|
+
background: var(--nave-color-surface-raised);
|
|
264
|
+
border-radius: var(--nave-radius-card);
|
|
265
|
+
padding: var(--nave-spacing-content-md);
|
|
266
|
+
@container (width >= 28rem) {
|
|
267
|
+
& {
|
|
268
|
+
flex-direction: row;
|
|
269
|
+
align-items: center;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Container query units
|
|
277
|
+
|
|
278
|
+
Inside a `@container` block, you can use container query length units
|
|
279
|
+
(`cqi`, `cqb`, `cqw`, `cqh`) to size elements relative to the container.
|
|
280
|
+
These are not atom concerns — use them directly in your CSS Module declarations.
|
|
281
|
+
|
|
282
|
+
```css
|
|
283
|
+
@layer components.consumer {
|
|
284
|
+
.title {
|
|
285
|
+
font-size: clamp(var(--nave-font-size-md), 4cqi, var(--nave-font-size-2xl));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Named containers
|
|
291
|
+
|
|
292
|
+
If you need to query a specific ancestor by name rather than the nearest
|
|
293
|
+
containment ancestor, define it directly in your CSS Module. The container
|
|
294
|
+
shorthand requires a name string that cannot be expressed as a static atom:
|
|
295
|
+
|
|
296
|
+
```css
|
|
297
|
+
/* layout.module.css */
|
|
298
|
+
@layer components.consumer {
|
|
299
|
+
.sidebar {
|
|
300
|
+
container: sidebar / inline-size;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* widget.module.css — targets the named sidebar container specifically */
|
|
305
|
+
@layer components.consumer {
|
|
306
|
+
.root {
|
|
307
|
+
/* base styles */
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* This rule cannot be expressed as a consumer atom: write it directly */
|
|
311
|
+
@container sidebar (width < 20rem) {
|
|
312
|
+
.root {
|
|
313
|
+
display: none;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Media queries vs container queries — when to use which
|
|
320
|
+
|
|
321
|
+
| Concern | Use |
|
|
322
|
+
| ------------------------------------------------- | --------------------------------- |
|
|
323
|
+
| Page skeleton — columns appearing, nav collapsing | `@media` + breakpoint atoms |
|
|
324
|
+
| User preference — dark mode, reduced motion | `@media` |
|
|
325
|
+
| Component layout — card stacking, grid reflow | `@container` + consumer atoms |
|
|
326
|
+
| Typography scaling relative to container | `@container` + cqi units |
|
|
327
|
+
| Sidebar-aware component behavior | `@container` with named container |
|
|
328
|
+
|
|
329
|
+
The clearest rule: if the component would behave differently in a sidebar
|
|
330
|
+
vs in a main column at the same viewport width, use a container query.
|
|
331
|
+
If the component always behaves the same way regardless of where it sits,
|
|
332
|
+
use a media query.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nave Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|