style: mute body copy, and let the standfirst lead - #82
Conversation
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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
|
Pushed I was wrong that this PR muted the callouts. Fumadocs hard-codes cn("text-fd-muted-foreground prose-no-margin empty:hidden", className)That's set directly on the description div, so it overrides any inherited prose colour — and only The fix you asked for is the same either way, so: Body text → Padding → doubled, Both required reaching the description, which Fumadocs' A dead rule found on the way: [data-callout] { border-radius: 2px !important; }Fumadocs never emits Verified in the built HTML rather than trusting tailwind-merge: Both conflicting pairs collapsed ( |
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
|
Pushed // fumadocs-ui/dist/layouts/docs/sidebar.js
itemVariants = cva("relative flex flex-row items-center gap-2 rounded-lg p-2
text-start text-fd-muted-foreground …")which is the same token Fumadocs already used for callout body text. So this lands the colour back where it started, and the net effect of the callout work is now just the doubled padding and the That also removed the reason for hand-composing Verified in the built HTML that the two now match, and that the padding survived: Worth flagging for when you look at the preview: callout body and page body copy are now the same colour, since #82 also moves Build green, |
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
|
Pushed [data-callout] .prose-no-margin {
color: color-mix(in oklab, var(--color-fd-foreground) 90%, transparent);
}Where the three land, for the record:
Done in CSS rather than threading a class through the component. Fumadocs puts The selector leans on Verified rather than assumed, since this is the third attempt at the same line: Padding is untouched at |
Body text moves to
text-fd-muted-foreground; the text immediately under the heading moves to the foreground colour. Same rule in light and dark.What was actually happening
Fumadocs ships the exact inverse:
.prose)--color-fd-foregroundat 90%--color-fd-muted-foregroundDocsDescription, the<p>under the H1)text-fd-muted-foregroundtext-fd-foregroundSo the lead line was the quietest text on the page and the body the loudest.
Only
--tw-prose-bodymoves. Headings, links, bold, code, quotes and captions each have their own--tw-prose-*variable pointing at the foreground token, so they keep full contrast — and read as more distinct now that paragraph text has stepped back. Both sides reference theme tokens, so light and dark follow automatically with no per-mode override.One thing worth knowing about
The obvious version of this change is a silent no-op. Fumadocs declares
.prosein@layer utilities. Cascade layers beat both specificity and source order, so putting the override in this file's existing@layer componentsblock compiles clean and does nothing — no warning, no error, and it looks correct in the source.I only caught it by checking the compiled CSS:
The fix is to leave the rule unlayered, since unlayered declarations outrank every layer — no
!importantneeded, which matters because this file already leans on!importantfor its other Fumadocs overrides. Re-checked after the fix, the shipped rule lands unlayered. There's a comment inglobal.csssaying so, because the next person to tidy it into the@layer componentsblock below will otherwise quietly revert it.Also verified in the built HTML that
tailwind-mergecollapses the class conflict rather than emitting both classes and leaving it to source order — the description renderstext-lg mb-0 text-fd-foreground, with the default muted class dropped.Scope
Applies to both page routes (
/[[...slug]]and/stack/[[...slug]]), so the v2 and legacy trees stay consistent.Worth a look when you review the preview: callout body text inherits the prose colour and is now muted too (callout titles and icons keep their own
--callout-color). That reads as consistent to me — a callout is body copy — but it's the one place the change reaches beyond ordinary paragraphs, so it's your call. Cards are unaffected; they settext-fd-card-foregroundexplicitly.Build is green at 763 pages.
Note: this will sit at
Internal links — Expecteduntil #81 merges, since the workflow only exists on that branch andv2doesn't have it yet. Same situation as #39.