@tenphi/cookbook 0.17.1 → 0.18.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.
@@ -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
 
@@ -316,6 +357,53 @@ bridge. Customize either kind by name under `theme.styles`; the configuration
316
357
  is resolved before CSS generation, so this is not a selector-based CSS
317
358
  override.
318
359
 
360
+ > **Direct styles, without a specificity battle.** Cookbook merges your partial
361
+ > style object with the component's base styles before Tasty extracts CSS. The
362
+ > generated stylesheet reflects that result; changing a supported property
363
+ > does not add a competing rule just to beat the original. You can customize
364
+ > existing elements without long selectors or `!important`.
365
+
366
+ Start with the area you want to change, then use the complete sub-element table
367
+ below to find its exact anatomy:
368
+
369
+ | Page area | Style names to inspect |
370
+ | ----------------------------- | ------------------------------------------------------------------------------------ |
371
+ | Header, title, and navigation | `HeaderFrame`, `StarlightHeader`, `SiteLogo`, `HeaderLinks`, `TopNavigation` |
372
+ | Sidebar and mobile menu | `Sidebar`, `MobileMenuToggle`, `MobileNavigationTabs`, `MobileMenuFooter` |
373
+ | Article text and headings | `Markdown`, `MarkdownHeading`, `Heading`, `MainContent` |
374
+ | Code and diagrams | `MarkdownCodeBlock`, `MarkdownInlineCode`, `SyntaxHighlight`, `CodeGroup`, `Mermaid` |
375
+ | Search | `SearchButton`, `Search`, `SearchResults` |
376
+ | Table of contents | `TableOfContents`, `MobileTableOfContents`, `TableOfContentsLayout` |
377
+ | Cards, notes, and tabs | `Card`, `StarlightCard`, `StarlightAside`, `Tabs` |
378
+ | Footer and page controls | `Footer`, `Pagination`, `PageActions`, `ThemeSelect` |
379
+
380
+ For example, this changes a typography role and one named element without
381
+ copying a component's full style tree:
382
+
383
+ ```ts
384
+ theme: {
385
+ presets: { h2: { fontSize: "1.75rem" } },
386
+ styles: {
387
+ Sidebar: {
388
+ LinkLabel: {
389
+ preset: "body",
390
+ whiteSpace: { "": "nowrap", "@mobile": "normal" }
391
+ }
392
+ }
393
+ }
394
+ }
395
+ ```
396
+
397
+ The `@mobile` value sits with the default value in one Tasty state map. Shared
398
+ tokens and presets keep the component aligned with the rest of the site, while
399
+ Glaze resolves semantic colors across the configured schemes and contrast
400
+ modes.
401
+
402
+ In a generated site, run `npm run validate` (or the matching package manager's
403
+ command) after editing `docs.config.ts`. It checks TypeScript theme properties,
404
+ then runs Cookbook's preflight and production output checks. A misspelled
405
+ preset or built-in sub-element property fails type checking.
406
+
319
407
  Provide only the root and named
320
408
  [sub-element](./upstream/tasty/docs/dsl.md#sub-element) properties you want to
321
409
  override. Cookbook deep-merges that partial style object into the complete
@@ -358,43 +446,62 @@ is merged into the base inside Cookbook.
358
446
  Every configurable surface accepts styles at the root plus these named Tasty
359
447
  sub-elements:
360
448
 
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` |
449
+ | Configuration name | Named sub-elements |
450
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
451
+ | `Card` | `Heading2`, `Heading3`, `Paragraph` |
452
+ | `Callout` | `Title`, `Body`, `Tip`, `Caution`, `Danger` |
453
+ | `CodeGroup` | `Caption`, `Pre`, `Code` |
454
+ | `Tab` | `Heading`, `Hidden`, `HiddenHeading` |
455
+ | `MobileTableOfContents` | `Summary`, `List`, `NestedList`, `Item`, `Link`, `HoverLink`, `Focus` |
456
+ | `PageActions` | `Control`, `Hover`, `Focus`, `Pending`, `Status` |
457
+ | `Footer` | `Meta`, `LoneMetaItem`, `MetaLink`, `HoverMetaLink`, `Credit`, `CreditLink`, `HoverCreditLink` |
458
+ | `Hero` | `Visual`, `Stack`, `Copy`, `Title`, `Tagline`, `Actions`, `Action`, `HoverAction`, `PrimaryAction`, `SecondaryAction`, `MinimalAction`, `ActionIcon` |
459
+ | `LanguageSelect` | `Label`, `HoverLabel`, `LabelIcon`, `Select`, `Caret`, `Option` |
460
+ | `Logo` | `Svg`, `Mark` |
461
+ | `SiteLogo` | `Image`, `Light`, `Dark` |
462
+ | `MarkdownCodeBlock` | `Pre`, `CopyButton`, `HoverCopyButton`, `CopiedButton`, `CopyIcon`, `CopiedIcon`, `Code`, `Diff`, `DiffCode`, `DiffLine`, `EmptyDiffLine`, `InsertedLine`, `DeletedLine` |
463
+ | `MarkdownHeading` | `Heading`, `Heading1`, `Heading2`, `Heading3`, `Heading4`, `Heading5`, `Heading6`, `Link`, `RevealedLink`, `HoverLink`, `LinkIcon`, `CopiedLink`, `CopiedLinkIcon`, `CopiedIcon` |
464
+ | `MarkdownInlineCode` | None |
465
+ | `MarkdownTable` | `Table`, `Cell`, `LastBodyRowCell`, `HeaderCell`, `Scroll` |
466
+ | `Mermaid` | `Diagram`, `Text`, `MonoText` |
467
+ | `MobileMenuFooter` | `Social` |
468
+ | `MobileMenuToggle` | `Control`, `Icon`, `Section`, `Page`, `HoverControl`, `ActiveControl` |
469
+ | `MobileNavigationTabs` | `Trigger`, `Marker`, `Caret`, `ExpandedCaret`, `Label`, `List`, `Item`, `Link`, `HoverLink`, `CurrentLink` |
470
+ | `MobileTableOfContents` | `Item`, `Link`, `LinkLabel`, `HoverLink`, `CurrentLink`, `CurrentIndicator` |
471
+ | `PackageVersion` | None |
472
+ | `VersionSwitcher` | `Trigger`, `Panel`, `OpenPanel`, `Link`, `CurrentLink` |
473
+ | `Preview` | `Caption`, `Stage`, `Frame`, `Code`, `Summary`, `Pre` |
474
+ | `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` |
475
+ | `SocialIcons` | `Link`, `HoverLink`, `Icon` |
476
+ | `StarlightHeader` | `Primary`, `TitleAndSearch`, `Title`, `LogoLink`, `Logo`, `SiteTitle`, `Search`, `SearchElement`, `Tools`, `ToolItem`, `Social`, `MobileTheme` |
477
+ | `TableOfContentsLayout` | `WithMobile`, `Content` |
478
+ | `SearchButton` | `PendingShortcut`, `Label`, `Shortcut`, `Hover`, `Active`, `NativeIcon`, `Icon` |
479
+ | `Layout` | `Islands`, `LockedPage`, `Light`, `Auto` |
480
+ | `HeaderLinks` | `Desktop`, `DesktopLink`, `Link`, `HoverLink`, `PrimaryLink`, `HoverPrimaryLink`, `Trigger`, `HoverTrigger`, `Panel`, `PanelNavigation`, `PanelLink`, `FirstPanelLink`, `Close`, `HoverClose` |
481
+ | `Heading` | `Level1`, `Level2`, `Level3`, `Level4`, `Level5`, `Level6`, `PageTitle` |
482
+ | `HeaderFrame` | None |
483
+ | `PageFrame` | `MainFrame`, `SidebarFrame`, `Columns` |
484
+ | `Steps` | `Item`, `Marker` |
485
+ | `Tabs` | `List`, `Button`, `SelectedButton`, `FocusedButton` |
486
+ | `TableOfContents` | `Heading`, `List`, `Item`, `Link`, `LinkLabel`, `HoverLink`, `CurrentLink` |
487
+ | `ThemeSelect` | `Trigger`, `HoverTrigger`, `ActiveTrigger`, `Icon`, `Panel`, `OpenPanel`, `Section`, `SectionSpacing`, `SectionLabel`, `Option`, `HoverOption`, `CheckedOption`, `FocusedOption`, `Input`, `OptionIcon`, `Checkmark`, `SelectedCheckmark` |
488
+ | `TopNavigation` | `Scrollbar`, `Link`, `HoverLink`, `CurrentLink`, `ActiveIndicator` |
489
+ | `Document` | `All`, `Body`, `Control`, `Pointer`, `ResponsiveWidth`, `ResponsiveHeight`, `Hidden`, `DesktopBlock`, `DesktopFlex`, `ScreenReaderOnly`, `Strong`, `Link`, `NarrowBlock`, `MobileBlock`, `Code`, `FocusRing`, `CurrentLink`, `SearchOpen` |
490
+ | `MainPane` | `WithSidebars` |
491
+ | `MainContent` | `ContentSpacing`, `Container`, `Panel`, `FirstPanel`, `BodyPanel` |
492
+ | `Banner` | `Link` |
493
+ | `SkipLink` | `Focus` |
494
+ | `Search` | `Status`, `Dialog`, `CloseIcon`, `OpenDialog`, `Backdrop`, `Frame`, `Container`, `Close`, `HoverClose`, `ActiveClose` |
495
+ | `Pagination` | `Link`, `NextLink`, `NextIcon`, `NextLabel`, `HoverLink`, `ActiveLink`, `Title`, `LoneNextLink`, `NativeIcon`, `Icon`, `PreviousIconRtl`, `NextIconRtl` |
496
+ | `Markdown` | `Block`, `BlockSpacing`, `HeadingSpacing`, `List`, `CompactItem`, `ListItem`, `DefinitionTerm`, `DefinitionDescription`, `Link`, `HoverLink`, `Quote`, `Rule`, `Details`, `HoverDetails`, `Summary`, `OpenSummary`, `SummaryMarker`, `SummaryIcon`, `OpenSummaryIcon`, `Code` |
497
+ | `MermaidSource` | None |
498
+ | `StarlightAside` | `Note`, `Tip`, `Caution`, `Danger`, `Title`, `Icon`, `FirstContent` |
499
+ | `StarlightCard` | `Title` |
500
+ | `StarlightLinkCard` | `Hover`, `Stack`, `Link`, `LinkOverlay`, `DescriptionAndIcon` |
501
+ | `StarlightBadge` | None |
502
+ | `StarlightSteps` | `Item`, `Counter`, `Connector` |
503
+ | `SyntaxHighlight` | `Scroll`, `Wrap`, `Marker`, `Comment`, `Punctuation`, `Keyword`, `String`, `Token`, `Property`, `Number`, `Function`, `Value`, `Operator`, `Text`, `Bg`, `Inserted`, `Deleted`, `Italic`, `Strong`, `Underline` |
504
+ | `SearchResults` | `Form`, `Input`, `Clear`, `Results`, `Result`, `ResultLink`, `SearchIcon`, `ClearIcon`, `SuppressedClear`, `Message`, `List`, `Title`, `Excerpt`, `NestedResult`, `Match`, `More`, `HoverMore` |
398
505
 
399
506
  `COOKBOOK_COMPONENT_NAMES` publishes the configuration names, and
400
507
  `COOKBOOK_COMPONENT_SUB_ELEMENTS` publishes the complete sub-element lists for
@@ -431,6 +538,10 @@ same.
431
538
  Register custom names and their partial Tasty objects in `theme.customStyles`.
432
539
  Built-in names belong in `theme.styles` and reject misspelled sub-elements.
433
540
  Custom names can also target a matching user-authored `data-tasty-anatomy` attribute.
541
+ The name must match the string passed to `defineComponent()` or
542
+ `resolveComponentStyles()`, or the `data-tasty-anatomy` value. A production
543
+ build warns when a configured custom name has no matching component style
544
+ resolver or rendered anatomy attribute.
434
545
 
435
546
  The default renderer runs Tasty in Astro extract mode. Direct components and
436
547
  the remaining document/vendor bridge styles are collected into shared static
@@ -476,8 +587,8 @@ import { Card, Logo, Preview, Steps, Tabs } from "@tenphi/cookbook/components";
476
587
 
477
588
  See [Authoring components](./authoring.mdx) for complete examples and props.
478
589
 
479
- * `Preview` isolates HTML and CSS with declarative Shadow DOM, or JavaScript
480
- in a sandboxed iframe.
590
+ * `Preview` isolates HTML, CSS, and optional JavaScript in a sandboxed iframe.
591
+ The frame never receives same-origin access to the documentation page.
481
592
 
482
593
  Use MDX only for repository content you control. Locked package content remains
483
594
  Markdown-only unless the consumer explicitly sets `trust: "mdx"` for that
@@ -505,7 +616,84 @@ is needed for static components. Set shared tokens, presets, and states in
505
616
  `docs.config.ts` before rendering. Import the styling entry point in component
506
617
  modules; configuration files should use the main `@tenphi/cookbook` entry point.
507
618
 
508
- ### A custom logo and site title
619
+ ### A shared site logo
620
+
621
+ Set `site.logo` once to use your artwork in the header and mobile drawer:
622
+
623
+ ```ts
624
+ site: {
625
+ title: "Acme",
626
+ logo: "./assets/acme.svg"
627
+ }
628
+ ```
629
+
630
+ Paths are local files relative to the resolved project root, just like
631
+ `site.favicon`. Cookbook reads the intrinsic dimensions and copies the image to
632
+ a content-hashed URL under the deployment base. SVG, PNG, JPEG, WebP, AVIF, and
633
+ GIF are supported. Missing or invalid files fail during setup.
634
+
635
+ ```ts
636
+ site: {
637
+ title: "Acme",
638
+ logo: {
639
+ light: "./assets/acme-light.svg",
640
+ dark: "./assets/acme-dark.svg",
641
+ alt: "Acme documentation",
642
+ href: "/",
643
+ decorative: true
644
+ },
645
+ favicon: "./assets/acme-icon.svg"
646
+ }
647
+ ```
648
+
649
+ Use `src` for one image, or both `light` and `dark`. Variants follow the
650
+ appearance control and the system preference in Auto mode. They must share an
651
+ aspect ratio to prevent layout shifts. Optional `width` and `height` describe
652
+ intrinsic dimensions; Tasty controls display size. Customize
653
+ `theme.styles.SiteLogo` and its complete anatomy: `Image`, `Light`, and `Dark`.
654
+ Wide wordmarks retain their proportions within the header.
655
+
656
+ The logo is decorative by default because the neighboring title labels the
657
+ site. Set `decorative: false` and `alt` when the image itself carries meaningful
658
+ text. The header logo link still has a name when the image is decorative.
659
+ `href` defaults to the localized documentation home; it accepts a root-relative
660
+ documentation route or HTTP(S) destination. The header title and drawer use the
661
+ same destination. `site.logo: false` removes the mark from both locations.
662
+
663
+ A logo does not change the favicon or touch icons. Configure `site.favicon`
664
+ separately for square artwork; the default Cookbook icon set remains in place
665
+ until changed. `Logo` remains available as the standalone Cookbook book mark.
666
+
667
+ The built-in mark uses `theme.palette["logo-surface"]` and `theme.palette["logo-mark"]`.
668
+ Both default to Glaze `mode: "fixed"`: the brand background and light book keep
669
+ their polarity in dark mode while respecting Glaze's tone boundaries and
670
+ high-contrast settings. The mark-to-background contrast floor is 3:1 normally
671
+ and 4.5:1 in high contrast. These colors are separate from interactive accents.
672
+ For example, to customize the logo background while retaining brand hue:
673
+
674
+ ```ts
675
+ theme: {
676
+ brand: { from: "#315efb" },
677
+ palette: {
678
+ "logo-surface": {
679
+ base: "logo-mark",
680
+ tone: 40,
681
+ saturation: 0.8,
682
+ mode: "fixed",
683
+ contrast: { wcag: [3, 4.5] },
684
+ },
685
+ "logo-mark": { tone: 100, saturation: 0, mode: "fixed" },
686
+ },
687
+ }
688
+ ```
689
+
690
+ Custom image assets keep their authored colors; use `light`/`dark` files when
691
+ you want distinct artwork. Header and drawer home links center logos with the
692
+ site title regardless of their aspect ratio. Use `theme.styles.SiteLogo` for
693
+ image size and `theme.styles.StarlightHeader.SiteTitle` / `Sidebar.HomeLink`
694
+ for typography; they do not need baseline offsets for a different preset.
695
+
696
+ ### Advanced site title markup
509
697
 
510
698
  To put your own logo and title in one home link, create
511
699
  `docs/components/site-title.ts`:
@@ -559,20 +747,20 @@ const { siteTitle, siteTitleHref } = Astro.locals.starlightRoute;
559
747
  </SiteTitleRoot>
560
748
  ```
561
749
 
562
- Register the replacement in `docs.config.ts` and hide the default header mark.
750
+ Register the replacement in `docs.config.ts` and disable the shared mark.
751
+ A `SiteTitle` override affects the header; use `site.logo` above for ordinary
752
+ branding shared with the drawer. For custom drawer markup, override `Sidebar`.
563
753
  The custom component supports root styles plus its complete list of named
564
754
  sub-elements: `Logo` and `Label`.
565
755
 
566
756
  ```ts
757
+ site: { logo: false },
567
758
  components: {
568
759
  overrides: {
569
760
  SiteTitle: "./docs/components/SiteTitle.astro"
570
761
  }
571
762
  },
572
763
  theme: {
573
- styles: {
574
- StarlightHeader: { LogoLink: { hide: true } }
575
- },
576
764
  customStyles: {
577
765
  ProjectSiteTitle: { Logo: { color: "#text" } }
578
766
  }
@@ -756,8 +944,264 @@ left-aligned navigation links with a `1bw` gap, without primary button variants.
756
944
  `HeaderLinks.PanelLink` styles the menu rows; `FirstPanelLink` reserves space
757
945
  for the close button. The inset divider above the tabs belongs to `TopNavigation`.
758
946
 
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
947
+ The desktop table of contents is hidden below 72rem. Set
948
+ `tableOfContents: { mobile: true }` to show an optional compact disclosure there.
949
+ `MobileTableOfContents` owns its summary and links; `TableOfContentsLayout`
950
+ controls placement. `Layout` controls shared
762
951
  navigation dimensions and the drawer's scroll lock; `PageFrame` styles the
763
952
  page container and content columns.
953
+
954
+ ### Header control shape
955
+
956
+ Header buttons and the mobile drawer close button use the independent
957
+ `$header-control-radius` token, which defaults to `999px` for pill and circular
958
+ shapes. Set it to `8px` for the same corners as the default content controls:
959
+
960
+ ```ts
961
+ theme: {
962
+ tokens: { "$header-control-radius": "8px" }
963
+ }
964
+ ```
965
+
966
+ `$radius` continues to control code-copy buttons, navigation items, and other
967
+ content controls. Individual `theme.styles` overrides still take precedence.
968
+ The mobile header divider spans the viewport while its content keeps the
969
+ configured horizontal padding.
970
+
971
+ ### Add palette colors and reference other roles
972
+
973
+ Every `theme.palette` entry participates in one Glaze color graph. Use lowercase
974
+ names with hyphens; a role named `review-panel` becomes the Tasty token
975
+ `#review-panel`. Declaration order does not matter. References can target custom
976
+ colors or built-in roles, including header, border, overlay, and syntax colors.
977
+
978
+ ```ts
979
+ theme: {
980
+ brand: { from: "#315efb" },
981
+ palette: {
982
+ "review-ink": {
983
+ base: "review-panel",
984
+ tone: [4, 0],
985
+ saturation: 0.05,
986
+ contrast: { wcag: [7, 10] },
987
+ },
988
+ "review-panel": { base: "surface", tone: "-2", saturation: 0.05 },
989
+ },
990
+ styles: {
991
+ Callout: { fill: "#review-panel", color: "#review-ink" },
992
+ },
993
+ },
994
+ ```
995
+
996
+ Relative and absolute declarations inherit the brand's hue and saturation.
997
+ Use saturation factors from `0` (neutral) to `1` (full seed saturation), and
998
+ `hue` to choose a different hue. All roles resolve for light, dark, and both
999
+ high-contrast modes. Configuration diagnostics and rendering use the same graph.
1000
+ Missing references and cycles fail with the color name; layered configurations
1001
+ resolve references after merging.
1002
+
1003
+ `textSoft` remains an alias for `text-soft`. Use either spelling, but do not
1004
+ configure both. The names `current`, `constructor`, and `prototype` are reserved.
1005
+ Declaring a built-in name intentionally overrides that role; use a project
1006
+ prefix for additional roles to avoid future naming collisions.
1007
+
1008
+ When a status role such as `info` uses a mix or shadow definition, its derived
1009
+ `info-text` defaults to neutral, contrast-corrected text. Override `info-text`
1010
+ explicitly for a colored label; the mix/shadow dependency graph stays intact.
1011
+ A shadow status also uses a neutral derived surface, because Glaze does not allow
1012
+ a shadow to be a mix target. Override the surface separately when needed.
1013
+
1014
+ ### Register units and recipes
1015
+
1016
+ Register custom units and flat recipes in `theme`; Cookbook configures Tasty
1017
+ before evaluating your components:
1018
+
1019
+ ```ts
1020
+ theme: {
1021
+ brand: { from: "#315efb" },
1022
+ palette: {
1023
+ "review-panel": { base: "surface", tone: "-2", saturation: 0.05 },
1024
+ "review-ink": { base: "review-panel", tone: 0, contrast: { wcag: [7, 10] } },
1025
+ },
1026
+ units: { rh: "6px" },
1027
+ recipes: {
1028
+ "review-panel": {
1029
+ fill: "#review-panel",
1030
+ color: "#review-ink",
1031
+ padding: "2rh",
1032
+ radius: "3px",
1033
+ },
1034
+ },
1035
+ customStyles: { DemoBadge: { Label: { color: "#review-ink" } } },
1036
+ },
1037
+ ```
1038
+
1039
+ ```ts
1040
+ import { defineComponent } from "@tenphi/cookbook/styling";
1041
+
1042
+ export const DemoBadge = defineComponent("DemoBadge", {
1043
+ as: "aside",
1044
+ elements: { Label: "strong" },
1045
+ styles: {
1046
+ display: "block",
1047
+ recipe: "review-panel",
1048
+ Label: { preset: "h4" },
1049
+ },
1050
+ });
1051
+ ```
1052
+
1053
+ Use `<DemoBadge><DemoBadge.Label>Review ready</DemoBadge.Label></DemoBadge>` in
1054
+ MDX. Keep these styled components server-rendered: their CSS is extracted at
1055
+ build time. Add behavior with a small client script targeting the rendered
1056
+ markup. This recipe produces 12px padding. Units merge by name, with the later
1057
+ configuration winning. Recipes merge using Tasty `mergeStyles`, including state
1058
+ maps. Recipes are flat: define named sub-elements on the owning component, and
1059
+ compose recipes with `recipe: "base elevated"` rather than referencing a recipe
1060
+ inside another recipe. `none` is a reserved recipe name. Cookbook's built-in
1061
+ units are `x`, `r`, `cr`, and `bw`; overriding one changes its meaning globally.
1062
+
1063
+ Keep editor and linter validation synchronized with your configuration:
1064
+
1065
+ ```ts
1066
+ // tasty.config.ts
1067
+ import { createValidationConfig } from "@tenphi/cookbook/eslint-plugin";
1068
+ import docs from "./docs.config";
1069
+ export default createValidationConfig(docs.theme);
1070
+ ```
1071
+
1072
+ ### Configure Glaze adaptation
1073
+
1074
+ `theme.glaze` accepts `lightTone`, `darkTone`, `darkDesaturation`, `autoFlip`,
1075
+ `pastel`, `inferRole`, and `shadowTuning`. Tone windows accept `[lo, hi]`, `false`
1076
+ for the full range, or the advanced `{ lo, hi, eps }` form. Continue to use
1077
+ `theme.contrastLevel` for manual contrast interpolation. Cookbook always emits
1078
+ all four appearance variants.
1079
+
1080
+ ```ts
1081
+ theme: {
1082
+ glaze: { lightTone: [10, 100], darkTone: [15, 95] },
1083
+ palette: {
1084
+ "accent-surface-subtle": {
1085
+ type: "mix", base: "surface", target: "accent-surface", value: [12, 20],
1086
+ },
1087
+ shadow: {
1088
+ type: "shadow", bg: "surface", fg: "text", intensity: [12, 24],
1089
+ tuning: { alphaMax: 0.3 },
1090
+ },
1091
+ },
1092
+ },
1093
+ ```
1094
+
1095
+ Palette roles accept Glaze's regular, mix, and shadow declarations. Mix and
1096
+ shadow references use the same graph as other roles. Native Glaze restrictions
1097
+ still apply, including the requirement for shadow backgrounds and foregrounds
1098
+ to reference non-shadow colors. `shadowTuning` exposes `saturationFactor`,
1099
+ `maxSaturation`, `lightnessFactor`, `lightnessBounds`, `minGapTarget`, `alphaMax`,
1100
+ and `bgHueBlend`; a role's own `tuning` takes precedence.
1101
+
1102
+ ### Built-in palette inventory
1103
+
1104
+ Every generated role below can be overridden in `theme.palette`. Names map
1105
+ directly to Tasty `#name` tokens. `COOKBOOK_PALETTE_NAMES`, exported from
1106
+ `@tenphi/docs`, lists the same roles for tooling.
1107
+
1108
+ | Area | Roles |
1109
+ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1110
+ | Surfaces | `surface`, `header`, `overlay`, `surface-2`, `surface-3`, `surface-2-hover`, `surface-2-pressed`, `surface-3-hover`, `surface-3-pressed` |
1111
+ | Reading | `text`, `heading`, `text-soft` (alias `textSoft`), `text-muted` |
1112
+ | Brand and focus | `accent-text`, `accent-surface`, `accent-surface-text`, `accent-surface-subtle`, `accent-surface-2-subtle`, `logo-surface`, `logo-mark`, `focus` |
1113
+ | Borders and effects | `border`, `border-strong`, `shadow`, `clear` |
1114
+ | Status | `info`, `success`, `warning`, `danger`, each with `-text` and `-surface` variants |
1115
+ | Additional hues | `orange`, `green`, `blue`, `purple`, `red`, each with `-text` and `-surface` variants |
1116
+ | 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` |
1117
+
1118
+ ## Contrast checks and troubleshooting
1119
+
1120
+ Cookbook checks text, headings, muted reading text, brand links, filled-control
1121
+ labels, status text on its tinted surface, focus rings on each elevated
1122
+ surface, and every palette declaration with a contrast floor. The same Glaze
1123
+ resolution powers rendering and `cookbook doctor`, in light, dark, and both
1124
+ high-contrast modes. Manual `theme.contrastLevel` targets interpolate with
1125
+ Glaze's contrast level.
1126
+
1127
+ `resolveDocsTheme(theme).contrastChecks` lists each foreground/background pair,
1128
+ mode, metric, target, measured value, and pass result. APCA uses display-channel
1129
+ luminance; WCAG ratios use the sRGB transfer function. Transparent foregrounds
1130
+ are composited onto their background before measuring. These checks cover
1131
+ Cookbook's semantic pairs; test actual pages after changing component styles,
1132
+ backgrounds, typography, or layout.
1133
+
1134
+ A `DOCS_SEMANTIC_CONTRAST_UNMET` or `DOCS_BRAND_CONTRAST_UNMET` error names the
1135
+ pair, mode, and required target. Adjust that pair's `theme.palette` declarations:
1136
+ use an absolute tone for reading text, reduce saturation when needed, and keep
1137
+ `autoFlip` enabled when the solver needs to cross its base. A fixed middle-tone
1138
+ button fill may make its label's contrast impossible. Cookbook's default
1139
+ brand fill adapts to avoid this, including for orange, yellow, and pale brands.
1140
+ Focus uses a WCAG 3:1 floor (4.5:1 in high contrast) against the surface ramp.
1141
+
1142
+ Keep contrast requirements when adjusting colors. Extreme custom tone windows,
1143
+ fixed colors, or opacity can prevent a requested floor from being met. A palette
1144
+ check does not replace keyboard, screen-reader, and rendered-page testing.
1145
+
1146
+ ## Customize shared page surfaces
1147
+
1148
+ Each bridge has a complete base style tree; supply only the properties you
1149
+ want to change. Sub-elements keep their selectors and all other defaults.
1150
+ For example, a compact search dialog and quieter pagination need no CSS:
1151
+
1152
+ ```ts
1153
+ theme: {
1154
+ styles: {
1155
+ Search: { Dialog: { inlineSize: "max 32rem" } },
1156
+ SearchResults: { ResultLink: { color: "#accent-text" } },
1157
+ Pagination: { Link: { padding: "1.5x", fill: "#surface" } },
1158
+ Banner: { padding: "1x 2x" },
1159
+ Markdown: { Quote: { color: "#text", inlinePadding: "3x start" } },
1160
+ },
1161
+ }
1162
+ ```
1163
+
1164
+ `Document` owns resets, base typography, generic controls, responsive media,
1165
+ and accessibility utilities. `MainPane` and `MainContent` own the content
1166
+ layout. `Markdown` owns prose, lists, links, quotations, and disclosure
1167
+ anatomy. `Search` owns the dialog; `SearchResults` owns the Pagefind UI.
1168
+ The `Starlight*` trees style compatible upstream component markup; Cookbook's
1169
+ own `Card`, `Callout`, and `Steps` use their respective trees.
1170
+
1171
+ Custom components created with `defineComponent()` receive `theme.customStyles`
1172
+ overrides during server rendering. Units, recipes, states, and presets are
1173
+ resolved there and emitted as static CSS. Cookbook ships no Tasty/Glaze browser
1174
+ runtime, theme configuration, or style hydration setup. Browser scripts and
1175
+ `client:*` islands must not import Cookbook styling, Tasty, or Glaze; the build
1176
+ rejects these imports. Attach small interaction scripts to server-rendered markup
1177
+ or pass already-styled static markup into a client island.
1178
+
1179
+ Code fences and `CodeGroup` use the same `MarkdownCodeBlock` style tree. Its
1180
+ root `$copy-button-size` defaults to `2rem`; the copy control and vertical
1181
+ padding share it, keeping equal top, right, and bottom insets on one-line
1182
+ snippets. The padding follows the `code` preset's line height. Override it with
1183
+ `theme.styles.MarkdownCodeBlock` when changing the control size or code layout.
1184
+
1185
+ ### Syntax highlighting and isolated previews
1186
+
1187
+ Shiki classifies code; Tasty renders every token using Glaze's semantic syntax
1188
+ colors. Customize token rules with `theme.styles.SyntaxHighlight`, or their
1189
+ colors with `theme.palette` (for example `syntax-keyword`). Code fences, including
1190
+ MDX and diff blocks, use classes without inline style attributes. Custom Shiki
1191
+ transformers should emit classes; unsupported inline declarations fail with a
1192
+ configuration hint.
1193
+
1194
+ The production build rejects style attributes, style blocks (including inside
1195
+ shadow-root templates), and non-Tasty stylesheets throughout documentation HTML.
1196
+ It parses actual markup, so escaped examples and strings in scripts are allowed.
1197
+ The explicit exception is the separate document in a sandboxed `Preview` iframe;
1198
+ it cannot access its parent's origin. Configure its size with
1199
+ `theme.styles.Preview.Frame`. The documentation shell supports the CSP directive
1200
+ `style-src-attr 'none'`. A host's CSP may also restrict the preview document; when
1201
+ using that directive, authored previews must use style blocks rather than style
1202
+ attributes.
1203
+
1204
+ Search uses an owned, keyboard-accessible dialog with Pagefind's indexing and
1205
+ results UI. Its clear-button sizing is adapted to `SearchResults.Input` styles,
1206
+ so the browser does not need inline layout declarations. The adapter is checked
1207
+ against the pinned Pagefind UI version during builds.