registry:ui · Data Display
prose — the reading region, eleven knobs and two channels
NOT <Typography> — that word is markdown's live user-facing vocabulary (<Markdown typography="relaxed"); the provider is <Prose>, rendering a div.jx-pure[data-jx-prose] host. Eleven frozen v1 knobs, every one an absentSlot in ProseDefaults: ABSENCE IS THE STATE — an unset knob emits nothing, stamps nothing, and the ambient channel (an outer prose region's declarations, or no opinion at all) keeps flowing; nested regions inherit every knob they do not set, nearest setter wins. Two channels carry the state: the JS scope (resolved through the plugin chain AT THE PROVIDER — a print-medium plugin can strip gradient/ground before CSS sees them — then provided through the typography lib's context pair) and the CSS residue sheet (prose.css), whose every rule sits at (0,2,0): above the face element rules, BELOW the markdown sheet. That ladder IS the sovereignty contract — an outer <Prose measure leading> can never fight <Markdown typography=…>; ink and flow pass through; chrome stays unaffected BY CASCADE; code/kbd stay mono under family; density stamps only for an explicit lane (W3-D2, the markdown trio founding law).
$npx jixoai-ui add prose
https://ui.jixoai.com/r/prose.json
usage
Usage
Mount the region, compose paragraphs with the text family inside (the residue lanes hook [data-jx-text='p'] — the P component, not a bare p). Set only the knobs you hold opinions on; every unset knob lets the ambient flow.
<script lang="ts">
import Prose from '@ui/prose';
import { P } from '@ui/text';
</script>
<div class={cx(rt.prsGrid760)}>
<div class={cx(rt.col8)}>
<span class={cx(rt.note11)}>ambient — no knob set</span>
<Prose>
<P>The face's own channels carry this region: 14px body, the p lane at 1.6, ink at the foreground token. Nothing was stamped.</P>
</Prose>
<span class={cx(rt.note11)}>size + leading — scale and flow</span>
<Prose measure="1.0625rem" leading={1.9}>
<P>17px by inheritance; the P rides the region's 1.9 leading through the presence-gated residue rule. The same string would move a heading's em ladder for free — ambient size scales the ladder.</P>
</Prose>
</div>
<div class={cx(rt.col8)}>
<span class={cx(rt.note11)}>align justify + hyphens auto (lang on the host)</span>
<Prose align="justify" hyphens="auto" lang="en" measure="13.5px">
<P>Justified columns read best when the engine may break words: hyphens auto needs a lang on the host or an ancestor, and the two knobs are documented as a pair — justify without hyphens rivers, hyphens without justify never shows its work.</P>
</Prose>
<span class={cx(rt.note11)}>wrap pretty — the prose word</span>
<Prose wrap="pretty">
<P>Pretty wrapping breaks the final line where the reader needs it; balance is the heading-scope word, stable the tabular one.</P>
</Prose>
</div>
</div>prose
The knob showcase — scale and flow. size rides pure inheritance (headings and paragraphs both scale, and a nested Markdown preset still beats it); leading is the P-only lane (headings keep their 1.25, pre keeps 1.55); align justify pairs with hyphens; wrap pretty is the prose word.
The face's own channels carry this region: 14px body, the p lane at 1.6, ink at the foreground token. Nothing was stamped.
17px by inheritance; the P rides the region's 1.9 leading through the presence-gated residue rule. The same string would move a heading's em ladder for free — ambient size scales the ladder.
Justified columns read best when the engine may break words: hyphens auto needs a lang on the host or an ancestor, and the two knobs are documented as a pair — justify without hyphens rivers, hyphens without justify never shows its work.
Pretty wrapping breaks the final line where the reader needs it; balance is the heading-scope word, stable the tabular one.
<script lang="ts">
import Prose from '@ui/prose';
import { P } from '@ui/text';
</script>
<div class={cx(rt.prsGrid760)}>
<div class={cx(rt.col8)}>
<span class={cx(rt.note11)}>ambient — no knob set</span>
<Prose>
<P>The face's own channels carry this region: 14px body, the p lane at 1.6, ink at the foreground token. Nothing was stamped.</P>
</Prose>
<span class={cx(rt.note11)}>size + leading — scale and flow</span>
<Prose measure="1.0625rem" leading={1.9}>
<P>17px by inheritance; the P rides the region's 1.9 leading through the presence-gated residue rule. The same string would move a heading's em ladder for free — ambient size scales the ladder.</P>
</Prose>
</div>
<div class={cx(rt.col8)}>
<span class={cx(rt.note11)}>align justify + hyphens auto (lang on the host)</span>
<Prose align="justify" hyphens="auto" lang="en" measure="13.5px">
<P>Justified columns read best when the engine may break words: hyphens auto needs a lang on the host or an ancestor, and the two knobs are documented as a pair — justify without hyphens rivers, hyphens without justify never shows its work.</P>
</Prose>
<span class={cx(rt.note11)}>wrap pretty — the prose word</span>
<Prose wrap="pretty">
<P>Pretty wrapping breaks the final line where the reader needs it; balance is the heading-scope word, stable the tabular one.</P>
</Prose>
</div>
</div>demo
Indent and the drop cap — the P-only lanes
indent is a first-line opinion the region holds about paragraphs only (headings never inherit one — a heading is not a paragraph); '2em' is the 中文稿纸 convention. initialLetter ships BOTH sheet arms — @supports initial-letter where the engine has it, the float ::first-letter fallback everywhere else — because a browser-class-dependent drop cap reads as a bug. The same P suppresses the indent: a drop cap plus a first-line indent double-counts. Drop caps KEEP printing on paper — the manuscript posture, the recorded decision.
<!-- 中文稿纸惯例: the 2em first-line indent, P-only -->
<Prose indent="2em">
<P>中文稿件每段首行缩进两字,标题永不缩进——标题不是段落。</P>
</Prose>
<!-- the drop cap: BOTH sheet arms, P-only, suppresses the indent -->
<Prose initialLetter={3}>
<P>The first letter sinks three lines — @supports initial-letter where
the engine has it, the float ::first-letter fallback everywhere else.</P>
</Prose>中文稿件每段首行缩进两字,正文齐头齐尾。段落之间不加空行,节奏全部由首行缩进承担——这是稿纸的惯例,区域的意见。
第二段同样缩进。缩进只作用于段落:区域内的标题永不继承首行缩进,因为标题不是段落。
The drop cap sinks three lines in every engine: the modern initial-letter path inside @supports, the floated first letter everywhere else. If the same region also set indent, this P would suppress it — the compound rule.
demo
Ink, gradient, ground — and the chrome that ignores them
ink is the curated four-word union (default/muted/primary/destructive → the four foreground tokens) with a raw escape; links keep primary inside a region (the face B2 element rule — the recorded exception). The gradient is FILL-ONLY: background-clip: text with -webkit-text-fill-color, never color: transparent — currentcolor on a mark would resolve to the inherited transparent and vanish; marks restore solid by element rule, and --jx-ty-ink carries only color tokens so Heading's var-fallback utility never sees an invalid var. ground tints the region; Mark's own highlight law is untouched. And the orthogonality probe: chrome beside the paragraph is unaffected BY CASCADE — element-level declarations beat an inherited wrapper color.
<Prose gradient={{ from: 'var(--primary)', to: 'var(--info)' }}>
<Heading level={2}>A gradient the marks survive</Heading>
<P>Fill-only: <Strong>strong</Strong> and <Mark>mark</Mark> keep their
voice — currentcolor restores against the inherited solid ink.</P>
</Prose>The region mutes its ink and tints its ground — and the chip beside this paragraph keeps its own ink: element-level declarations beat the inherited wrapper color, so control chrome never follows the region's ink by accident.
ink is the four-token union plus the raw escape — destructive here, any css color verbatim for everything else.
A gradient the marks survive
Fill-only lane: strong, mark and the rest keep their voice — the solid color stays inherited, so currentcolor restores against it. Print and forced-colors restores live in the sheet: the paged clone never re-runs the provider.
demo
Family — and the code that will not follow it
family words resolve to the theme's --font-* tokens; code and kbd keep their own mono element rules (face B1) — a family change is a prose opinion, never a code opinion, so InlineCode chips inside stay their calibrated mono regardless.
prose · family
The whole region sets in the theme's mono token — and the inline chip npm run verify keeps its own mono law anyway: code's face rules are element-level, so family is prose-only by construction.
<script lang="ts">
import Prose from '@ui/prose';
import { P } from '@ui/text';
import InlineCode from '@ui/inline-code.svelte';
</script>
<div class={cx(rt.col8)}>
<span class={cx(rt.note11)}>family mono — the region, not the chips</span>
<Prose family="mono" measure="13px">
<P>The whole region sets in the theme's mono token — and the inline chip <InlineCode lang="text">npm run verify</InlineCode> keeps its own mono law anyway: code's face rules are element-level, so family is prose-only by construction.</P>
</Prose>
</div>The serif word resolves to var(--font-serif) — and this theme does
NOT define --font-serif today (only
--font-sans and --font-mono). Until the theme ships the token, family="serif" degrades to inheritance
(the declaration computes to nothing). That is documented, not assumed: the knob is
frozen with the word, the token is the theme's to add.
law
Sovereignty — the trio wins, by cascade
The layering ladder is the whole contract: face element rules (0,1,1) < prose residue rules (0,2,0) < the markdown sheet's §2a (0,2,1) and typography trio (0,3,0) < consumer utilities. An outer <Prose measure leading> wrapping a <Markdown typography=…> can never fight the preset — the trio's root declarations beat inheritance, and the residue rules lose to §2a inside [data-jx-markdown]. Zero JS masking, zero markdown changes; ink and flow knobs still pass through.
<Prose measure="1.125rem" leading={2}>
<Markdown typography="relaxed" {source} />
</Prose>
<!-- the trio's root declarations beat inheritance BY CASCADE: the outer
size/leading can never fight <Markdown typography=…> inside -->Sovereignty, demonstrated
The typography trio owns scale inside markdown — this relaxed preset stamps its own root declarations, and the outer <Prose measure leading> wrapper loses to them by cascade, not by JS masking.
Ink and flow knobs still pass through: the region mute tints these lines, but the scale stays the trio's.
This paragraph is not inside markdown: it takes the region's 18px and the P lane's 2.0 leading. The ladder applies where the sheets speak — §2a only ever speaks inside [data-jx-markdown].
a11y
Accessibility
The host is a generic flow container — no role, no landmark claim; the reading semantics live in the content's own elements. The presence hooks are styling seams, never semantics. The two engine restores keep the words readable everywhere: forced-colors drops the clipped gradient for CanvasText ink, and print restores solid fill on paper.
Keyboard
| Key | Action |
|---|---|
| — | Not focusable — a reading region, not a control |
ARIA
| Attribute | Value | Description |
|---|---|---|
| div[data-jx-prose] | generic container | No ARIA injected; the region is structural. Compose landmarks (section, article) around it as the document demands. |
| data-jx-ty-* attrs | presence hooks | data-jx-ty-leading/-indent/-initial and data-jx-ty-ink="gradient" are styling/test seams stamped only when the knob is set — they carry no semantics. |
| gradient lane | forced-colors restore | The clipped fill drops to currentcolor under forced colors (CanvasText); nothing informational depends on the gradient. |
| drop cap | ::first-letter paint | initialLetter is paint on the real first letter — screen readers announce the word whole; it keeps printing on paper (the manuscript posture). |
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. THE §13 RENAME (Owner table ruling): the type-scale knob is measure now — never size; the freed name is the universal scale axis. Density stamps only for an EXPLICIT lane (the markdown trio's founding law — ambient control chrome keeps flowing).
Prose · universal props
The §13 rename: measure is the css type scale; size is the universal scale axis — both live on one host.
Named steps resolve via the alias ladder; the knobs stay sovereign by cascade.
<Prose measure="1.0625rem" size={18}>…</Prose>api
API
Eleven knobs, every one an absentSlot — no own, no fallback; absence IS the state. Consumer class merges LAST; consumer style lands after the emitted declarations (consumer wins on conflict); rest props pass through untouched.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| measure | CssLength | absent | The region's type scale — inheritance ONLY, never an element stamp: markdown sovereignty is a cascade fact, not a JS mask, and ambient measure scales the heading em ladder for free. RENAMED from size (explicit-props §13, W3-D2): a css measure is not the base-scale axis — the freed name belongs to the universal size lane. |
| leading | number | absent | P-only line-height lane (headings keep their 1.25, pre keeps 1.55); applied through the presence-gated residue rule on [data-jx-text='p']. |
| family | 'sans' | 'mono' | 'serif' | raw | absent | Words resolve to the theme --font-* tokens — 'serif' NEEDS the theme to define --font-serif (this theme does not yet; it degrades to inheritance until it does) — or a raw font-family value. Code/kbd keep their own mono element rules. |
| ink | 'default' | 'muted' | 'primary' | 'destructive' | raw | absent | The four foreground tokens or a raw color; also mirrors as --jx-ty-ink for Heading's var-fallback utility. Links keep primary inside a region (the face B2 element rule, the recorded exception). |
| gradient | { from: string; to: string; angle?: number | string } | raw | absent | Fill-only lane: structured stops compose a linear-gradient (numeric angle to degrees, default 180deg) or a raw CSS image value verbatim. Never color: transparent — marks restore solid by element rule; print/forced-colors restores live in the sheet. |
| ground | 'background' | 'card' | 'popover' | 'muted' | 'secondary' | 'accent' | 'transparent' | raw | absent | The region's ground token or raw value; Mark's own highlight law is untouched. |
| align | 'start' | 'center' | 'end' | 'justify' | absent | Inheritance lane; justify pairs with hyphens (docs guidance — justify without hyphens rivers). |
| indent | CssLength | absent | P-only first-line indent; '2em' is the 中文稿纸 convention. Headings never inherit one; suppressed on a drop-cap P. |
| initialLetter | 2 | 3 | 4 | 5 | absent | Drop-cap depth; BOTH sheet arms (@supports initial-letter + the float ::first-letter fallback); suppresses indent on the same P; keeps printing on paper. |
| wrap | 'pretty' | 'balance' | 'stable' | absent | pretty for prose scopes, balance for heading scopes, stable where lines must not reflow — guidance, not enforcement. |
| hyphens | 'auto' | 'none' | 'manual' | absent | auto needs a lang on the host or an ancestor (documented); pairs with align justify. |
| children | Snippet | — | The region's content — compose with the text family (P and the marks) so the residue lanes hook [data-jx-text='p']. |
| class | string | '' | cn()-merged onto the host LAST — consumer classes win through tailwind-merge dedupe. |
| ...rest | HTMLAttributes<HTMLDivElement> | spread | Every other attribute (including lang, needed by hyphens auto) passes through to the host div; consumer style lands after the emitted declarations. |
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) |