registry:ui · Data Display
math-block — display math, baked at prerender
The display math surface: a native <figure> (figure semantics kept) framing a wide-equation strip that rides the SHARED scroll-run contract wholesale — createScrollStamp's verdict (data-jx-scroll-state none|start-closed|end-closed|open) gates ScrollChrome's shadow veil and nudge chips, so a formula that fits paints no chrome — with real KaTeX markup rendered SYNCHRONOUSLY during SSR/prerender (the isomorphic-small lane: true zero flash, zero CLS, print-freeze-safe markup — NOT code-card's floor-then-upgrade), a copy control carrying the raw TeX source (press physics, clipboard fallback, labels localization payload), and errors painted IN PLACE (the errorColor token run inside the same box, one console.warn diagnostic; a caller-forced throw degrades to the raw source — errors never escape a component boundary).
$npx jixoai-ui add math-block
https://ui.jixoai.com/r/math-block.json
usage
Usage
tex is a runtime string rendered synchronously in display mode — the markup IS the paint, so prerender bakes it and a prop change re-derives live.
<script lang="ts">
import MathBlock from '@ui/math-block';
</script>
let tex = $state('\\int_{-\\infty}^{\\infty} \\mathrm{e}^{-x^2} \\, dx = \\sqrt{\\pi}');
</script>
<!-- display math: markup bakes during prerender — zero flash, zero CLS -->
<MathBlock {tex} />
<!-- the wide strip: long formulas scroll, chips and veil ride the
shared scroll-run verdict; copyable=false drops the footer -->
<MathBlock
tex="\begin{aligned} \nabla \cdot \mathbf{E} &= \frac{\rho}{\varepsilon_0} \\ \nabla \times \mathbf{B} &= \mu_0 \mathbf{J} \end{aligned}"
copyable={false}
/>math-block
The complete figure: the strip (the run is the scroller), ScrollChrome's shadow veil + nudge chips gated by the scroll verdict, and the copy control carrying the raw TeX. The playground swaps the formula — a wider one flips the verdict open and the chrome arms.
<script lang="ts">
import MathBlock from '@ui/math-block';
</script>
<!-- the tex prop is a runtime string; the markup bakes at prerender -->
<MathBlock tex="\mathrm{e}^{\mathrm{i}\pi} + 1 = 0" />scroll law
Wide equations — the shared scroll strip
A display formula is not prose: it must not wrap. The strip is a scroll-run (data-jx-scroll-run + data-axis=horizontal) inside a one-cell grid host; the stamp verdict gates the shadow veil and the two nudge chips, and a formula that fits paints no chrome at all.
math-block · wide strip
aligned — Maxwell's equations
matrices — a linear system wider than the column
Both ride the same contract as tabs and button-group strips — the ONE
scrollable-region system, never a local copy. The run measures along the inline
axis only; the verdict none | start-closed | end-closed | open is the single truth every overlay keys on, and pre-hydration the shared css
paints nothing (the prerendered markup stands alone).
<script lang="ts">
import MathBlock from '@ui/math-block';
</script>
<div class="border border-border p-4">
<p class="font-nav mb-3 text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
aligned — Maxwell's equations
</p>
<MathBlock tex="\begin{aligned}\nabla \cdot \mathbf{E} &= \frac{\rho}{\varepsilon_0} & \nabla \cdot \mathbf{B} &= 0 \\\nabla \times \mathbf{E} &= -\frac{\partial \mathbf{B}}{\partial t} & \nabla \times \mathbf{B} &= \mu_0 \mathbf{J} + \mu_0 \varepsilon_0 \frac{\partial \mathbf{E}}{\partial t}\end{aligned}" copyable={false} class="w-full max-w-[42rem]" />
</div>
<div class="border border-border p-4">
<p class="font-nav mb-3 text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
matrices — a linear system wider than the column
</p>
<MathBlock tex="\mathbf{A}\mathbf{x} = \mathbf{b}, \quad \mathbf{A} = \begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \end{pmatrix}" class="w-full max-w-[42rem]" />
</div>no-scroll variant
Fit — scale instead of scroll
fit=true forces the formula into the container: a font-size scale (KaTeX is em-based throughout, so it re-lays out honestly — no transform residue, no layout compensation), never scaling up. Print engages fit by DEFAULT (beforeprint/afterprint track the medium): a paged sheet never owes a horizontal scrollport — print this page and the linear system below arrives whole.
math-block · fit
the same linear system, fitted — no strip, no chips
The fitter measures the formula's natural width and clamps the wrapper's font-size to the run's client box (re-measured on resize); when fit is off the inline scale clears and the scroll law owns the strip again — the stamp verdict follows the restored width naturally.
<script lang="ts">
import MathBlock from '@ui/math-block';
</script>
<!-- fit: the no-scroll variant — a font-size scale, never scaling up -->
<div class="border border-border p-4">
<p class="font-nav mb-3 text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
the same linear system, fitted — no strip, no chips
</p>
<MathBlock tex="\mathbf{A}\mathbf{x} = \mathbf{b}, \quad \lVert \mathbf{A}\mathbf{x} - \mathbf{b} \rVert_2 \le \varepsilon" fit copyable={false} class="w-full max-w-[42rem]" />
</div>errors
Invalid TeX paints in place
throwOnError:false is the facade default: the bad source run renders error-tinted inside the same box — no error chrome, no thrown exception — and one console.warn carries the KaTeX diagnostic.
math-block · error paint
an unclosed group — \frac{ left open
The error run paints in var(--error) — katex's own
output under the facade's errorColor token binding,
so light/dark follows the sheet with zero re-render. A caller forcing throwOnError: true (strict options) still gets a
safe surface: the component catches, paints the escaped raw source, and warns —
errors never escape a component boundary.
<script lang="ts">
import MathBlock from '@ui/math-block';
</script>
<!-- throwOnError:false is the facade default: the bad source run
renders error-tinted inside the same box -->
<div class="border border-border p-4">
<p class="font-nav mb-3 text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
an unclosed group — \frac{ left open
</p>
<MathBlock tex="\frac{\pi}{2} + \frac{" class="w-full max-w-[40rem]" />
</div>a11y
Accessibility
The figure keeps figure semantics; role=math lives on the inner content-only wrapper, so the copy button stays a discoverable interactive node. KaTeX's hidden MathML is the screen-reader path — no aria-label shadows it.
Keyboard
| Key | Action |
|---|---|
| Tab | Reaches the strip (focusable scroll run) and the copy control |
| ← / → | Scroll the focused strip when the verdict is open |
| Enter / Space | Activate the copy button (raw TeX payload) |
ARIA
| Attribute | Value | Description |
|---|---|---|
| role="math" | inner wrapper | Only the katex output carries it — the figure and the button keep their own semantics. |
| hidden MathML | katex output | The screen-reader path ships in the markup itself (output: htmlAndMathml default); surfaces mount no aria-label. |
| aria-label | copy / copied | On the copy button; flips for the 1.6s feedback window. |
theming
Tokens
The surface owns almost no paint: KaTeX paints in currentColor, the error run rides the error token, and the strip's chrome rides the shared scroll-run knobs on the host.
| Token | Default | Source |
|---|---|---|
| --error | jixoai-theme error token | color |
| --ring | jixoai-theme ring token | color |
| --jx-scroll-veil | inset · 1.5 | color |
| --jx-scroll-chevron-* | shared defaults | color |
axes
Universal props
The eight-axis surface (explicit-props): size · shape · radius · density · color · theme · elevation · motion — each axis takes named steps, auto (inherit the ambient context; stamps nothing), an exact number (px · coefficient · dp · hue per axis), or query() for responsive/container-conditional values. FIRST-TIME contract, all no-own: the KaTeX engine's internals (the em-based formula scale, the fit-mode compensation) are OUTSIDE the supply set — the surface rides the family's OWN figure root (the size lane scales the shell chrome).
MathBlock · universal props
The engine's em scale is outside the supply set; the shell's chrome scales.
<MathBlock size="medium">…</MathBlock>api
API
Six props plus the HTML rest; tex is the only required one. Everything else is composition over the facade.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| tex * | string | — | TeX source (runtime string — rendered synchronously in display mode). |
| copyable | boolean | true | Copy control on the footer bar (press physics, clipboard fallback, 1.6s copied feedback; the payload is the raw TeX). |
| labels | { copy?: string; copied?: string } | — | Localization payload for the control vocabulary; absent = the English literals shipped, rendered byte-identically. |
| macros | KatexOptions['macros'] | — | KaTeX macros — merged per key OVER the site-level registerMacros table ($lib/katex). |
| strict | KatexOptions['strict'] | — | KaTeX strict mode passthrough (boolean | ignore | warn | error | handler). |
| trust | KatexOptions['trust'] | — | KaTeX trust passthrough (boolean | handler). |
| fit | boolean | false (print: true) | The no-scroll variant: force-scale the formula into the container instead of scrolling — a font-size fit (KaTeX is em-based, so it is a true re-layout), never scaling up. PRINT engages fit by default regardless of the prop: a paged sheet never owes a horizontal scrollport. |
| class | string | '' | Merged onto the figure through cn(). |
| …rest | HTMLAttributes<HTMLElement> | — | Spread onto the figure BEFORE the component’s own data-kind/data-jx-math-block stamps — consumer attributes land, component semantics stay un-overridable. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |