From 904198d46b6ef682e9fe02fa23cd22897b8f445d Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 29 Jul 2026 11:41:56 +1000 Subject: [PATCH 1/5] style: mute body copy, and let the standfirst lead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fumadocs ships the inverse of what we want: prose body text is `--color-fd-foreground` at 90%, while DocsDescription — the standfirst directly under the H1 — renders `text-fd-muted-foreground`. So the lead line was the quietest text on the page and the body the loudest. Swaps the two: - `.prose` sets `--tw-prose-body` to `--color-fd-muted-foreground`. Only that one variable moves; headings, links, bold, code, quotes and captions each have their own, so they keep full contrast and now read as more distinct against the softer paragraph text. - Both page routes pass `text-fd-foreground` to DocsDescription. Both sides reference theme tokens, so light and dark follow the same rule with no per-mode override. The `.prose` rule is deliberately **unlayered**. Fumadocs declares `.prose` in `@layer utilities`, and layer order beats both specificity and source order — the same declaration inside the `@layer components` block further down this file compiles fine and is silently ignored. Verified against the built CSS rather than assumed: the shipped rule lands unlayered, after fumadocs' own, and unlayered declarations outrank every layer without needing `!important`. Also verified in the built HTML that tailwind-merge resolves the class conflict rather than emitting both — the description renders as `text-lg mb-0 text-fd-foreground`, with the default muted class dropped. Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8 --- src/app/[[...slug]]/page.tsx | 2 +- src/app/global.css | 21 +++++++++++++++++++++ src/app/stack/[[...slug]]/page.tsx | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/app/[[...slug]]/page.tsx b/src/app/[[...slug]]/page.tsx index 5efba49..7f029e7 100644 --- a/src/app/[[...slug]]/page.tsx +++ b/src/app/[[...slug]]/page.tsx @@ -33,7 +33,7 @@ export default async function Page(props: PageProps<"/[[...slug]]">) { return ( {page.data.title} - + {page.data.description}
diff --git a/src/app/global.css b/src/app/global.css index 79d1558..aab25f2 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -72,6 +72,27 @@ } } +/* ── Body copy ── */ + +/* Fumadocs sets prose body text to `--color-fd-foreground` at 90%, and renders + the page description (DocsDescription, the standfirst under the H1) muted. + We want the reverse: the standfirst leads at full contrast, and the body + copy sits back. Only `--tw-prose-body` moves — headings, links, bold, code, + quotes and captions keep their own variables, so they stay prominent + against the softer paragraph text. + + Both values are theme tokens, so light and dark follow the same rule with + no per-mode override. The description's colour is set at the call site in + the two page routes. + + Deliberately unlayered. Fumadocs declares `.prose` in `@layer utilities`, + and layer order beats both specificity and source order — the same rule + inside `@layer components` below is silently ignored. Unlayered + declarations outrank every layer, so this wins without `!important`. */ +.prose { + --tw-prose-body: var(--color-fd-muted-foreground); +} + /* ── Fumadocs component overrides ── */ @layer components { diff --git a/src/app/stack/[[...slug]]/page.tsx b/src/app/stack/[[...slug]]/page.tsx index ef050ee..992bf75 100644 --- a/src/app/stack/[[...slug]]/page.tsx +++ b/src/app/stack/[[...slug]]/page.tsx @@ -22,7 +22,7 @@ export default async function Page(props: PageProps<"/stack/[[...slug]]">) { return ( {page.data.title} - + {page.data.description}
From 308f0821e99a9b57b3cf1bd39b7e67a9f6d6693e Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 29 Jul 2026 12:03:35 +1000 Subject: [PATCH 2/5] style(callout): un-mute callout body text, and double the padding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes to Callout, plus a correction. **The correction.** I claimed in #82's description that muting `.prose` body text also muted callouts. That was wrong: Fumadocs hard-codes `text-fd-muted-foreground` on `CalloutDescription`, set directly on that div, so it overrides any inherited prose colour — and only `.prose` itself consumes `--tw-prose-body`. Callout text has always been muted; the body change did not touch it. The fix is the same either way, but the reasoning in that description was not. **Body text** now uses `text-fd-foreground`, matching the title (the container is `text-fd-card-foreground`, which resolves to the same value in both themes). A callout reads as one block instead of a heading over greyed-out copy, and stands out from the surrounding muted body copy, which is the point of a callout. **Padding** doubles, `p-3 ps-1` → `p-6 ps-2`. `ps` stays proportionally small because the accent bar is the container's first child and sits near the edge. Both need reaching the description, which Fumadocs' `Callout` does not expose, so we compose `CalloutContainer` / `CalloutTitle` / `CalloutDescription` — its exported primitives — instead. The container still resolves the `warn`/`tip` aliases and picks the icon, so only the trivial wrapper is reimplemented. The wrapper also emits `data-callout`, which Fumadocs does not. global.css has had a `[data-callout]` rule since the theme work that consequently matched nothing; it now applies (its `border-radius: 2px` was already coming from the `[class*="rounded-xl"]` rule above it, so nothing moves). Verified in the built HTML rather than assumed — tailwind-merge collapses both conflicts, leaving `p-6 ps-2` on the container and `prose-no-margin empty:hidden text-fd-foreground` on the description, with no muted class left anywhere in the callout subtree. Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8 --- src/mdx-components.tsx | 47 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx index 57411a1..a3280b5 100644 --- a/src/mdx-components.tsx +++ b/src/mdx-components.tsx @@ -1,13 +1,58 @@ -import { Callout } from "fumadocs-ui/components/callout"; +import { + CalloutContainer, + CalloutDescription, + CalloutTitle, +} from "fumadocs-ui/components/callout"; import { Step, Steps } from "fumadocs-ui/components/steps"; import defaultMdxComponents from "fumadocs-ui/mdx"; import type { MDXComponents } from "mdx/types"; +import type { ComponentProps, ReactNode } from "react"; import { BadExample } from "@/components/bad-example"; import { TrackedCodeBlock } from "@/components/code-block"; import { EqlFn } from "@/components/eql-fn"; import { EqlVersion } from "@/components/eql-version"; import { ZeroKmsRegions } from "@/components/zerokms-regions"; +/** + * Fumadocs' own `Callout` is a thin wrapper over these three primitives, but it + * gives no way to reach the description, which it hard-codes to + * `text-fd-muted-foreground`. Callouts are the one place on the page that + * should not recede, so we compose the primitives ourselves to: + * + * - set the body text to `text-fd-foreground`, matching the title (the + * container is `text-fd-card-foreground`, which resolves to the same value + * in both themes) so a callout reads as one block rather than a heading + * over greyed-out copy; + * - double the padding, `p-3 ps-1` → `p-6 ps-2` (`ps` stays small because + * the accent bar is the first child and sits near the edge); + * - add a `data-callout` attribute, which Fumadocs does not emit. global.css + * already had a `[data-callout]` rule that consequently matched nothing. + * + * `CalloutContainer` still resolves the `warn`/`tip` aliases and picks the + * icon, so only the trivial wrapper is reimplemented. + */ +function Callout({ + title, + children, + className, + ...props +}: ComponentProps & { title?: ReactNode }) { + return ( + // CalloutContainer runs its own `cn`, so it merges these against its + // defaults; a caller's className comes last and still wins. + + {title && {title}} + + {children} + + + ); +} + export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, From fc360ff45dcf82938da58c781d9433a1e8d9a219 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 29 Jul 2026 12:09:44 +1000 Subject: [PATCH 3/5] style(callout): keep Fumadocs' body colour, matching the sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the `text-fd-foreground` from 308f082. `text-fd-muted-foreground` — Fumadocs' default for CalloutDescription — is the same token the sidebar's resting nav items use, so callouts sit at the weight of the surrounding chrome instead of shouting. That means the colour lands back where it started, and the net effect of the callout work is the doubled padding plus the `data-callout` hook. Dropping the colour override also drops the reason for composing CalloutContainer / CalloutTitle / CalloutDescription by hand: reaching the description was the only thing Fumadocs' `Callout` wrapper could not do. It spreads unknown props onto the container, so the wrapper is now four lines over the public component, with no copy of its alias resolution, icon selection or title rendering to drift out of step. Verified in the built HTML that the callout body and a resting sidebar item carry the same class, and that the padding survived the simplification: container: … bg-fd-card text-sm text-fd-card-foreground shadow-md p-6 ps-2 body: text-fd-muted-foreground prose-no-margin empty:hidden sidebar: … rounded-lg p-2 text-start text-fd-muted-foreground … Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8 --- src/mdx-components.tsx | 54 +++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx index a3280b5..622c2e3 100644 --- a/src/mdx-components.tsx +++ b/src/mdx-components.tsx @@ -1,12 +1,8 @@ -import { - CalloutContainer, - CalloutDescription, - CalloutTitle, -} from "fumadocs-ui/components/callout"; +import { Callout as FumaCallout } from "fumadocs-ui/components/callout"; import { Step, Steps } from "fumadocs-ui/components/steps"; import defaultMdxComponents from "fumadocs-ui/mdx"; import type { MDXComponents } from "mdx/types"; -import type { ComponentProps, ReactNode } from "react"; +import type { ComponentProps } from "react"; import { BadExample } from "@/components/bad-example"; import { TrackedCodeBlock } from "@/components/code-block"; import { EqlFn } from "@/components/eql-fn"; @@ -14,42 +10,30 @@ import { EqlVersion } from "@/components/eql-version"; import { ZeroKmsRegions } from "@/components/zerokms-regions"; /** - * Fumadocs' own `Callout` is a thin wrapper over these three primitives, but it - * gives no way to reach the description, which it hard-codes to - * `text-fd-muted-foreground`. Callouts are the one place on the page that - * should not recede, so we compose the primitives ourselves to: + * Callouts keep Fumadocs' own body colour — `text-fd-muted-foreground`, the + * same token the sidebar's resting nav items use — so they sit at the same + * weight as the rest of the chrome rather than shouting. This wrapper only: * - * - set the body text to `text-fd-foreground`, matching the title (the - * container is `text-fd-card-foreground`, which resolves to the same value - * in both themes) so a callout reads as one block rather than a heading - * over greyed-out copy; - * - double the padding, `p-3 ps-1` → `p-6 ps-2` (`ps` stays small because - * the accent bar is the first child and sits near the edge); - * - add a `data-callout` attribute, which Fumadocs does not emit. global.css - * already had a `[data-callout]` rule that consequently matched nothing. + * - doubles the padding, `p-3 ps-1` → `p-6 ps-2`. `ps` stays proportionally + * small because the accent bar is the container's first child and sits + * near the edge; + * - adds a `data-callout` attribute, which Fumadocs does not emit. global.css + * has had a `[data-callout]` rule since the theme work that consequently + * matched nothing. * - * `CalloutContainer` still resolves the `warn`/`tip` aliases and picks the - * icon, so only the trivial wrapper is reimplemented. + * Fumadocs' `Callout` still resolves the `warn`/`tip` aliases, picks the icon + * and renders the title, and it spreads unknown props onto the container, so + * there is nothing here to reimplement. */ -function Callout({ - title, - children, - className, - ...props -}: ComponentProps & { title?: ReactNode }) { +function Callout({ className, ...props }: ComponentProps) { + // CalloutContainer runs its own `cn`, so these merge against its defaults; + // a caller's className comes last and still wins. return ( - // CalloutContainer runs its own `cn`, so it merges these against its - // defaults; a caller's className comes last and still wins. - - {title && {title}} - - {children} - - + /> ); } From 818db4527687f396a98f7ca341f7bb6acdccf27a Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 29 Jul 2026 12:14:49 +1000 Subject: [PATCH 4/5] style(callout): sit callout body one step above the page copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third setting for callout text, and the one between the two already tried: the colour prose body copy had before this branch muted it, `color-mix(in oklab, var(--color-fd-foreground) 90%, transparent)`. `text-fd-foreground` read as shouting; `text-fd-muted-foreground` matched the surrounding paragraphs exactly, leaving the callout to carry its distinction entirely on card background, border, accent bar and icon. This keeps it a step above the page copy without competing with headings. Done in CSS rather than by threading a class through the component, because Fumadocs puts `text-fd-muted-foreground` directly on the description div — inheritance from the container cannot reach it, so the element has to be selected either way. Doing it here keeps the wrapper in mdx-components.tsx at four lines over the public `Callout`, with nothing of its internals duplicated. The selector leans on `data-callout` (ours, from that wrapper) and `prose-no-margin` (Fumadocs' marker on the description's prose block). Unlayered, for the same reason as the `.prose` rule above it: the utility class being overridden lives in `@layer utilities`, and layer order beats specificity. Verified in the built CSS that the rule ships unlayered with both the srgb fallback and the themed `color-mix`, and in the built HTML that the selector matches the rendered description and the doubled padding survived. Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8 --- src/app/global.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/app/global.css b/src/app/global.css index aab25f2..3ebd230 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -93,6 +93,20 @@ --tw-prose-body: var(--color-fd-muted-foreground); } +/* Callout body sits one step above the muted paragraphs around it: the colour + prose body copy had before the rule above. A callout is already set apart by + its card background, border, accent bar and icon; matching the body copy + exactly made it read flat, and full foreground read as shouting. + + Fumadocs puts `text-fd-muted-foreground` directly on the description div, so + inheritance can't reach it — this has to select the element. `data-callout` + is ours (see mdx-components.tsx); `prose-no-margin` is what Fumadocs marks + the description's prose block with. Unlayered for the same reason as the + rule above: the utility class it overrides lives in `@layer utilities`. */ +[data-callout] .prose-no-margin { + color: color-mix(in oklab, var(--color-fd-foreground) 90%, transparent); +} + /* ── Fumadocs component overrides ── */ @layer components { From 7675b52d7beb928df6f69444c8748d661104f22c Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 29 Jul 2026 12:23:22 +1000 Subject: [PATCH 5/5] style(callout): adjust padding for callout component --- src/mdx-components.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx index 622c2e3..e8b9bd4 100644 --- a/src/mdx-components.tsx +++ b/src/mdx-components.tsx @@ -31,7 +31,7 @@ function Callout({ className, ...props }: ComponentProps) { return ( );