@sofidevo/astro-dynamic-header 3.1.0 → 4.0.1

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
@@ -2,6 +2,8 @@
2
2
 
3
3
  A dynamic, responsive header component for Astro projects. Supports floating and fullscreen layouts, multi-level dropdown navigation, native CSS variable customization, dark mode, and TypeScript — all with zero external icon dependencies.
4
4
 
5
+ As of v4.0.0 the component styles are shipped inside CSS cascade layers, so any utility class you pass to the component wins over the built-in styles **without `!important`**.
6
+
5
7
  ## Features
6
8
 
7
9
  - **Floating & Fullscreen layouts** — switch layouts with a single prop.
@@ -11,6 +13,7 @@ A dynamic, responsive header component for Astro projects. Supports floating and
11
13
  - **Inline SVG Icons** — no external CDNs, no extra network requests, no flash of missing icons.
12
14
  - **Slot Support** — inject your custom logo and header actions directly into slots.
13
15
  - **Pure CSS Customization** — customize background, blur, and colors using native CSS variables.
16
+ - **Cascade-layer friendly** — component styles live in `@layer components`, so your utility classes override them without `!important` and without `twMerge()`.
14
17
  - **Full TypeScript** — all props and config interfaces are fully typed.
15
18
 
16
19
  ### Live Demo
@@ -22,10 +25,19 @@ A dynamic, responsive header component for Astro projects. Supports floating and
22
25
  ## Installation
23
26
 
24
27
  ```bash
28
+ # npm
25
29
  npm i @sofidevo/astro-dynamic-header
30
+
31
+ # pnpm
32
+ pnpm add @sofidevo/astro-dynamic-header
33
+
34
+ # yarn
35
+ yarn add @sofidevo/astro-dynamic-header
26
36
  ```
27
37
 
28
- No external CDN scripts or stylesheet additions are required.
38
+ - Peer dependency: `astro ^7.0.0`.
39
+ - No external CDN scripts or stylesheet additions are required.
40
+ - No CSS framework is required. Tailwind, UnoCSS, or plain CSS all work.
29
41
 
30
42
  ---
31
43
 
@@ -39,15 +51,78 @@ const menuItems = [
39
51
  { link: '/about', text: 'About' },
40
52
  { link: '/contact', text: 'Contact' },
41
53
  ];
54
+
55
+ const navigation = { menuItems };
42
56
  ---
43
57
 
44
- <Header navigation={{ menuItems }}>
58
+ <Header navigation={navigation}>
45
59
  <a slot="logo" href="/">MyLogo</a>
46
60
  </Header>
47
61
  ```
48
62
 
49
63
  ---
50
64
 
65
+ ## Breaking Changes in v4.0.0
66
+
67
+ > [!WARNING]
68
+ > v4.0.0 changes how the component's CSS participates in the cascade. Read this section before upgrading from `3.x`.
69
+
70
+ ### 1. `classNames.logo` and `classNames.logoText` were removed
71
+
72
+ `HeaderClassNames` no longer accepts `logo` or `logoText`. Those keys never matched any rendered markup (the logo has always been a slot), but passing them now fails type-checking.
73
+
74
+ Before (v3) — never actually styled anything:
75
+
76
+ ```astro
77
+ <Header classNames={{ logo: "hover:opacity-80", logoText: "font-bold" }} />
78
+ ```
79
+
80
+ After (v4) — style the markup you put in the slot:
81
+
82
+ ```astro
83
+ <Header>
84
+ <a slot="logo" href="/" class="logo-link hover:opacity-80">
85
+ <span class="font-bold">MyLogo</span>
86
+ </a>
87
+ </Header>
88
+ ```
89
+
90
+ ### 2. Component styles are now scoped and layered
91
+
92
+ | | v3.x | v4.0.0 |
93
+ | --- | --- | --- |
94
+ | Style block | `<style is:inline>` (global) | `<style>` (scoped to the component) |
95
+ | Layer | none (unlayered) | `@layer components` |
96
+ | Order statement | none | `@layer theme, base, components, utilities;` |
97
+ | Override your header with utilities | required `!important` | works out of the box |
98
+ | Your unlayered CSS vs. component | component usually won (specificity + document order) | **your unlayered CSS wins** |
99
+
100
+ Two practical consequences:
101
+
102
+ - **Utilities now win.** `classNames`, Tailwind classes, and any rule you put in `@layer utilities` beat the built-in styles even at equal or lower specificity. You can delete the `!important`s you may have added.
103
+ - **Unlayered CSS now wins over the component.** Global resets, hand-written element selectors (`header { ... }`, `a { ... }`), and Tailwind v3 Preflight are emitted outside any layer, and unlayered rules beat *every* layered rule. If your reset should sit *under* the header, wrap it in `@layer base` (see the [Styling Guide](#styling-guide) and the [FAQ](#my-overrides-do-not-apply)).
104
+
105
+ ### 3. Other behavior changes
106
+
107
+ - **`theme.dark.zIndex` is now honored.** The container z-index resolves as `theme.light.zIndex ?? theme.dark.zIndex ?? 10`; previously only `light.zIndex` was read.
108
+ - **Forced theme rules no longer use `!important`.** `.header.header--force-light` / `.header.header--force-dark` rely on specificity instead, so they can be overridden by your own layered CSS if you ever need to.
109
+ - **The mobile panel's `.active` rule no longer uses `!important`**, so active-link styles are overridable like everything else.
110
+ - **`MobileNav` defaults to `type="floating"`** when rendered on its own (previously it produced an undefined modifier class).
111
+ - **Theme variable fallbacks now match `defaultThemes`** exactly (for example `rgba(255, 255, 255, 0.9)` for `--l-bg`).
112
+
113
+ ### Migration checklist
114
+
115
+ ```md
116
+ - [ ] Update the package: npm i -U @sofidevo/astro-dynamic-header
117
+ - [ ] Remove `logo` / `logoText` from `classNames` (style the `logo` slot instead).
118
+ - [ ] Remove the `!important` declarations you added to override the header (they are no longer needed).
119
+ - [ ] Move your global resets / element selectors into `@layer base`.
120
+ - [ ] Using Tailwind v3? Read "Preflight changed my nav link colors" in the FAQ.
121
+ - [ ] Setting only `dark.zIndex`? It now takes effect — double-check stacking.
122
+ ```
123
+
124
+ ---
125
+
51
126
  ## Component Props
52
127
 
53
128
  ### `<Header>`
@@ -78,41 +153,62 @@ const menuItems = [
78
153
 
79
154
  | Property | Type | Required | Description |
80
155
  | --------- | --------------------- | -------- | --------------------------------- |
81
- | `link` | `string` | ✅ | URL the item points to |
82
- | `text` | `string` | ✅ | Display label |
83
- | `submenu` | `SecondaryMenuItem[]` | — | Optional nested items (2nd level) |
156
+ | `link` | `string` | Yes | URL the item points to |
157
+ | `text` | `string` | Yes | Display label |
158
+ | `submenu` | `SecondaryMenuItem[]` | No | Optional nested items (2nd level) |
84
159
 
85
160
  ### `SecondaryMenuItem`
86
161
 
87
162
  | Property | Type | Required | Description |
88
163
  | --------- | -------------------- | -------- | --------------------------------- |
89
- | `link` | `string` | ✅ | URL the item points to |
90
- | `text` | `string` | ✅ | Display label |
91
- | `submenu` | `TertiaryMenuItem[]` | — | Optional nested items (3rd level) |
164
+ | `link` | `string` | Yes | URL the item points to |
165
+ | `text` | `string` | Yes | Display label |
166
+ | `submenu` | `TertiaryMenuItem[]` | No | Optional nested items (3rd level) |
92
167
 
93
168
  ### `TertiaryMenuItem`
94
169
 
95
170
  | Property | Type | Required | Description |
96
171
  | -------- | -------- | -------- | ---------------------- |
97
- | `link` | `string` | ✅ | URL the item points to |
98
- | `text` | `string` | ✅ | Display label |
172
+ | `link` | `string` | Yes | URL the item points to |
173
+ | `text` | `string` | Yes | Display label |
174
+
175
+ ---
176
+
177
+ ## Slots
178
+
179
+ | Slot name | Visible on | Description |
180
+ | --------- | ---------------- | ---------------------------------------------------------- |
181
+ | `logo` | Header | Render your logo exactly as you need (native HTML/widgets) |
182
+ | `actions` | Desktop + mobile | Add buttons, links, or utility widgets |
183
+
184
+ ```astro
185
+ <Header navigation={{ menuItems }}>
186
+ <a slot="logo" href="/" class="logo-link">
187
+ <img src="/logo.svg" alt="Branding" width="40" />
188
+ <span>MyBrand</span>
189
+ </a>
190
+ <div slot="actions">
191
+ <a href="/login" class="btn">Log in</a>
192
+ </div>
193
+ </Header>
194
+ ```
195
+
196
+ Slotted markup is rendered by *your* page, so your page's CSS applies to it normally. Style the logo with your own classes (this replaces the removed `classNames.logo` / `classNames.logoText`).
99
197
 
100
198
  ---
101
199
 
102
200
  ## Custom Class Names
103
201
 
104
- The `classNames` prop lets you inject CSS classes into specific structural elements.
202
+ The `classNames` prop injects CSS classes into specific structural elements.
105
203
 
106
204
  ### `HeaderClassNames`
107
205
 
108
206
  | Property | Target element | Common use cases |
109
207
  | ----------- | -------------------------------------- | ------------------------------------------------- |
110
- | `container` | Outer `<div>` wrapping the header | Positioning, padding, `z-index` |
111
- | `header` | Inner `<header>` element | Shadows, borders, transitions |
112
- | `logo` | `<a>` tag surrounding the logo slot | Hover effects, focus rings |
113
- | `logoText` | `<span>` containing the logo text | Font weight, tracking, text transforms |
208
+ | `container` | Outer `<div>` wrapping the header | Positioning, padding, outer gutters |
209
+ | `header` | Inner `<header>` element | Shadows, borders, transitions, radius, backdrop |
114
210
  | `nav` | `<div>` wrapping the desktop nav items | Spacing, alignment, responsive visibility |
115
- | `mobileNav` | Root `<nav>` of the mobile panel | Backdrop blur, custom z-index, slide-in overrides |
211
+ | `mobileNav` | Root `<nav>` of the mobile panel | Backdrop blur, slide-in overrides, panel width |
116
212
 
117
213
  ```astro
118
214
  <!-- Tailwind example -->
@@ -127,34 +223,325 @@ The `classNames` prop lets you inject CSS classes into specific structural eleme
127
223
 
128
224
  ---
129
225
 
130
- ## Slots
226
+ ## Styling Guide
131
227
 
132
- | Slot name | Visible on | Description |
133
- | --------- | ---------------- | ---------------------------------------------------------- |
134
- | `logo` | Header | Render your logo exactly as you need (native HTML/widgets) |
135
- | `actions` | Desktop + mobile | Add buttons, links, or utility widgets |
228
+ Everything below assumes you want to change how the header looks or behaves. Pick the lightest tool that solves your case:
229
+
230
+ | Goal | Use |
231
+ | --- | --- |
232
+ | One-off tweaks (shadow, border, padding, radius) | `classNames` with utilities |
233
+ | A different overall design (square, full-width, compact) | Your own CSS in `@layer utilities` |
234
+ | Restyle nav links, dropdowns, or the mobile panel | Selectors targeting the internal class hooks |
235
+ | Brand colors, blur, background | CSS variables (`--l-*` / `--d-*`) |
236
+ | Per-instance tokens or z-index | `theme` prop |
237
+ | Logo / buttons markup | Slots |
238
+
239
+ ### Style precedence
240
+
241
+ The component ships this statement ahead of its own rules:
242
+
243
+ ```css
244
+ @layer theme, base, components, utilities;
245
+ ```
246
+
247
+ Priority, lowest to highest:
248
+
249
+ ```text
250
+ base < components (this header) < utilities (your classNames/overrides) < unlayered CSS
251
+ ```
252
+
253
+ Rules of thumb:
254
+
255
+ - Put your resets and element defaults in `@layer base`.
256
+ - Put overrides for this header in `@layer utilities` (or pass them as `classNames`).
257
+ - Keep in mind that CSS **outside** any layer beats everything layered, including this component. That is the spec, not a bug.
258
+ - If you write plain CSS with no framework, declare the order statement at the top of your global stylesheet so the order is fixed before any layer is used. Tailwind v4 does this for you (`@layer theme, base, components, utilities;`).
259
+
260
+ ### Example: redesign with plain CSS
261
+
262
+ Square, full-width, no glass effect — the classic "make it look like a different component" case:
136
263
 
137
264
  ```astro
138
- <Header navigation={{ menuItems }}>
139
- <a slot="logo" href="/" class="logo-link">
140
- <img src="/logo.svg" alt="Branding" width="40" />
141
- <span>MyBrand</span>
142
- </a>
143
- <div slot="actions">
144
- <a href="/login" class="btn">Log in</a>
145
- </div>
146
- </Header>
265
+ <Header
266
+ classNames={{
267
+ container: "header-wide",
268
+ header: "header-square",
269
+ }}
270
+ />
271
+ ```
272
+
273
+ ```css
274
+ @layer utilities {
275
+ .header-wide {
276
+ padding: 0;
277
+ }
278
+
279
+ .header-square {
280
+ max-width: 100%;
281
+ border-radius: 0;
282
+ box-shadow: none;
283
+ backdrop-filter: none;
284
+ background-color: #ffffff;
285
+ border-bottom: 1px solid #e5e7eb;
286
+ padding: 0.75rem 2rem;
287
+ }
288
+ }
289
+ ```
290
+
291
+ No `!important`, and the same classes work whether you are on Tailwind or not.
292
+
293
+ ### Example: compact sticky bar
294
+
295
+ The container is `position: fixed` by default. To make the header participate in normal flow (or stick to the top while scrolling):
296
+
297
+ ```astro
298
+ <Header classNames={{ container: "nav-sticky", header: "nav-compact" }} />
299
+ ```
300
+
301
+ ```css
302
+ @layer utilities {
303
+ .nav-sticky {
304
+ position: sticky;
305
+ top: 0;
306
+ }
307
+
308
+ .nav-compact {
309
+ padding-top: 0.25rem;
310
+ padding-bottom: 0.25rem;
311
+ border-radius: 0;
312
+ }
313
+ }
314
+ ```
315
+
316
+ ### Example: override internal elements
317
+
318
+ The header renders regular DOM (no shadow root), so any selector reaches the internals. Wrap the header in a class to scope your rules to one page:
319
+
320
+ ```astro
321
+ <div class="docs-header">
322
+ <Header navigation={{ menuItems }} />
323
+ </div>
324
+ ```
325
+
326
+ ```css
327
+ @layer utilities {
328
+ /* Typography of the desktop links */
329
+ .docs-header .menu__link {
330
+ text-transform: uppercase;
331
+ letter-spacing: 0.02em;
332
+ font-size: 0.875rem;
333
+ }
334
+
335
+ /* Dropdown surfaces */
336
+ .docs-header .submenu,
337
+ .docs-header .subsubmenu {
338
+ border-radius: 12px;
339
+ padding: 0.75rem;
340
+ box-shadow: 0 12px 32px rgb(0 0 0 / 0.18);
341
+ }
342
+
343
+ /* Hover underline color */
344
+ .docs-header .menu__link::after {
345
+ background: #7c3aed;
346
+ height: 3px;
347
+ top: 30px;
348
+ }
349
+
350
+ /* Active link in the mobile panel */
351
+ .docs-header .mobile-menu__link.active {
352
+ color: #7c3aed;
353
+ }
354
+ }
355
+ ```
356
+
357
+ Because these live in `utilities`, they beat the component's `components` layer regardless of specificity.
358
+
359
+ ### Example: custom responsive breakpoint
360
+
361
+ The desktop nav hides below `768px`. To keep it visible down to `640px`:
362
+
363
+ ```css
364
+ @layer utilities {
365
+ @media (width >= 640px) and (width < 768px) {
366
+ .docs-header .header__menu {
367
+ display: flex;
368
+ }
369
+
370
+ .docs-header .hamburger {
371
+ display: none;
372
+ }
373
+ }
374
+ }
375
+ ```
376
+
377
+ Media queries live inside the rule, so you re-declare the property at the breakpoints you care about; the layer decides which declaration wins.
378
+
379
+ ### Example: dark-mode-only tweaks
380
+
381
+ ```css
382
+ @layer utilities {
383
+ :root.dark .docs-header .header {
384
+ --d-bg: rgb(17 24 39 / 0.85);
385
+ box-shadow: 0 8px 30px rgb(0 0 0 / 0.35);
386
+ }
387
+ }
388
+ ```
389
+
390
+ ### Example: colors with CSS variables
391
+
392
+ See [Customization & Theme Config](#customization--theme-config) for the full variable reference. Global (whole site):
393
+
394
+ ```css
395
+ :root {
396
+ --l-accent: #7c3aed;
397
+ --l-bg: rgb(255 255 255 / 0.85);
398
+ --d-accent: #a78bfa;
399
+ --d-bg: rgb(10 10 10 / 0.85);
400
+ }
401
+ ```
402
+
403
+ Scoped to one section (marketing page gets a purple tint, the docs stay neutral):
404
+
405
+ ```css
406
+ .marketing-hero {
407
+ --l-bg: rgb(124 58 237 / 0.14);
408
+ --l-blur: blur(28px);
409
+ }
147
410
  ```
148
411
 
412
+ ```astro
413
+ <section class="marketing-hero">
414
+ <Header navigation={{ menuItems }} />
415
+ </section>
416
+ ```
417
+
418
+ Variables are inherited, so defining them on any ancestor of the header works.
419
+
420
+ ### Example: z-index and per-instance tokens with the `theme` prop
421
+
422
+ The container renders an **inline** `style="z-index: N"`, and inline styles beat any non-`!important` declaration — so `classNames.container="z-50"` (or any z-index utility) will not win. Use the prop:
423
+
424
+ ```astro
425
+ ---
426
+ import { defaultThemes } from '@sofidevo/astro-dynamic-header';
427
+
428
+ const theme = {
429
+ light: { ...defaultThemes.light, zIndex: 60 },
430
+ dark: { ...defaultThemes.dark, zIndex: 60 },
431
+ };
432
+ ---
433
+
434
+ <Header theme={theme} classNames={{ header: "shadow-2xl" }} />
435
+ ```
436
+
437
+ > [!NOTE]
438
+ > `zIndex` resolves as `theme.light.zIndex ?? theme.dark.zIndex ?? 10`, so setting it in either block is enough. The value is a single inline number; it does not change between light and dark mode.
439
+
440
+ ### Example: mobile panel
441
+
442
+ ```astro
443
+ <Header classNames={{ mobileNav: "mobile-wide" }} />
444
+ ```
445
+
446
+ ```css
447
+ @layer utilities {
448
+ /* Full-bleed panel instead of the default over-wide slide-in */
449
+ .mobile-wide {
450
+ width: 100vw;
451
+ }
452
+
453
+ .mobile-wide.is-active {
454
+ transform: translateX(0);
455
+ }
456
+
457
+ /* Softer surface + accent links */
458
+ .mobile-wide .mobile-menu li a {
459
+ border-color: rgb(124 58 237 / 0.35);
460
+ font-size: 1.1rem;
461
+ }
462
+ }
463
+ ```
464
+
465
+ If you prefer the fullscreen slide behavior everywhere, render with `headerType="fullscreen"` — the panel then uses `translate(0)` when open.
466
+
467
+ ### Example: hamburger and icons
468
+
469
+ The hamburger lines follow the header text color (`--l-text` / `--d-text`), so recoloring the text recolors the lines:
470
+
471
+ ```css
472
+ :root {
473
+ --l-text: #111827;
474
+ --d-text: #f9fafb;
475
+ }
476
+ ```
477
+
478
+ Shape and size of the button itself:
479
+
480
+ ```astro
481
+ <Header classNames={{ header: "header-round-btn" }} />
482
+ ```
483
+
484
+ ```css
485
+ @layer utilities {
486
+ #hamburger-btn {
487
+ border-radius: 999px;
488
+ padding: 0.6rem 0.9rem;
489
+ }
490
+ }
491
+ ```
492
+
493
+ The sun and chevron icons use `currentColor`, so they follow the surrounding text color automatically. The moon glyph uses `var(--svg-color--fff, #fff)` instead, so it stays white unless you override that variable.
494
+
495
+ ### Internal class hooks
496
+
497
+ | Element | Classes / id |
498
+ | --- | --- |
499
+ | Outer wrapper | `.header__container`, `.header__container--floating`, `.header__container--fullscreen` |
500
+ | Header bar | `.header`, `.header--floating`, `.header--fullscreen`, `.header--force-light`, `.header--force-dark` |
501
+ | Desktop nav wrapper | `.nav-menu-wrapper` |
502
+ | Desktop nav | `#header-menu`, `.header__menu`, `.menu`, `.menu__item`, `.header__item`, `.menu__link` |
503
+ | Dropdowns | `.submenu`, `.subsubmenu`, `.submenu__item--secondary`, `.submenu__item--tertiary` |
504
+ | Hamburger | `#hamburger-btn`, `.hamburger`, `.hamburger-box`, `.hamburger-inner` |
505
+ | Mobile panel | `#mobile-header-menu`, `.mobile-header__menu`, `.mobile-header__menu--floating`, `.mobile-header__menu--fullscreen` |
506
+ | Mobile list | `.mobile-menu`, `.mobile-menu__link`, `.mobile-details`, `.menu__summary`, `.mobile-submenu`, `.mobile-subsubmenu` |
507
+ | Slot wrappers | `.actions-desktop`, `.actions-mobile` |
508
+
509
+ ### Styling caveats
510
+
511
+ - **z-index is inline.** Use `theme.zIndex`, not a utility class, on `container`.
512
+ - **`!important` in a layer still works** (important declarations reverse layer order), but you should not need it.
513
+ - **Unlayered CSS beats everything layered.** If a global rule seems "too strong", that is why — move it into `@layer base`.
514
+ - **Astro scopes component styles with `data-astro-cid-*` attributes.** Your selectors do not need them; plain class selectors work.
515
+
149
516
  ---
150
517
 
151
518
  ## Customization & Theme Config
152
519
 
153
520
  You can fully customize the color scheme using **CSS Custom Properties** (recommended) or the `theme` prop.
154
521
 
155
- ### Option 1: Native CSS Variables (Recommended)
522
+ ### CSS variable reference
523
+
524
+ Input variables (set them wherever the header lives — `:root`, a wrapper, or the `theme` prop):
156
525
 
157
- Set CSS variables globally in your `:root` style block:
526
+ | Variable | Used for | Light default | Dark default |
527
+ | --- | --- | --- | --- |
528
+ | `--l-bg` / `--d-bg` | Header background (translucent) | `rgba(255, 255, 255, 0.9)` | `#0d0d0dcc` |
529
+ | `--l-bg-opaque` / `--d-bg-opaque` | Solid background of dropdowns and the mobile panel | `rgb(255, 255, 255)` | `#0d0d0d` |
530
+ | `--l-text` / `--d-text` | Text, hamburger lines, icons | `#1a1a1a` | `#ffffff` |
531
+ | `--l-accent` / `--d-accent` | Hover underline, active links, dashed borders | `#3e1c71` | `#00ffff` |
532
+ | `--l-blur` / `--d-blur` | `backdrop-filter` value | `blur(20px)` | `blur(20px)` |
533
+
534
+ Derived variables (resolved by the component per theme state; override them only if you need to target internals directly):
535
+
536
+ | Variable | Resolves to |
537
+ | --- | --- |
538
+ | `--bg-color` | `--l-bg` or `--d-bg` |
539
+ | `--bg-color-opaque` | `--l-bg-opaque` or `--d-bg-opaque` |
540
+ | `--text-color` | `--l-text` or `--d-text` |
541
+ | `--accent-color` | `--l-accent` or `--d-accent` |
542
+ | `--backdrop-blur` | `--l-blur` or `--d-blur` |
543
+
544
+ ### Option 1: Native CSS Variables (Recommended)
158
545
 
159
546
  ```css
160
547
  :root {
@@ -171,12 +558,11 @@ Set CSS variables globally in your `:root` style block:
171
558
  --d-bg-opaque: #0a0a0a;
172
559
  --d-text: #f5f5f5;
173
560
  --d-blur: blur(20px);
174
-
175
- /* Hamburger lines override */
176
- --color-hamburger-lines: #7c3aed;
177
561
  }
178
562
  ```
179
563
 
564
+ The hamburger lines and the sun/chevron icons follow `--l-text` / `--d-text`, so text color drives them too. The moon glyph keeps its own white fill (`--svg-color--fff`, default `#fff`).
565
+
180
566
  ### Option 2: JS Theme Prop
181
567
 
182
568
  ```astro
@@ -199,6 +585,8 @@ const theme = {
199
585
  <Header theme={theme} />
200
586
  ```
201
587
 
588
+ The prop writes the variables as inline styles on the header element, so it wins over `:root` values for that instance.
589
+
202
590
  > [!IMPORTANT]
203
591
  > When using transparent backgrounds, always supply a solid fallback in `backgroundColorOpaque`. Submenus and mobile panels utilize this solid color to prevent visual glitches with nested blur effects.
204
592
 
@@ -239,6 +627,20 @@ const classNames: HeaderClassNames = {
239
627
 
240
628
  ---
241
629
 
630
+ ## Development
631
+
632
+ ```bash
633
+ pnpm install
634
+ pnpm dev # demo at localhost:4321
635
+ pnpm check # astro check (0 errors expected)
636
+ pnpm test # vitest: unit + rendering + built-CSS tests
637
+ pnpm build # builds the demo site
638
+ ```
639
+
640
+ The test suite runs in two Vitest projects: `dom` (jsdom, exercises the extracted behaviour scripts) and `astro` (Node, renders `Header.astro` through Astro's Container API and asserts the built CSS contract — layer order, rules inside `@layer components`, no `!important`). See `tests/README.md`.
641
+
642
+ ---
643
+
242
644
  ## Troubleshooting & FAQ
243
645
 
244
646
  ### Import issues
@@ -257,16 +659,50 @@ import MobileNav from '@sofidevo/astro-dynamic-header/MobileNav';
257
659
  import ChevronIcon from '@sofidevo/astro-dynamic-header/ChevronIcon';
258
660
  ```
259
661
 
260
- ### Acordion icons not showing up
662
+ ### My overrides do not apply
663
+
664
+ Check these in order:
665
+
666
+ 1. **Which layer is your rule in?** Overrides belong in `@layer utilities`, or in a class passed through `classNames`. Rules in `@layer base` (and your resets) lose to the component by design.
667
+ 2. **Are you fighting the inline z-index?** `container` always renders `style="z-index: N"`. Use `theme.zIndex` instead of a z-index utility.
668
+ 3. **Are you selecting the right hook?** See the [internal class hooks](#internal-class-hooks) table; plain class selectors are enough (you do not need `data-astro-cid-*`).
669
+ 4. **Is an inline style winning?** Inline styles beat every non-`!important` declaration, layered or not. The header renders inline styles for `z-index` and for any `theme` values you pass.
670
+ 5. **Tailwind v4?** Its layer order matches this component exactly, so utilities work automatically. Make sure `@import "tailwindcss"` comes first in your entry CSS.
671
+
672
+ ### Preflight changed my nav link colors (Tailwind v3)
673
+
674
+ Tailwind v3 emits Preflight and utilities **outside** native cascade layers (v3 hijacks `@layer` as its own directive), and CSS outside a layer beats CSS inside a layer. Preflight's `a { color: inherit; ... }` therefore overrides the component's layered link colors.
675
+
676
+ Options:
677
+
678
+ 1. **Upgrade to Tailwind v4** (recommended) — native layers with the exact order `theme, base, components, utilities`, so everything lines up.
679
+ 2. **Disable Preflight** in v3: `module.exports = { corePlugins: { preflight: false } }`.
680
+ 3. **Compensate** with your own unlayered rule (unlayered wins):
681
+
682
+ ```css
683
+ .header__menu a,
684
+ .mobile-menu a {
685
+ color: var(--accent-color, #3e1c71);
686
+ }
687
+ ```
688
+
689
+ Note that v3 *utilities* still work as expected, because unlayered utilities also beat the component.
690
+
691
+ ### Accordion icons not showing up
261
692
 
262
693
  Icons are rendered as inline SVG components. If you are upgrading from `v1.x` or `v2.0` and have the old ChevronIcon CDN `<script>` tag in your layout `<head>`, you can safely remove it.
263
694
 
695
+ ### The header sits behind my other content
696
+
697
+ Set a z-index through the `theme` prop (`theme={{ light: { zIndex: 60 } }}`), not through a class on `container`. See [z-index and per-instance tokens](#example-z-index-and-per-instance-tokens-with-the-theme-prop).
698
+
264
699
  ---
265
700
 
266
701
  ## Compatibility
267
702
 
268
- - **Astro 7.x**
269
- - SSG, SSR, and Hybrid project outputs.
703
+ - **Astro 7.x** (peer dependency), SSG, SSR, and CSS builds (no client framework required).
704
+ - **CSS cascade layers** require a modern browser: Chrome/Edge 99+, Firefox 97+, Safari 15.4+.
705
+ - Works with Tailwind v4 out of the box; Tailwind v3 works with the Preflight caveat above.
270
706
 
271
707
  ---
272
708
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sofidevo/astro-dynamic-header",
3
- "version": "3.1.0",
3
+ "version": "4.0.1",
4
4
  "description": "A dynamic Astro header component that switches between floating and fullscreen styles",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "files": [
26
26
  "src/*.ts",
27
+ "src/scripts/*.ts",
27
28
  "src/*.astro",
28
29
  "README.md"
29
30
  ],
@@ -56,10 +57,10 @@
56
57
  "astro": "^7.0.0"
57
58
  },
58
59
  "devDependencies": {
59
- "@astrojs/check": "^0.9.9",
60
+ "@astrojs/check": "^0.9.10",
60
61
  "@types/jsdom": "^21.1.7",
61
62
  "@vitest/coverage-v8": "^3.2.4",
62
- "astro": "^7.0.4",
63
+ "astro": "^7.3.6",
63
64
  "jsdom": "^26.1.0",
64
65
  "typescript": "^5.0.0",
65
66
  "vitest": "^3.2.4"
@@ -71,5 +72,5 @@
71
72
  "bugs": {
72
73
  "url": "https://github.com/SofiDevO/astro-dynamic-header/issues"
73
74
  },
74
- "homepage": "https://base-astro-psi.vercel.app/fullscreen-demo"
75
+ "homepage": "https://header.sofidev.top"
75
76
  }
@@ -16,10 +16,18 @@ const content = `<path class="hlhawq"/>`;
16
16
  />
17
17
 
18
18
  <style is:inline>
19
- .hlhawq {
20
- fill: currentColor;
21
- d: path(
22
- "M11.646 15.146L5.854 9.354a.5.5 0 0 1 .353-.854h11.586a.5.5 0 0 1 .353.854l-5.793 5.792a.5.5 0 0 1-.707 0"
23
- );
19
+ /*
20
+ * base (resets) < components (ours) < utilities (consumer classNames).
21
+ * Order statement keeps it deterministic regardless of bundling order.
22
+ */
23
+ @layer theme, base, components, utilities;
24
+
25
+ @layer components {
26
+ .hlhawq {
27
+ fill: currentColor;
28
+ d: path(
29
+ "M11.646 15.146L5.854 9.354a.5.5 0 0 1 .353-.854h11.586a.5.5 0 0 1 .353.854l-5.793 5.792a.5.5 0 0 1-.707 0"
30
+ );
31
+ }
24
32
  }
25
33
  </style>