@tenphi/cookbook 0.17.1 → 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
@@ -118,8 +126,8 @@ white tint does not become vivid dark chrome when its tone is inverted.
118
126
 
119
127
  Components consume semantic colors consistently: `surface`, `header`, `surface-2`,
120
128
  `surface-3`, `text`, `heading`, `text-soft`, `border`, `border-strong`, `accent-text`,
121
- `accent-surface`, `accent-surface-text`, and `focus`. Tasty components can use
122
- these as `#surface`, `#text`, `#border`, and so on; the Astro shell consumes the
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
123
131
  same resolved values. Glaze also generates hover and pressed states, subtle
124
132
  accent fills, overlays, shadows, and the orange, green, blue, purple, and red
125
133
  roles used by Starlight content components. No browser color mixes or
@@ -206,15 +214,20 @@ theme: {
206
214
  }
207
215
  ```
208
216
 
209
- The short form requests the family's default style. To use specific weights,
210
- 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
211
224
  [Google Fonts CSS API](https://developers.google.com/fonts/docs/css2) for its
212
225
  available styles and weight syntax.
213
226
 
214
227
  ```ts
215
228
  theme: {
216
229
  fonts: {
217
- body: { google: "Inter", weights: [400, 600, 700] }
230
+ body: { google: "Inter", weights: ["400 750"], styles: ["normal", "italic"] }
218
231
  }
219
232
  }
220
233
  ```
@@ -240,10 +253,38 @@ theme: {
240
253
 
241
254
  The `body` role also feeds navigation and small text. `heading` feeds `h1`
242
255
  through `h6`, while `code` covers code text. Each role has a system fallback.
243
- Cookbook emits the font faces through Tasty. Google font names are resolved at
244
- build time, so those builds need network access; the generated site has no
245
- Google CSS stylesheet. Font files are requested from Google's font CDN when
246
- 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`.
247
288
 
248
289
  ### Adjust presets
249
290
 
@@ -358,43 +399,62 @@ is merged into the base inside Cookbook.
358
399
  Every configurable surface accepts styles at the root plus these named Tasty
359
400
  sub-elements:
360
401
 
361
- | Configuration name | Named sub-elements |
362
- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
363
- | `Card` | `Heading2`, `Heading3`, `Paragraph` |
364
- | `Callout` | `Title`, `Body`, `Tip`, `Caution`, `Danger` |
365
- | `CodeGroup` | `Caption`, `Pre`, `Code` |
366
- | `Tab` | `Heading`, `Hidden`, `HiddenHeading` |
367
- | `Footer` | `Meta`, `LoneMetaItem`, `MetaLink`, `HoverMetaLink`, `Credit`, `CreditLink`, `HoverCreditLink` |
368
- | `Hero` | `Visual`, `Stack`, `Copy`, `Title`, `Tagline`, `Actions`, `Action`, `HoverAction`, `PrimaryAction`, `SecondaryAction`, `MinimalAction`, `ActionIcon` |
369
- | `LanguageSelect` | `Label`, `HoverLabel`, `LabelIcon`, `Select`, `Caret`, `Option` |
370
- | `Logo` | `Svg`, `Mark` |
371
- | `MarkdownCodeBlock` | `Pre`, `CopyButton`, `HoverCopyButton`, `CopiedButton`, `CopyIcon`, `CopiedIcon` |
372
- | `MarkdownHeading` | `Heading`, `Heading1`, `Heading2`, `Heading3`, `Heading4`, `Heading5`, `Heading6`, `Link`, `RevealedLink`, `HoverLink`, `LinkIcon`, `CopiedLink`, `CopiedLinkIcon`, `CopiedIcon` |
373
- | `MarkdownInlineCode` | None |
374
- | `MarkdownTable` | `Table`, `Cell`, `LastBodyRowCell`, `HeaderCell` |
375
- | `Mermaid` | `Diagram`, `Text`, `MonoText` |
376
- | `MobileMenuFooter` | `Social` |
377
- | `MobileMenuToggle` | `Control`, `Icon`, `Section`, `Page`, `HoverControl`, `ActiveControl` |
378
- | `MobileNavigationTabs` | `Trigger`, `Marker`, `Caret`, `ExpandedCaret`, `Label`, `List`, `Item`, `Link`, `HoverLink`, `CurrentLink` |
379
- | `MobileTableOfContents` | `Item`, `Link`, `LinkLabel`, `HoverLink`, `CurrentLink`, `CurrentIndicator` |
380
- | `PackageVersion` | None |
381
- | `VersionSwitcher` | `Trigger`, `Panel`, `OpenPanel`, `Link`, `CurrentLink` |
382
- | `Preview` | `Caption`, `Stage`, `Frame`, `Code`, `Summary`, `Pre` |
383
- | `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` |
384
- | `SocialIcons` | `Link`, `HoverLink`, `Icon` |
385
- | `StarlightHeader` | `Primary`, `TitleAndSearch`, `Title`, `LogoLink`, `Logo`, `SiteTitle`, `Search`, `SearchElement`, `Tools`, `ToolItem`, `Social`, `MobileTheme` |
386
- | `TableOfContentsLayout` | `Content` |
387
- | `SearchButton` | `Label`, `Shortcut`, `Hover`, `Active`, `NativeIcon`, `Icon` |
388
- | `Layout` | `LockedPage` |
389
- | `HeaderLinks` | `Desktop`, `Link`, `HoverLink`, `PrimaryLink`, `HoverPrimaryLink`, `Trigger`, `HoverTrigger`, `Panel`, `PanelNavigation`, `PanelLink`, `FirstPanelLink`, `Close`, `HoverClose` |
390
- | `Heading` | `Level1`, `Level2`, `Level3`, `Level4`, `Level5`, `Level6`, `PageTitle` |
391
- | `HeaderFrame` | None |
392
- | `PageFrame` | `MainFrame`, `SidebarFrame`, `Columns` |
393
- | `Steps` | `Item`, `Marker` |
394
- | `Tabs` | `List`, `Button`, `SelectedButton`, `FocusedButton` |
395
- | `TableOfContents` | `Heading`, `List`, `Item`, `Link`, `LinkLabel`, `HoverLink`, `CurrentLink` |
396
- | `ThemeSelect` | `Trigger`, `HoverTrigger`, `ActiveTrigger`, `Icon`, `Panel`, `OpenPanel`, `Section`, `SectionSpacing`, `SectionLabel`, `Option`, `HoverOption`, `CheckedOption`, `FocusedOption`, `Input`, `OptionIcon`, `Checkmark`, `SelectedCheckmark` |
397
- | `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` |
398
458
 
399
459
  `COOKBOOK_COMPONENT_NAMES` publishes the configuration names, and
400
460
  `COOKBOOK_COMPONENT_SUB_ELEMENTS` publishes the complete sub-element lists for
@@ -476,8 +536,8 @@ import { Card, Logo, Preview, Steps, Tabs } from "@tenphi/cookbook/components";
476
536
 
477
537
  See [Authoring components](./authoring.mdx) for complete examples and props.
478
538
 
479
- * `Preview` isolates HTML and CSS with declarative Shadow DOM, or JavaScript
480
- 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.
481
541
 
482
542
  Use MDX only for repository content you control. Locked package content remains
483
543
  Markdown-only unless the consumer explicitly sets `trust: "mdx"` for that
@@ -505,7 +565,84 @@ is needed for static components. Set shared tokens, presets, and states in
505
565
  `docs.config.ts` before rendering. Import the styling entry point in component
506
566
  modules; configuration files should use the main `@tenphi/cookbook` entry point.
507
567
 
508
- ### 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
509
646
 
510
647
  To put your own logo and title in one home link, create
511
648
  `docs/components/site-title.ts`:
@@ -559,20 +696,20 @@ const { siteTitle, siteTitleHref } = Astro.locals.starlightRoute;
559
696
  </SiteTitleRoot>
560
697
  ```
561
698
 
562
- 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`.
563
702
  The custom component supports root styles plus its complete list of named
564
703
  sub-elements: `Logo` and `Label`.
565
704
 
566
705
  ```ts
706
+ site: { logo: false },
567
707
  components: {
568
708
  overrides: {
569
709
  SiteTitle: "./docs/components/SiteTitle.astro"
570
710
  }
571
711
  },
572
712
  theme: {
573
- styles: {
574
- StarlightHeader: { LogoLink: { hide: true } }
575
- },
576
713
  customStyles: {
577
714
  ProjectSiteTitle: { Logo: { color: "#text" } }
578
715
  }
@@ -756,8 +893,264 @@ left-aligned navigation links with a `1bw` gap, without primary button variants.
756
893
  `HeaderLinks.PanelLink` styles the menu rows; `FirstPanelLink` reserves space
757
894
  for the close button. The inset divider above the tabs belongs to `TopNavigation`.
758
895
 
759
- The table of contents is hidden below 72rem through `TableOfContentsLayout`.
760
- `MobileTableOfContents` remains a legacy style name for custom overrides; the
761
- 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
762
900
  navigation dimensions and the drawer's scroll lock; `PageFrame` styles the
763
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.