Skip to content

Admonition, exercise and proof bodies render at the full 18px content size, with no step down from the surrounding copy #185

Description

@mmcky

Found in the 2026-09 design review of the deployed theme (https://6a9b93b9108dd4beb6bebadc--epic-agnesi-957267.netlify.app, v2.5.0 / main @ 6d773b6). Nothing in this theme sets a font-size on any callout container, so an admonition, exercise, solution or prf:* body renders at .article's full 18px — exactly the size of the copy around it. The Sphinx build steps every one of them down with a single rule that is present verbatim in the stylesheet python-programming.quantecon.org serves, and which off its 18px desktop root resolves to 16.2px, so the box reads as subordinate to the prose. There is no equivalent rule in styles/quantecon.css, in @myst-theme/styles, or in the myst-to-react components.

What this is not. The same screenshot shows the inline maths inside that block reading large against the copy beside it — that ratio is KaTeX's fixed 1.21em, and shrinking the container does not change the maths-to-copy ratio at all, so it is a separate decision.

Measured

Sphinx figures were fetched and grepped directly: python-programming.quantecon.org/functions.html and its _static/styles/quantecon-book-theme.css?digest=1c349b9a0045df0ce6f4a606c2cc9441cbf3bef6. The theme's 18px is a computed font-size read from .myst-exercise on the reviewed deploy during triage; every source-side figure below I confirmed by opening the file.

Surface Theme v2.5.0 Sphinx (≥992px) Difference
Body copy 18px 18px 0%
Admonition / exercise / proof body 18px 16.2px +11.1%
Callout title 18px 16.2px +11.1%
Callout body ÷ body copy 1.00 0.90

The last row is the defect: the Sphinx build expresses a size hierarchy between an aside and the prose it interrupts, and this theme expresses none. The only remaining cues here are the border and a bg-gray-50/10 ground, which on a white page are faint — which is what the review screenshot shows.

The Sphinx side is one rule, quoted from the fetched stylesheet:

.admonition,div.admonition{font-size:.9rem;margin:1.5rem auto;padding:0 1rem .5rem;page-break-inside:avoid;box-shadow:0 .2rem .5rem rgba(0,0,0,.1),0 0 .05rem rgba(0,0,0,.1)}

That is .9rem off a root the same stylesheet sets to 16px, raised to 18px at @media(min-width:992px) — hence 16.2px at desktop. Every callout on the reviewed page carries the class, so exercises, solutions and dropdowns all inherit the step down: class="exercise admonition" appears 5 times, class="solution dropdown admonition" 5 times, class="dropdown admonition hint" once. Titles have no size of their own — of the ten rules in that stylesheet mentioning admonition-title, none sets font-size, and the base rule sets weight, ground, margin and padding only — so they take the container's 16.2px.

Why nothing sets it here

.article { font-size: 1.125rem; /* 18px */ } at styles/quantecon.css:314, inside the @layer components block that opens at line 289, and pinned on first paint by :where(.article){font-size:1.125rem} at app/root.tsx:125. Grepping styles/quantecon.css for admonition, exercise, myst-proof or dropdown returns exactly one hit — a comment at line 282 — and no rule at all. Nothing downstream sizes a callout either: myst-to-react 1.3.0 puts text-sm only on the dropdown chevron.

The selectors to write against

Read from myst-to-react 1.3.0 dist, which is what the deploy renders through — not guessed from the Sphinx class names.

Directive family Wrapper Body Header Header size today
{note} {warning} {tip} {admonition} .myst-admonition (admonitions.js:100) .myst-admonition-body (:122) .myst-admonition-header (:107) text-lg → 18px, rem-fixed
the same with :class: simple .myst-admonition .myst-admonition-body .myst-admonition-header (:109) text-md — inherits
{exercise} {solution} .myst-exercise (exercise.js:40) .myst-exercise-body (:66) .myst-exercise-header (:48) text-md — inherits
{prf:*} — theorem, lemma, definition, example, remark, algorithm, … .myst-proof (proof.js:73) .myst-proof-body (:90) .myst-proof-header (:81) text-md — inherits
{dropdown} .myst-dropdown (dropdown.js:10) .myst-dropdown-body (:10) .myst-dropdown-header (:10) text-lg → 18px, rem-fixed

Two corrections to carry forward, because triage got both wrong. prf:* renders through proof.js as .myst-proof, not through the exercise renderer — the repo's own test asserts ol.closest(".myst-proof-body") at tests/visual/theme.spec.ts:156. And a selector on .admonition or aside.admonition, the Sphinx class names, matches nothing this theme emits.

{dropdown} is included above on this evidence: on the reviewed page Sphinx renders every dropdown as admonition-derived (solution dropdown admonition, dropdown admonition hint), already at .9rem, and both map to .myst-exercise and .myst-admonition here. What is not established is what Sphinx emits for a bare {dropdown}, because there is no instance on that page; including .myst-dropdown is the low-risk choice, leaving it out until an instance turns up is defensible.

.myst-aside is deliberately excluded: the Sphinx stylesheet has no size rule for aside.sidebar, and the margin column is a mystmd surface with no Sphinx counterpart, so there is nothing to bring to parity.

text-md does not exist, so three of the five headers silently inherit

.myst-exercise-header, .myst-proof-header and the simple-admonition header all carry text-md. That class is never generated. Tailwind's default fontSize scale has no md key (xs, sm, base, lg, xl, 2xl, …); @myst-theme/styles' themeExtensions carries no fontSize key at all, its only keys being gridTemplateColumns, gridColumn, typography, keyframes and animation; and this theme's own tailwind.config.js extend adds gridTemplateColumns, gridColumn, colors, fontFamily, keyframes and animation, also no fontSize. Confirmed against the installed package:

node -e "const t=require('@myst-theme/styles'); console.log(Object.keys(t.themeExtensions), 'fontSize' in t.themeExtensions)"

So those three headers take whatever their container gives them, while .myst-admonition-header (non-simple) and .myst-dropdown-header are pinned at text-lg — 18px, root-relative, immune to a container rule. That is invisible today because everything is 18px, and it becomes visible the moment the container rule lands: admonition and dropdown titles would hold at 18px while exercise and proof titles step down with their container, swapping one inconsistency for a new one. That is why this is two rules and not one. Sphinx renders every callout title at its container's size.

The change

Two rules in @layer components, next to the .article rule.

The container value must be root-relative — rem, not em. em off .article compounds: a {note} nested inside an {exercise}, which is common in the lecture corpus, would take the step twice. Sphinx's own value is root-relative for the same reason. This is also the precedent the file already set for inline literals at styles/quantecon.css:225-230, whose comment makes the argument explicitly:

    /* Root-relative on purpose: this must NOT scale with `.article`. The
       Sphinx build sizes inline code at `.9rem` off its own root (16.2px),
       fixed regardless of the surrounding text -- including inside headings.
       Against this theme's 16px root, `1rem` is the same 16px. `1em` would
       inherit the heading scale and blow code in an h2/h3 up to match it. */
    font-size: 1rem; /* 16px; the Sphinx build renders 16.2px */

The two rules, with the container size standing in at 1rem to show the shape:

.article .myst-admonition,
.article .myst-exercise,
.article .myst-proof,
.article .myst-dropdown {
  font-size: 1rem;
}

/* without this, `text-lg` holds admonition and dropdown headers at 18px while
   the exercise and proof headers step down with their container */
.article .myst-admonition-header,
.article .myst-dropdown-header {
  font-size: inherit;
}

Specificity and purging both check out. .article .myst-admonition at (0,2,0) beats the .text-lg utility at (0,1,0) regardless of emission order. And the class names are found candidates — myst-admonition, myst-exercise, myst-proof and myst-dropdown are string literals in node_modules/myst-to-react/dist/*.js, which the Tailwind content list covers via node_modules/myst-to-react/{src,dist}/**/*.{js,ts,jsx,tsx} — so the rules survive the @layer components tree-shaking the file header warns about at styles/quantecon.css:39-44.

Knock-ons to expect. Every em inside a callout re-resolves against the container size instead of 18px, so heading sizes and figure spacing within callouts move with it. The text-lg line-height (1.75rem = 28px) survives the header override and is loose against smaller text, so the header rule may want a matching line-height.

Callout title weight is a separate divergence and is out of scope here: Sphinx sets font-weight:700 on .admonition > .admonition-title, while this theme's headers carry font-medium (500).

Baselines

Five of the six snapshot names move, across all four platform directories (desktop-chrome and mobile-chrome × -darwin and -linux).

Snapshot Page Callouts present Moves?
intro.png /, full page {note} at tests/visual/fixture/intro.md:22 yes
features.png /features, full page {warning} and {tip} at tests/visual/fixture/features.md:65,69 yes
history-open.png /features, full page the same two yes
lists.png /lists, full page {prf:theorem} at tests/visual/fixture/lists.md:49 yes, and only if .myst-proof is in the selector
sidebar-open.png /, viewport only the {note} header sits at the fold, its body below yes — from the header rule alone
notebook.png /notebook none no

Locally the refresh needs --update-snapshots=all: the change on lists.png in particular sits under the 1% maxDiffPixelRatio at tests/visual/theme.spec.ts:32-34, and a plain --update-snapshots will skip it silently (#113). CI's -linux set goes through an /update-snapshots PR comment.

Both open PRs that carry binaries rewrite these same files. #171 rewrites 20 PNGs — intro, features, history-open, notebook and sidebar-open across the four directories, which is every name here except lists.png. #174 rewrites 28 — the same five plus lists.png and a new rtl.png. So this change should be branched from whichever of those lands last rather than refreshing the same binaries in parallel.

No textual conflict in source. #171's styles/quantecon.css hunks are at @@ -81, @@ -98, @@ -243 and @@ -468; none reaches the .article block at 289-316, though they do shift its line numbers. #174 touches app/, styles/app.css and styles/rtl.css, no styles/quantecon.css. #175 touches app/root.tsx and tests/visual/fouc.spec.ts.

The fixture has no exercise

tests/visual/fixture/ covers {note} (intro.md:22), {warning} and {tip} (features.md:65,69) and {prf:theorem} (lists.md:49) — and contains no {exercise} or {solution} directive anywhere. So .myst-exercise, the family the review screenshot is actually of and one of the three carrying the non-existent text-md header, is untested. Adding an {exercise}, and ideally a {solution} dropdown, to features.md in the same change is cheap, and it is the difference between a baseline that proves the change and one that merely records it. Worth doing in this PR; note that #171 already edits features.md, in the Code section above the Admonitions heading.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is wrong or broken in a lecture or builddesign-review

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions