@tenphi/cookbook 0.17.0 → 0.18.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.
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: Theme and components
2
+ title: Theme reference
3
3
  description: Build an accessible Glaze palette, customize Tasty tokens, and use the supported Astro components.
4
4
  sidebar:
5
5
  order: 5
@@ -26,20 +26,25 @@ cover the configuration language accepted by `theme.styles`:
26
26
  * [Tasty methodology](./upstream/tasty/docs/methodology.md) for the design-system
27
27
  patterns behind roots, sub-elements, and controlled overrides.
28
28
 
29
+ For the short brand, logo, and font path, start with the
30
+ [configuration recipes](./recipes.md). This reference covers semantic colors,
31
+ typography, tokens, every configurable style tree, and custom components. Read
32
+ the [customization rules](./customization-rules.md) before adding styles.
33
+
29
34
  ## Brand color
30
35
 
31
36
  The default brand is a calm blue with 68% OKHSL saturation. Set a brand color
32
37
  with Glaze's `from` declaration:
33
38
 
34
39
  ```ts
35
- theme: {
36
- brand: {
37
- from: "#2f5bff";
38
- }
39
- }
40
+ import { defineDocsConfig } from "@tenphi/cookbook/config";
41
+
42
+ export default defineDocsConfig({
43
+ theme: { brand: { from: "#2f5bff" } },
44
+ });
40
45
  ```
41
46
 
42
- Add a contrast target when the brand appears as text or focus color:
47
+ Add a contrast target when the brand appears as text:
43
48
 
44
49
  ```ts
45
50
  theme: {
@@ -50,9 +55,12 @@ theme: {
50
55
  }
51
56
  ```
52
57
 
53
- The authored color remains exact when it already satisfies the required
54
- contrast against the page surface. Otherwise Glaze moves it only as far as the
55
- floor requires. Dark and high-contrast schemes resolve independently.
58
+ The brand supplies the hue and saturation seed. Cookbook derives separate text,
59
+ filled-control, and focus roles from it. Small accent text and filled controls
60
+ use stronger default APCA floors (75 normally, 90 in high contrast), and diagnostics
61
+ also enforce WCAG 4.5:1 / 7:1 on their actual backgrounds. Higher authored brand
62
+ targets are retained. Glaze adjusts tones only as far as these floors require;
63
+ dark and high-contrast schemes resolve independently.
56
64
 
57
65
  Cookbook rejects a normal APCA target below 45 unless
58
66
  `unsafeContrast: true` is present. That escape hatch is intentionally visible
@@ -72,13 +80,19 @@ theme: {
72
80
  surface: { tone: 98, saturation: 0.05 },
73
81
  text: {
74
82
  base: "surface",
75
- tone: "-10",
83
+ tone: 0,
84
+ saturation: 0,
85
+ contrast: { wcag: [7, 10] }
86
+ },
87
+ heading: {
88
+ base: "surface",
89
+ tone: [4, 0],
76
90
  saturation: 0,
77
91
  contrast: { wcag: [7, 10] }
78
92
  },
79
93
  textSoft: {
80
94
  base: "surface",
81
- tone: "-10",
95
+ tone: [25, 10],
82
96
  saturation: 0.05,
83
97
  contrast: { wcag: [4.5, 7] }
84
98
  }
@@ -88,8 +102,19 @@ theme: {
88
102
  ```
89
103
 
90
104
  Glaze resolves each declaration for light, dark, normal, and high-contrast
91
- modes. The first contrast value applies to normal mode; the second applies to
92
- high contrast. Palette declarations inherit the brand hue and saturation:
105
+ modes. In a tone or contrast pair, the first value applies to normal mode;
106
+ the second applies to high contrast. Use absolute tones for reading text:
107
+ `text` starts at tone 0 and `heading` at tone 4, giving headings only a slight
108
+ reduction in contrast. Glaze applies its tone boundaries in normal mode, so
109
+ body text is not pure black, then inverts the tones for dark mode. High-contrast
110
+ mode uses the full range. `textSoft` is reserved for secondary text.
111
+ `heading` follows an explicitly configured `text` declaration unless it has
112
+ its own declaration, preserving existing custom palettes.
113
+ Contrast requirements are minimum safeguards: Glaze preserves the authored
114
+ tone when it already meets the floor. A small relative tone step from the
115
+ surface would instead leave the solver to produce text at that minimum.
116
+
117
+ Palette declarations inherit the brand hue and saturation:
93
118
  `saturation` is a 0–1 factor of that seed, and `tone` is 0–100. They can set
94
119
  an absolute tone or a tone relative to `base`. Use `from` on a palette role
95
120
  when it needs its own color seed; otherwise relative declarations keep its
@@ -100,9 +125,9 @@ Cookbook keeps literal surface seeds desaturated in the dark scheme so a nearly
100
125
  white tint does not become vivid dark chrome when its tone is inverted.
101
126
 
102
127
  Components consume semantic colors consistently: `surface`, `header`, `surface-2`,
103
- `surface-3`, `text`, `text-soft`, `border`, `border-strong`, `accent-text`,
104
- `accent-surface`, `accent-surface-text`, and `focus`. Tasty components can use
105
- these as `#surface`, `#text`, `#border`, and so on; the Astro shell consumes the
128
+ `surface-3`, `text`, `heading`, `text-soft`, `border`, `border-strong`, `accent-text`,
129
+ `accent-surface`, `accent-surface-text`, `logo-surface`, `logo-mark`, and `focus`.
130
+ Tasty components can use these as `#surface`, `#text`, `#border`, and so on; the Astro shell consumes the
106
131
  same resolved values. Glaze also generates hover and pressed states, subtle
107
132
  accent fills, overlays, shadows, and the orange, green, blue, purple, and red
108
133
  roles used by Starlight content components. No browser color mixes or
@@ -189,15 +214,20 @@ theme: {
189
214
  }
190
215
  ```
191
216
 
192
- The short form requests the family's default style. To use specific weights,
193
- list them explicitly. Google Fonts must support the requested weights; see the
217
+ The short form follows the regular and strong weights in your configured
218
+ presets, including heading and custom preset references. It requests normal and
219
+ italic styles, prefers a variable range, and falls back to real static weights
220
+ when ranges are unavailable. A font without italics produces a build warning;
221
+ choose another family or select only its supported styles. To control the
222
+ request exactly, list weights or ascending variable ranges and styles. Google
223
+ Fonts must support them; see the
194
224
  [Google Fonts CSS API](https://developers.google.com/fonts/docs/css2) for its
195
225
  available styles and weight syntax.
196
226
 
197
227
  ```ts
198
228
  theme: {
199
229
  fonts: {
200
- body: { google: "Inter", weights: [400, 600, 700] }
230
+ body: { google: "Inter", weights: ["400 750"], styles: ["normal", "italic"] }
201
231
  }
202
232
  }
203
233
  ```
@@ -223,10 +253,38 @@ theme: {
223
253
 
224
254
  The `body` role also feeds navigation and small text. `heading` feeds `h1`
225
255
  through `h6`, while `code` covers code text. Each role has a system fallback.
226
- Cookbook emits the font faces through Tasty. Google font names are resolved at
227
- build time, so those builds need network access; the generated site has no
228
- Google CSS stylesheet. Font files are requested from Google's font CDN when
229
- visitors open the site. Use local files if you need a self-hosted site.
256
+ Cookbook emits font faces through Tasty with `font-display: swap`. Google CSS
257
+ and font files are downloaded at build time and cached in
258
+ `<Astro cacheDir>/cookbook-fonts`. Files are served from your own site's
259
+ `_cookbook/fonts/` path by default, including the deployment base. Visitors make
260
+ no Google Fonts requests. Existing local-file definitions keep working.
261
+
262
+ ```ts
263
+ theme: {
264
+ fonts: { body: "Inter" },
265
+ fontLoading: {
266
+ google: "self-hosted", // or "remote" to keep Google's font CDN URLs
267
+ cache: "reuse", // "refresh" to fetch again; "offline" to forbid network
268
+ display: "swap" // also auto, block, fallback, optional
269
+ }
270
+ }
271
+ ```
272
+
273
+ The first build requires network access. Preserve the Astro cache between CI
274
+ runs; `offline` fails with an actionable message when an entry is missing or
275
+ corrupt. Cache entries are content-verified and keyed by the exact request.
276
+ `refresh` deliberately updates them. For a fully checked-in font source, copy
277
+ licensed files into `public/fonts/` and use the local form. Only `wght` and
278
+ `ital` axes are requested; use local variable files and Tasty typography styles
279
+ for other axes. Styles and weights that do not exist are rejected for explicit
280
+ requests. For static fonts, browsers select their nearest real weight;
281
+ intermediate weights require a variable font.
282
+
283
+ Migration: name-only Google font configuration now downloads the required
284
+ weights and italics and serves them locally. This increases the initial build's
285
+ font downloads and removes visitors' external font dependency. Set
286
+ `fontLoading.google: "remote"` to retain CDN delivery. Existing CSS-variable
287
+ weights that cannot be resolved from presets require explicit `weights`.
230
288
 
231
289
  ### Adjust presets
232
290
 
@@ -341,42 +399,62 @@ is merged into the base inside Cookbook.
341
399
  Every configurable surface accepts styles at the root plus these named Tasty
342
400
  sub-elements:
343
401
 
344
- | Configuration name | Named sub-elements |
345
- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
346
- | `Card` | `Heading2`, `Heading3`, `Paragraph` |
347
- | `Callout` | `Title`, `Body`, `Tip`, `Caution`, `Danger` |
348
- | `CodeGroup` | `Caption`, `Pre`, `Code` |
349
- | `Tab` | `Heading`, `Hidden`, `HiddenHeading` |
350
- | `Footer` | `Meta`, `LoneMetaItem`, `MetaLink`, `HoverMetaLink`, `Credit`, `CreditLink`, `HoverCreditLink` |
351
- | `Hero` | `Visual`, `Stack`, `Copy`, `Title`, `Tagline`, `Actions`, `Action`, `HoverAction`, `PrimaryAction`, `SecondaryAction`, `MinimalAction`, `ActionIcon` |
352
- | `LanguageSelect` | `Label`, `HoverLabel`, `LabelIcon`, `Select`, `Caret`, `Option` |
353
- | `Logo` | `Svg`, `Mark` |
354
- | `MarkdownCodeBlock` | `Pre`, `CopyButton`, `HoverCopyButton`, `CopiedButton`, `CopyIcon`, `CopiedIcon` |
355
- | `MarkdownHeading` | `Heading`, `Heading1`, `Heading2`, `Heading3`, `Heading4`, `Heading5`, `Heading6`, `Link`, `RevealedLink`, `HoverLink`, `LinkIcon`, `CopiedLink`, `CopiedLinkIcon`, `CopiedIcon` |
356
- | `MarkdownInlineCode` | None |
357
- | `MarkdownTable` | `Table`, `Cell`, `LastBodyRowCell`, `HeaderCell` |
358
- | `Mermaid` | `Diagram`, `Text`, `MonoText` |
359
- | `MobileMenuFooter` | `Social` |
360
- | `MobileMenuToggle` | `Control`, `Icon`, `Section`, `Page`, `HoverControl`, `ActiveControl` |
361
- | `MobileNavigationTabs` | `Trigger`, `Marker`, `Caret`, `ExpandedCaret`, `Label`, `List`, `Item`, `Link`, `HoverLink`, `CurrentLink` |
362
- | `MobileTableOfContents` | `Item`, `Link`, `LinkLabel`, `HoverLink`, `CurrentLink`, `CurrentIndicator` |
363
- | `PackageVersion` | None |
364
- | `VersionSwitcher` | `Trigger`, `Panel`, `OpenPanel`, `Link`, `CurrentLink` |
365
- | `Preview` | `Caption`, `Stage`, `Frame`, `Code`, `Summary`, `Pre` |
366
- | `Sidebar` | `Backdrop`, `OpenBackdrop`, `MobileHeading`, `HomeLink`, `HomeLogo`, `HomeLabel`, `Close`, `HoverClose`, `CloseIcon`, `CurrentLink`, `OpenPane`, `Content`, `Tree`, `List`, `Item`, `TopLevelSpacing`, `GroupSpacing`, `NestedItem`, `SectionHeading`, `Control`, `Summary`, `GroupLabel`, `GroupLabelText`, `Link`, `LinkLabel`, `InteractiveControl`, `SummaryMarker`, `Caret`, `ExpandedCaret`, `LinkedSummary`, `GroupLink`, `LinkedSectionHeading`, `SectionLink`, `Badge`, `TopLevelLink` |
367
- | `SocialIcons` | `Link`, `HoverLink`, `Icon` |
368
- | `StarlightHeader` | `Primary`, `TitleAndSearch`, `Title`, `LogoLink`, `Logo`, `SiteTitle`, `Search`, `SearchElement`, `Tools`, `ToolItem`, `Social`, `MobileTheme` |
369
- | `TableOfContentsLayout` | `Content` |
370
- | `SearchButton` | `Label`, `Shortcut`, `Hover`, `Active`, `NativeIcon`, `Icon` |
371
- | `Layout` | `LockedPage` |
372
- | `HeaderLinks` | `Desktop`, `Link`, `HoverLink`, `PrimaryLink`, `HoverPrimaryLink`, `Trigger`, `HoverTrigger`, `Panel`, `PanelNavigation`, `PanelLink`, `FirstPanelLink`, `Close`, `HoverClose` |
373
- | `HeaderFrame` | None |
374
- | `PageFrame` | `MainFrame`, `SidebarFrame`, `Columns` |
375
- | `Steps` | `Item`, `Marker` |
376
- | `Tabs` | `List`, `Button`, `SelectedButton`, `FocusedButton` |
377
- | `TableOfContents` | `Heading`, `List`, `Item`, `Link`, `LinkLabel`, `HoverLink`, `CurrentLink` |
378
- | `ThemeSelect` | `Trigger`, `HoverTrigger`, `ActiveTrigger`, `Icon`, `Panel`, `OpenPanel`, `Section`, `SectionSpacing`, `SectionLabel`, `Option`, `HoverOption`, `CheckedOption`, `FocusedOption`, `Input`, `OptionIcon`, `Checkmark`, `SelectedCheckmark` |
379
- | `TopNavigation` | `Scrollbar`, `Link`, `HoverLink`, `CurrentLink`, `ActiveIndicator` |
402
+ | Configuration name | Named sub-elements |
403
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
404
+ | `Card` | `Heading2`, `Heading3`, `Paragraph` |
405
+ | `Callout` | `Title`, `Body`, `Tip`, `Caution`, `Danger` |
406
+ | `CodeGroup` | `Caption`, `Pre`, `Code` |
407
+ | `Tab` | `Heading`, `Hidden`, `HiddenHeading` |
408
+ | `MobileTableOfContents` | `Summary`, `List`, `NestedList`, `Item`, `Link`, `HoverLink`, `Focus` |
409
+ | `PageActions` | `Control`, `Hover`, `Focus`, `Pending`, `Status` |
410
+ | `Footer` | `Meta`, `LoneMetaItem`, `MetaLink`, `HoverMetaLink`, `Credit`, `CreditLink`, `HoverCreditLink` |
411
+ | `Hero` | `Visual`, `Stack`, `Copy`, `Title`, `Tagline`, `Actions`, `Action`, `HoverAction`, `PrimaryAction`, `SecondaryAction`, `MinimalAction`, `ActionIcon` |
412
+ | `LanguageSelect` | `Label`, `HoverLabel`, `LabelIcon`, `Select`, `Caret`, `Option` |
413
+ | `Logo` | `Svg`, `Mark` |
414
+ | `SiteLogo` | `Image`, `Light`, `Dark` |
415
+ | `MarkdownCodeBlock` | `Pre`, `CopyButton`, `HoverCopyButton`, `CopiedButton`, `CopyIcon`, `CopiedIcon`, `Code`, `Diff`, `DiffCode`, `DiffLine`, `EmptyDiffLine`, `InsertedLine`, `DeletedLine` |
416
+ | `MarkdownHeading` | `Heading`, `Heading1`, `Heading2`, `Heading3`, `Heading4`, `Heading5`, `Heading6`, `Link`, `RevealedLink`, `HoverLink`, `LinkIcon`, `CopiedLink`, `CopiedLinkIcon`, `CopiedIcon` |
417
+ | `MarkdownInlineCode` | None |
418
+ | `MarkdownTable` | `Table`, `Cell`, `LastBodyRowCell`, `HeaderCell`, `Scroll` |
419
+ | `Mermaid` | `Diagram`, `Text`, `MonoText` |
420
+ | `MobileMenuFooter` | `Social` |
421
+ | `MobileMenuToggle` | `Control`, `Icon`, `Section`, `Page`, `HoverControl`, `ActiveControl` |
422
+ | `MobileNavigationTabs` | `Trigger`, `Marker`, `Caret`, `ExpandedCaret`, `Label`, `List`, `Item`, `Link`, `HoverLink`, `CurrentLink` |
423
+ | `MobileTableOfContents` | `Item`, `Link`, `LinkLabel`, `HoverLink`, `CurrentLink`, `CurrentIndicator` |
424
+ | `PackageVersion` | None |
425
+ | `VersionSwitcher` | `Trigger`, `Panel`, `OpenPanel`, `Link`, `CurrentLink` |
426
+ | `Preview` | `Caption`, `Stage`, `Frame`, `Code`, `Summary`, `Pre` |
427
+ | `Sidebar` | `Backdrop`, `OpenBackdrop`, `MobileHeading`, `HomeLink`, `HomeLogo`, `HomeLabel`, `Close`, `HoverClose`, `CloseIcon`, `CurrentLink`, `OpenPane`, `EnteredPane`, `Content`, `Tree`, `List`, `Item`, `TopLevelSpacing`, `GroupSpacing`, `NestedItem`, `SectionHeading`, `Control`, `Summary`, `GroupLabel`, `GroupLabelText`, `Link`, `LinkLabel`, `InteractiveControl`, `SummaryMarker`, `Caret`, `ExpandedCaret`, `LinkedSummary`, `GroupLink`, `LinkedSectionHeading`, `SectionLink`, `Badge`, `TopLevelLink` |
428
+ | `SocialIcons` | `Link`, `HoverLink`, `Icon` |
429
+ | `StarlightHeader` | `Primary`, `TitleAndSearch`, `Title`, `LogoLink`, `Logo`, `SiteTitle`, `Search`, `SearchElement`, `Tools`, `ToolItem`, `Social`, `MobileTheme` |
430
+ | `TableOfContentsLayout` | `WithMobile`, `Content` |
431
+ | `SearchButton` | `PendingShortcut`, `Label`, `Shortcut`, `Hover`, `Active`, `NativeIcon`, `Icon` |
432
+ | `Layout` | `Islands`, `LockedPage`, `Light`, `Auto` |
433
+ | `HeaderLinks` | `Desktop`, `DesktopLink`, `Link`, `HoverLink`, `PrimaryLink`, `HoverPrimaryLink`, `Trigger`, `HoverTrigger`, `Panel`, `PanelNavigation`, `PanelLink`, `FirstPanelLink`, `Close`, `HoverClose` |
434
+ | `Heading` | `Level1`, `Level2`, `Level3`, `Level4`, `Level5`, `Level6`, `PageTitle` |
435
+ | `HeaderFrame` | None |
436
+ | `PageFrame` | `MainFrame`, `SidebarFrame`, `Columns` |
437
+ | `Steps` | `Item`, `Marker` |
438
+ | `Tabs` | `List`, `Button`, `SelectedButton`, `FocusedButton` |
439
+ | `TableOfContents` | `Heading`, `List`, `Item`, `Link`, `LinkLabel`, `HoverLink`, `CurrentLink` |
440
+ | `ThemeSelect` | `Trigger`, `HoverTrigger`, `ActiveTrigger`, `Icon`, `Panel`, `OpenPanel`, `Section`, `SectionSpacing`, `SectionLabel`, `Option`, `HoverOption`, `CheckedOption`, `FocusedOption`, `Input`, `OptionIcon`, `Checkmark`, `SelectedCheckmark` |
441
+ | `TopNavigation` | `Scrollbar`, `Link`, `HoverLink`, `CurrentLink`, `ActiveIndicator` |
442
+ | `Document` | `All`, `Body`, `Control`, `Pointer`, `ResponsiveWidth`, `ResponsiveHeight`, `Hidden`, `DesktopBlock`, `DesktopFlex`, `ScreenReaderOnly`, `Strong`, `Link`, `NarrowBlock`, `MobileBlock`, `Code`, `FocusRing`, `CurrentLink`, `SearchOpen` |
443
+ | `MainPane` | `WithSidebars` |
444
+ | `MainContent` | `ContentSpacing`, `Container`, `Panel`, `FirstPanel`, `BodyPanel` |
445
+ | `Banner` | `Link` |
446
+ | `SkipLink` | `Focus` |
447
+ | `Search` | `Status`, `Dialog`, `CloseIcon`, `OpenDialog`, `Backdrop`, `Frame`, `Container`, `Close`, `HoverClose`, `ActiveClose` |
448
+ | `Pagination` | `Link`, `NextLink`, `NextIcon`, `NextLabel`, `HoverLink`, `ActiveLink`, `Title`, `LoneNextLink`, `NativeIcon`, `Icon`, `PreviousIconRtl`, `NextIconRtl` |
449
+ | `Markdown` | `Block`, `BlockSpacing`, `HeadingSpacing`, `List`, `CompactItem`, `ListItem`, `DefinitionTerm`, `DefinitionDescription`, `Link`, `HoverLink`, `Quote`, `Rule`, `Details`, `HoverDetails`, `Summary`, `OpenSummary`, `SummaryMarker`, `SummaryIcon`, `OpenSummaryIcon`, `Code` |
450
+ | `MermaidSource` | None |
451
+ | `StarlightAside` | `Note`, `Tip`, `Caution`, `Danger`, `Title`, `Icon`, `FirstContent` |
452
+ | `StarlightCard` | `Title` |
453
+ | `StarlightLinkCard` | `Hover`, `Stack`, `Link`, `LinkOverlay`, `DescriptionAndIcon` |
454
+ | `StarlightBadge` | None |
455
+ | `StarlightSteps` | `Item`, `Counter`, `Connector` |
456
+ | `SyntaxHighlight` | `Scroll`, `Wrap`, `Marker`, `Comment`, `Punctuation`, `Keyword`, `String`, `Token`, `Property`, `Number`, `Function`, `Value`, `Operator`, `Text`, `Bg`, `Inserted`, `Deleted`, `Italic`, `Strong`, `Underline` |
457
+ | `SearchResults` | `Form`, `Input`, `Clear`, `Results`, `Result`, `ResultLink`, `SearchIcon`, `ClearIcon`, `SuppressedClear`, `Message`, `List`, `Title`, `Excerpt`, `NestedResult`, `Match`, `More`, `HoverMore` |
380
458
 
381
459
  `COOKBOOK_COMPONENT_NAMES` publishes the configuration names, and
382
460
  `COOKBOOK_COMPONENT_SUB_ELEMENTS` publishes the complete sub-element lists for
@@ -458,8 +536,8 @@ import { Card, Logo, Preview, Steps, Tabs } from "@tenphi/cookbook/components";
458
536
 
459
537
  See [Authoring components](./authoring.mdx) for complete examples and props.
460
538
 
461
- * `Preview` isolates HTML and CSS with declarative Shadow DOM, or JavaScript
462
- in a sandboxed iframe.
539
+ * `Preview` isolates HTML, CSS, and optional JavaScript in a sandboxed iframe.
540
+ The frame never receives same-origin access to the documentation page.
463
541
 
464
542
  Use MDX only for repository content you control. Locked package content remains
465
543
  Markdown-only unless the consumer explicitly sets `trust: "mdx"` for that
@@ -487,7 +565,84 @@ is needed for static components. Set shared tokens, presets, and states in
487
565
  `docs.config.ts` before rendering. Import the styling entry point in component
488
566
  modules; configuration files should use the main `@tenphi/cookbook` entry point.
489
567
 
490
- ### A custom logo and site title
568
+ ### A shared site logo
569
+
570
+ Set `site.logo` once to use your artwork in the header and mobile drawer:
571
+
572
+ ```ts
573
+ site: {
574
+ title: "Acme",
575
+ logo: "./assets/acme.svg"
576
+ }
577
+ ```
578
+
579
+ Paths are local files relative to the resolved project root, just like
580
+ `site.favicon`. Cookbook reads the intrinsic dimensions and copies the image to
581
+ a content-hashed URL under the deployment base. SVG, PNG, JPEG, WebP, AVIF, and
582
+ GIF are supported. Missing or invalid files fail during setup.
583
+
584
+ ```ts
585
+ site: {
586
+ title: "Acme",
587
+ logo: {
588
+ light: "./assets/acme-light.svg",
589
+ dark: "./assets/acme-dark.svg",
590
+ alt: "Acme documentation",
591
+ href: "/",
592
+ decorative: true
593
+ },
594
+ favicon: "./assets/acme-icon.svg"
595
+ }
596
+ ```
597
+
598
+ Use `src` for one image, or both `light` and `dark`. Variants follow the
599
+ appearance control and the system preference in Auto mode. They must share an
600
+ aspect ratio to prevent layout shifts. Optional `width` and `height` describe
601
+ intrinsic dimensions; Tasty controls display size. Customize
602
+ `theme.styles.SiteLogo` and its complete anatomy: `Image`, `Light`, and `Dark`.
603
+ Wide wordmarks retain their proportions within the header.
604
+
605
+ The logo is decorative by default because the neighboring title labels the
606
+ site. Set `decorative: false` and `alt` when the image itself carries meaningful
607
+ text. The header logo link still has a name when the image is decorative.
608
+ `href` defaults to the localized documentation home; it accepts a root-relative
609
+ documentation route or HTTP(S) destination. The header title and drawer use the
610
+ same destination. `site.logo: false` removes the mark from both locations.
611
+
612
+ A logo does not change the favicon or touch icons. Configure `site.favicon`
613
+ separately for square artwork; the default Cookbook icon set remains in place
614
+ until changed. `Logo` remains available as the standalone Cookbook book mark.
615
+
616
+ The built-in mark uses `theme.palette["logo-surface"]` and `theme.palette["logo-mark"]`.
617
+ Both default to Glaze `mode: "fixed"`: the brand background and light book keep
618
+ their polarity in dark mode while respecting Glaze's tone boundaries and
619
+ high-contrast settings. The mark-to-background contrast floor is 3:1 normally
620
+ and 4.5:1 in high contrast. These colors are separate from interactive accents.
621
+ For example, to customize the logo background while retaining brand hue:
622
+
623
+ ```ts
624
+ theme: {
625
+ brand: { from: "#315efb" },
626
+ palette: {
627
+ "logo-surface": {
628
+ base: "logo-mark",
629
+ tone: 40,
630
+ saturation: 0.8,
631
+ mode: "fixed",
632
+ contrast: { wcag: [3, 4.5] },
633
+ },
634
+ "logo-mark": { tone: 100, saturation: 0, mode: "fixed" },
635
+ },
636
+ }
637
+ ```
638
+
639
+ Custom image assets keep their authored colors; use `light`/`dark` files when
640
+ you want distinct artwork. Header and drawer home links center logos with the
641
+ site title regardless of their aspect ratio. Use `theme.styles.SiteLogo` for
642
+ image size and `theme.styles.StarlightHeader.SiteTitle` / `Sidebar.HomeLink`
643
+ for typography; they do not need baseline offsets for a different preset.
644
+
645
+ ### Advanced site title markup
491
646
 
492
647
  To put your own logo and title in one home link, create
493
648
  `docs/components/site-title.ts`:
@@ -541,20 +696,20 @@ const { siteTitle, siteTitleHref } = Astro.locals.starlightRoute;
541
696
  </SiteTitleRoot>
542
697
  ```
543
698
 
544
- Register the replacement in `docs.config.ts` and hide the default header mark.
699
+ Register the replacement in `docs.config.ts` and disable the shared mark.
700
+ A `SiteTitle` override affects the header; use `site.logo` above for ordinary
701
+ branding shared with the drawer. For custom drawer markup, override `Sidebar`.
545
702
  The custom component supports root styles plus its complete list of named
546
703
  sub-elements: `Logo` and `Label`.
547
704
 
548
705
  ```ts
706
+ site: { logo: false },
549
707
  components: {
550
708
  overrides: {
551
709
  SiteTitle: "./docs/components/SiteTitle.astro"
552
710
  }
553
711
  },
554
712
  theme: {
555
- styles: {
556
- StarlightHeader: { LogoLink: { hide: true } }
557
- },
558
713
  customStyles: {
559
714
  ProjectSiteTitle: { Logo: { color: "#text" } }
560
715
  }
@@ -738,8 +893,264 @@ left-aligned navigation links with a `1bw` gap, without primary button variants.
738
893
  `HeaderLinks.PanelLink` styles the menu rows; `FirstPanelLink` reserves space
739
894
  for the close button. The inset divider above the tabs belongs to `TopNavigation`.
740
895
 
741
- The table of contents is hidden below 72rem through `TableOfContentsLayout`.
742
- `MobileTableOfContents` remains a legacy style name for custom overrides; the
743
- default renderer no longer renders that component. `Layout` controls shared
896
+ The desktop table of contents is hidden below 72rem. Set
897
+ `tableOfContents: { mobile: true }` to show an optional compact disclosure there.
898
+ `MobileTableOfContents` owns its summary and links; `TableOfContentsLayout`
899
+ controls placement. `Layout` controls shared
744
900
  navigation dimensions and the drawer's scroll lock; `PageFrame` styles the
745
901
  page container and content columns.
902
+
903
+ ### Header control shape
904
+
905
+ Header buttons and the mobile drawer close button use the independent
906
+ `$header-control-radius` token, which defaults to `999px` for pill and circular
907
+ shapes. Set it to `8px` for the same corners as the default content controls:
908
+
909
+ ```ts
910
+ theme: {
911
+ tokens: { "$header-control-radius": "8px" }
912
+ }
913
+ ```
914
+
915
+ `$radius` continues to control code-copy buttons, navigation items, and other
916
+ content controls. Individual `theme.styles` overrides still take precedence.
917
+ The mobile header divider spans the viewport while its content keeps the
918
+ configured horizontal padding.
919
+
920
+ ### Add palette colors and reference other roles
921
+
922
+ Every `theme.palette` entry participates in one Glaze color graph. Use lowercase
923
+ names with hyphens; a role named `review-panel` becomes the Tasty token
924
+ `#review-panel`. Declaration order does not matter. References can target custom
925
+ colors or built-in roles, including header, border, overlay, and syntax colors.
926
+
927
+ ```ts
928
+ theme: {
929
+ brand: { from: "#315efb" },
930
+ palette: {
931
+ "review-ink": {
932
+ base: "review-panel",
933
+ tone: [4, 0],
934
+ saturation: 0.05,
935
+ contrast: { wcag: [7, 10] },
936
+ },
937
+ "review-panel": { base: "surface", tone: "-2", saturation: 0.05 },
938
+ },
939
+ styles: {
940
+ Callout: { fill: "#review-panel", color: "#review-ink" },
941
+ },
942
+ },
943
+ ```
944
+
945
+ Relative and absolute declarations inherit the brand's hue and saturation.
946
+ Use saturation factors from `0` (neutral) to `1` (full seed saturation), and
947
+ `hue` to choose a different hue. All roles resolve for light, dark, and both
948
+ high-contrast modes. Configuration diagnostics and rendering use the same graph.
949
+ Missing references and cycles fail with the color name; layered configurations
950
+ resolve references after merging.
951
+
952
+ `textSoft` remains an alias for `text-soft`. Use either spelling, but do not
953
+ configure both. The names `current`, `constructor`, and `prototype` are reserved.
954
+ Declaring a built-in name intentionally overrides that role; use a project
955
+ prefix for additional roles to avoid future naming collisions.
956
+
957
+ When a status role such as `info` uses a mix or shadow definition, its derived
958
+ `info-text` defaults to neutral, contrast-corrected text. Override `info-text`
959
+ explicitly for a colored label; the mix/shadow dependency graph stays intact.
960
+ A shadow status also uses a neutral derived surface, because Glaze does not allow
961
+ a shadow to be a mix target. Override the surface separately when needed.
962
+
963
+ ### Register units and recipes
964
+
965
+ Register custom units and flat recipes in `theme`; Cookbook configures Tasty
966
+ before evaluating your components:
967
+
968
+ ```ts
969
+ theme: {
970
+ brand: { from: "#315efb" },
971
+ palette: {
972
+ "review-panel": { base: "surface", tone: "-2", saturation: 0.05 },
973
+ "review-ink": { base: "review-panel", tone: 0, contrast: { wcag: [7, 10] } },
974
+ },
975
+ units: { rh: "6px" },
976
+ recipes: {
977
+ "review-panel": {
978
+ fill: "#review-panel",
979
+ color: "#review-ink",
980
+ padding: "2rh",
981
+ radius: "3px",
982
+ },
983
+ },
984
+ customStyles: { DemoBadge: { Label: { color: "#review-ink" } } },
985
+ },
986
+ ```
987
+
988
+ ```ts
989
+ import { defineComponent } from "@tenphi/cookbook/styling";
990
+
991
+ export const DemoBadge = defineComponent("DemoBadge", {
992
+ as: "aside",
993
+ elements: { Label: "strong" },
994
+ styles: {
995
+ display: "block",
996
+ recipe: "review-panel",
997
+ Label: { preset: "h4" },
998
+ },
999
+ });
1000
+ ```
1001
+
1002
+ Use `<DemoBadge><DemoBadge.Label>Review ready</DemoBadge.Label></DemoBadge>` in
1003
+ MDX. Keep these styled components server-rendered: their CSS is extracted at
1004
+ build time. Add behavior with a small client script targeting the rendered
1005
+ markup. This recipe produces 12px padding. Units merge by name, with the later
1006
+ configuration winning. Recipes merge using Tasty `mergeStyles`, including state
1007
+ maps. Recipes are flat: define named sub-elements on the owning component, and
1008
+ compose recipes with `recipe: "base elevated"` rather than referencing a recipe
1009
+ inside another recipe. `none` is a reserved recipe name. Cookbook's built-in
1010
+ units are `x`, `r`, `cr`, and `bw`; overriding one changes its meaning globally.
1011
+
1012
+ Keep editor and linter validation synchronized with your configuration:
1013
+
1014
+ ```ts
1015
+ // tasty.config.ts
1016
+ import { createValidationConfig } from "@tenphi/cookbook/eslint-plugin";
1017
+ import docs from "./docs.config";
1018
+ export default createValidationConfig(docs.theme);
1019
+ ```
1020
+
1021
+ ### Configure Glaze adaptation
1022
+
1023
+ `theme.glaze` accepts `lightTone`, `darkTone`, `darkDesaturation`, `autoFlip`,
1024
+ `pastel`, `inferRole`, and `shadowTuning`. Tone windows accept `[lo, hi]`, `false`
1025
+ for the full range, or the advanced `{ lo, hi, eps }` form. Continue to use
1026
+ `theme.contrastLevel` for manual contrast interpolation. Cookbook always emits
1027
+ all four appearance variants.
1028
+
1029
+ ```ts
1030
+ theme: {
1031
+ glaze: { lightTone: [10, 100], darkTone: [15, 95] },
1032
+ palette: {
1033
+ "accent-surface-subtle": {
1034
+ type: "mix", base: "surface", target: "accent-surface", value: [12, 20],
1035
+ },
1036
+ shadow: {
1037
+ type: "shadow", bg: "surface", fg: "text", intensity: [12, 24],
1038
+ tuning: { alphaMax: 0.3 },
1039
+ },
1040
+ },
1041
+ },
1042
+ ```
1043
+
1044
+ Palette roles accept Glaze's regular, mix, and shadow declarations. Mix and
1045
+ shadow references use the same graph as other roles. Native Glaze restrictions
1046
+ still apply, including the requirement for shadow backgrounds and foregrounds
1047
+ to reference non-shadow colors. `shadowTuning` exposes `saturationFactor`,
1048
+ `maxSaturation`, `lightnessFactor`, `lightnessBounds`, `minGapTarget`, `alphaMax`,
1049
+ and `bgHueBlend`; a role's own `tuning` takes precedence.
1050
+
1051
+ ### Built-in palette inventory
1052
+
1053
+ Every generated role below can be overridden in `theme.palette`. Names map
1054
+ directly to Tasty `#name` tokens. `COOKBOOK_PALETTE_NAMES`, exported from
1055
+ `@tenphi/docs`, lists the same roles for tooling.
1056
+
1057
+ | Area | Roles |
1058
+ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1059
+ | Surfaces | `surface`, `header`, `overlay`, `surface-2`, `surface-3`, `surface-2-hover`, `surface-2-pressed`, `surface-3-hover`, `surface-3-pressed` |
1060
+ | Reading | `text`, `heading`, `text-soft` (alias `textSoft`), `text-muted` |
1061
+ | Brand and focus | `accent-text`, `accent-surface`, `accent-surface-text`, `accent-surface-subtle`, `accent-surface-2-subtle`, `logo-surface`, `logo-mark`, `focus` |
1062
+ | Borders and effects | `border`, `border-strong`, `shadow`, `clear` |
1063
+ | Status | `info`, `success`, `warning`, `danger`, each with `-text` and `-surface` variants |
1064
+ | Additional hues | `orange`, `green`, `blue`, `purple`, `red`, each with `-text` and `-surface` variants |
1065
+ | Syntax | `syntax-bg`, `syntax-text`, `syntax-comment`, `syntax-punctuation`, `syntax-keyword`, `syntax-string`, `syntax-token`, `syntax-property`, `syntax-number`, `syntax-function`, `syntax-value`, `syntax-operator` |
1066
+
1067
+ ## Contrast checks and troubleshooting
1068
+
1069
+ Cookbook checks text, headings, muted reading text, brand links, filled-control
1070
+ labels, status text on its tinted surface, focus rings on each elevated
1071
+ surface, and every palette declaration with a contrast floor. The same Glaze
1072
+ resolution powers rendering and `cookbook doctor`, in light, dark, and both
1073
+ high-contrast modes. Manual `theme.contrastLevel` targets interpolate with
1074
+ Glaze's contrast level.
1075
+
1076
+ `resolveDocsTheme(theme).contrastChecks` lists each foreground/background pair,
1077
+ mode, metric, target, measured value, and pass result. APCA uses display-channel
1078
+ luminance; WCAG ratios use the sRGB transfer function. Transparent foregrounds
1079
+ are composited onto their background before measuring. These checks cover
1080
+ Cookbook's semantic pairs; test actual pages after changing component styles,
1081
+ backgrounds, typography, or layout.
1082
+
1083
+ A `DOCS_SEMANTIC_CONTRAST_UNMET` or `DOCS_BRAND_CONTRAST_UNMET` error names the
1084
+ pair, mode, and required target. Adjust that pair's `theme.palette` declarations:
1085
+ use an absolute tone for reading text, reduce saturation when needed, and keep
1086
+ `autoFlip` enabled when the solver needs to cross its base. A fixed middle-tone
1087
+ button fill may make its label's contrast impossible. Cookbook's default
1088
+ brand fill adapts to avoid this, including for orange, yellow, and pale brands.
1089
+ Focus uses a WCAG 3:1 floor (4.5:1 in high contrast) against the surface ramp.
1090
+
1091
+ Keep contrast requirements when adjusting colors. Extreme custom tone windows,
1092
+ fixed colors, or opacity can prevent a requested floor from being met. A palette
1093
+ check does not replace keyboard, screen-reader, and rendered-page testing.
1094
+
1095
+ ## Customize shared page surfaces
1096
+
1097
+ Each bridge has a complete base style tree; supply only the properties you
1098
+ want to change. Sub-elements keep their selectors and all other defaults.
1099
+ For example, a compact search dialog and quieter pagination need no CSS:
1100
+
1101
+ ```ts
1102
+ theme: {
1103
+ styles: {
1104
+ Search: { Dialog: { inlineSize: "max 32rem" } },
1105
+ SearchResults: { ResultLink: { color: "#accent-text" } },
1106
+ Pagination: { Link: { padding: "1.5x", fill: "#surface" } },
1107
+ Banner: { padding: "1x 2x" },
1108
+ Markdown: { Quote: { color: "#text", inlinePadding: "3x start" } },
1109
+ },
1110
+ }
1111
+ ```
1112
+
1113
+ `Document` owns resets, base typography, generic controls, responsive media,
1114
+ and accessibility utilities. `MainPane` and `MainContent` own the content
1115
+ layout. `Markdown` owns prose, lists, links, quotations, and disclosure
1116
+ anatomy. `Search` owns the dialog; `SearchResults` owns the Pagefind UI.
1117
+ The `Starlight*` trees style compatible upstream component markup; Cookbook's
1118
+ own `Card`, `Callout`, and `Steps` use their respective trees.
1119
+
1120
+ Custom components created with `defineComponent()` receive `theme.customStyles`
1121
+ overrides during server rendering. Units, recipes, states, and presets are
1122
+ resolved there and emitted as static CSS. Cookbook ships no Tasty/Glaze browser
1123
+ runtime, theme configuration, or style hydration setup. Browser scripts and
1124
+ `client:*` islands must not import Cookbook styling, Tasty, or Glaze; the build
1125
+ rejects these imports. Attach small interaction scripts to server-rendered markup
1126
+ or pass already-styled static markup into a client island.
1127
+
1128
+ Code fences and `CodeGroup` use the same `MarkdownCodeBlock` style tree. Its
1129
+ root `$copy-button-size` defaults to `2rem`; the copy control and vertical
1130
+ padding share it, keeping equal top, right, and bottom insets on one-line
1131
+ snippets. The padding follows the `code` preset's line height. Override it with
1132
+ `theme.styles.MarkdownCodeBlock` when changing the control size or code layout.
1133
+
1134
+ ### Syntax highlighting and isolated previews
1135
+
1136
+ Shiki classifies code; Tasty renders every token using Glaze's semantic syntax
1137
+ colors. Customize token rules with `theme.styles.SyntaxHighlight`, or their
1138
+ colors with `theme.palette` (for example `syntax-keyword`). Code fences, including
1139
+ MDX and diff blocks, use classes without inline style attributes. Custom Shiki
1140
+ transformers should emit classes; unsupported inline declarations fail with a
1141
+ configuration hint.
1142
+
1143
+ The production build rejects style attributes, style blocks (including inside
1144
+ shadow-root templates), and non-Tasty stylesheets throughout documentation HTML.
1145
+ It parses actual markup, so escaped examples and strings in scripts are allowed.
1146
+ The explicit exception is the separate document in a sandboxed `Preview` iframe;
1147
+ it cannot access its parent's origin. Configure its size with
1148
+ `theme.styles.Preview.Frame`. The documentation shell supports the CSP directive
1149
+ `style-src-attr 'none'`. A host's CSP may also restrict the preview document; when
1150
+ using that directive, authored previews must use style blocks rather than style
1151
+ attributes.
1152
+
1153
+ Search uses an owned, keyboard-accessible dialog with Pagefind's indexing and
1154
+ results UI. Its clear-button sizing is adapted to `SearchResults.Input` styles,
1155
+ so the browser does not need inline layout declarations. The adapter is checked
1156
+ against the pinned Pagefind UI version during builds.