jixoai-ui
the terminal design language
loading the style sheet…
Skip to content
docs · print medium: screen

registry:ui · Data Display

heading — the ladder emigrated from the face

A native h1–h6 by level (1–6, rounded then clamped), carrying the em size ladder that used to live in the markdown prose face: 1.875em at h1 down through 1em at h5/6. em, never rem — the hierarchy scales with the ambient font-size preset and survives the escape from any face. What it owns: bold, leading 1.25, the foreground ink, the size. What it refuses: a paint ladder (level IS the structural axis), block margins, and a Defaults file (level is not a style prop).

install — heading

$npx jixoai-ui add heading

https://ui.jixoai.com/r/heading.json

native h1–h6 em ladder · preset-scaled level = the structural axis no margins of its own

overview

Overview

One clamped value derives everything: the native element, the em rung and the hook never disagree.

Heading renders one native element — h1 through h6 via svelte:element — from a single clamped level: round to the nearest integer, bound to 1–6, then derive the tag, the em-scaled size rung and the data-jx-heading hook from that ONE value, so an out-of-range prop never desyncs the rendered truth from anything. Default 2 — h1 is the page's one-per-page title, and a component defaulting to it would mint competing titles; the markdown map always passes level explicitly.

The em ladder is em, never rem: h1 1.875em → h2 1.5em → h3 1.25em → h4 1.125em → h5/h6 1em, of the ambient font-size (the typography trio presets bump it; the whole hierarchy rescales). No block margins — preflight zeroes them and the component stamps none back; the container rhythm owns spacing. No Defaults file: level is structural, not a style prop (the separator literal-axis precedent). Kept outside the eight axes per migration-census §13.

The paint is exactly the face's B1 heading channels — bold, leading 1.25, and the ink seam var(--jx-ty-ink, var(--foreground)) — so the component works standalone AND rejoins the prose face by hook: inside a data-jx-ty-ink='gradient' region the face fills the glyphs through data-jx-heading; the component owns no gradient knowledge. A leaf in the fleet (grep receipt: only the blueprint scenes mount it) — composition is consumer-side. The eight axes resolve on the h root: size is the one axis that repaints (via its stamp), theme is the partial pole (ink flips, ladder frozen), and six stamp-and-supply. Per-axis below; the shared grammar lives on the universal props page.

usage

Usage

Hand it a level; the element, the size and the hook all derive from the one clamped value — the tag and data-jx-heading never disagree.

Heading usage
<script lang="ts">
  import { Heading } from '@ui/heading';
</script>

<!-- the ladder: level sets the element AND the em-scaled size -->
<Heading level={2}>The section title</Heading>
<Heading level={3}>A subsection</Heading>

<!-- standalone: the hierarchy scales with the ambient font-size preset
     (em, never rem) and carries no margins of its own -->
<div class="flex flex-col">
  <Heading level={4}>Tight composition</Heading>
  <p>The container rhythm owns the spacing.</p>
</div>

heading

The level ladder, rendered: each level is the native element at its em-scaled size with the same bold 1.25 leading and foreground ink. The demo spans levels 2–6 — a demo-minted h1 would compete with the page's single hero title (the one-h1 law); level 1 renders at 1.875em.

h2 · 1.5em

The level 2 rung

h3 · 1.25em

The level 3 rung

h4 · 1.125em

The level 4 rung

h5 · 1em
The level 5 rung
h6 · 1em
The level 6 rung
heading-usage.svelte svelte
<script lang="ts">
  import { Heading } from '@ui/heading';
</script>

<!-- the ladder: level sets the element AND the em-scaled size -->
<Heading level={2}>The section title</Heading>
<Heading level={3}>A subsection</Heading>

<!-- standalone: the hierarchy scales with the ambient font-size preset
     (em, never rem) and carries no margins of its own -->
<div class="flex flex-col">
  <Heading level={4}>Tight composition</Heading>
  <p>The container rhythm owns the spacing.</p>
</div>

demo

Standalone — outside any prose face

The component owns its channels outright, so it works with no jx-pure scope in sight: same ink, same weight, same ladder. It carries no margins — the container rhythm owns the spacing, GitHub's container-tight posture — and the markdown map escapes it (no-jx-pure) so the face stops double-painting it.

the six levels
<!-- the six levels (level 1 clamps in; h1 is the page's
     one-per-page title — components default to 2) -->
<Heading level={1}>1.875em — document title</Heading>
<Heading level={2}>1.5em — section</Heading>
<Heading level={3}>1.25em — subsection</Heading>
<Heading level={4}>1.125em — minor</Heading>
<Heading level={5}>1em — quiet label</Heading>
<Heading level={6}>1em — quietest</Heading>

No face, no problem

The heading above rendered with zero prose-face context — the em ladder, weight and ink are the component's own. The gap you see is this container's rhythm, not the heading's margin.

api

Props

The table renders from the GENERATED meta + curation; the eight axis rows split into the shared section beneath. 14 meta props − 8 ambient axes = 6 rows; no EXTRA lane — level is the §13-kept structural prop.

Properties

PropertyTypeDefaultDescription
level number2The heading level: sets the native element (h1–h6), the em-scaled size rung and the data-jx-heading hook from ONE clamped value (rounded, then bounded) — the rendered truth never desyncs from the raw prop. Default 2 — h1 is the page's one-per-page title, and a component defaulting to it would mint competing titles; the markdown map always passes level explicitly. Kept outside the eight axes per migration-census.md's §13 row (component-specific, no collision with the size axis).
id string—Optional explicit address — anchor links and aria-labelledby point here.
style string | null—Inline style passthrough — composed AFTER the family's carrier stamp (the #4 seam law: never clobbered, never dropped).
children Snippet—The heading text.
class string''Forwarded to the rendered heading element; consumer classes land last.
rest unknown (spread passthrough)—Every other attribute passes through to the native heading element untouched (Omit<…, 'color'> — the §1 native-collision rule).

Universal props

PropertyTypeDefaultDescription
size'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopethe base scale — root font-size; parts size in em
shape'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto''auto' · ambient scopecorner geometry (CSS corner-shape; §14 degrade table)
radius'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopecorner size; auto = the concentric broadcast (§3)
density'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopespacing/leading scale over the kernel channels (§4)
color'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string'auto' · ambient scopethe hue axis of the fixed oklch primary system (§5)
theme'light' | 'dark' | 'system' | 'auto''auto' · ambient scopelight/dark profile; system = the JS-mutable global (§6)
elevation'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number'auto' · ambient scopeofficial M3 levels over the surface ladder (§7)
motion'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number'auto' · ambient scopeintensity across the motion kernels (§8)

axes

The eight axes on heading

heading is a typography primitive (census batch D): the paint is three fixed voices — weight, leading, the ink seam — and the em ladder, so six of the eight axes stamp-and-supply without a reader. The exceptions: size REPLACES the em rung through its carrier stamp (the only axis that repaints), and theme is the partial pole — the seam's --foreground fallback flips under a dark island while the ladder stays frozen. The carriers stamp the h root itself (the promoted root is self-carried), greppable in the raw SSR.

Reading the table: Property is the axis, Type is the real carrier or consumption it drives on THIS family, Default is the lane default — the named steps, number unit, and consumption are in each description.

PropertyTypeDefaultDescription
size 'small' | 'medium' | 'large' | 'auto' | number'auto'CONSUMED VIA THE STAMP — the one axis that repaints. The §11 carrier emits BOTH --jx-size-effective AND the root's font-size (font-size: var(--jx-size-effective, 1rem)) inline on the h root, and inline wins the cascade — an explicit lane REPLACES the em rung (size={14} on an h3 renders exactly 14px; named steps resolve --jx-size-small/medium/large = 14/16/18px). auto stamps NOTHING and the em ladder rules: font-size = the level's rung × the ambient preset. Zero family css reads the var (grep receipt) — the stamp is the whole consumption. Number unit: px.
shape 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto''auto'SUPPLY-ONLY — stamps --jx-shape-effective and --jx-radius-factor-effective; no family css reads them (grep receipt: zero readers). Text has no corners. Number unit: none.
radius 'small' | 'medium' | 'large' | 'auto' | number'auto'SUPPLY-ONLY — stamps --jx-radius-effective; no family css reads it (grep receipt: zero readers) — the heading draws no boxes. Number unit: px.
density 'small' | 'medium' | 'large' | 'auto' | number (+ the five legacy spellings)'auto'SUPPLY-ONLY — the named rung stamps data-density and the lane resets --jx-density-coefficient; neither moves paint (grep receipt: zero --jx-density-* reads, and the --jx-text/--jx-line channels the rung scope re-bases are unread too — zero hits in ui/heading/). The heading's type voice is em-of-font-size, NOT the --jx-text channel: the density axis has no lever here, and a wrapper-level coefficient cannot re-base the rung (the declaring-element law). Number unit: coefficient.
color 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string'auto'SUPPLY-ONLY — stamps --jx-color-effective; no family css reads it (grep receipt: zero readers — zero hue-carrier and zero oklch-formula reads). The ink rides the seam: var(--jx-ty-ink, var(--foreground)) — the theme's foreground, never the hue carrier. Number unit: hue degrees.
theme 'light' | 'dark' | 'system' | 'auto''auto'THE PARTIAL POLE — one raw-token voice flips. A resolved dark stamps .dark on the h root, and the root's OWN ink reads the raw token: color: var(--jx-ty-ink, var(--foreground)) re-substitutes against the .dark slot block and the heading re-inks (measured below). Everything else is theme-free and frozen: the weight, the leading and the em ladder carry no raw reads (grep receipt: the only raw token in ui/heading/ is --foreground). Inside a prose face with data-jx-ty-ink='gradient' the seam pins to --jx-ty-ink instead — the prose scope owns the ink and the island can't take it. light and system stamp nothing — tree inheritance.
elevation 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number'auto'SUPPLY-ONLY — stamps --jx-elevation-effective; no family css reads it (grep receipt: zero readers). Text casts no shadow. Number unit: dp.
motion 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number'auto'SUPPLY-ONLY — stamps --jx-motion-effective; no family css reads it. Zero transitions in the family — nothing animates, so the axis has nothing to step. Number unit: coefficient.

Deviations, cited: the adoption is the census batch D row (the long tail — openspec/changes/explicit-props/research/migration-census.md); level is KEPT outside the axes per the §13 row (component-specific, no collision with the size axis — the family's local em ladder even renamed from size to sizeLadder in the same ruling). The §1 collision rule: the native element never receives a size or color attribute — Omit<HTMLAttributes<HTMLHeadingElement>, 'color'> clears the one native name at stake, and every axis name is the family's own destructured lane.

the eight axes on heading
<!-- size: an explicit lane RESOLVES the em rung — the §11
     carrier stamps the root's font-size itself (inline wins the
     cascade); auto keeps the ladder scaling with the ambient preset -->
<Heading level={3}>1.25 × the ambient preset</Heading>
<Heading level={3} size={14}>14px — the number is verbatim</Heading>
<Heading level={3} size="large">18px — named steps resolve --jx-size-*</Heading>

<!-- theme: the PARTIAL pole — a dark island re-inks through the
     seam's --foreground fallback; the em ladder is theme-free -->
<Heading level={3} theme="dark">dark ink, same rung</Heading>

heading · the consumed lane and the partial pole

auto — the em rung: 1.25 × the ambient preset

1.25 × the ambient preset

size={14} — the rung replaced: exactly 14px

14px — the number is verbatim

size="large" — --jx-size-large = 18px

18px — the named step

theme="dark" — the ink flips; the rung is byte-identical

dark ink, same rung

The top pair is the disagreement probe: same level, same tag — the unstamped h3 computes 1.25 × the inherited font-size while the stamped one computes exactly 14px (inline wins the cascade). The dark panel re-inks through the seam's --foreground fallback; its font-size, weight and leading are byte-identical to the light panels — the theme row's partial flip, not a full repaint.

heading-axes.svelte svelte
<!-- size: an explicit lane RESOLVES the em rung — the §11
     carrier stamps the root's font-size itself (inline wins the
     cascade); auto keeps the ladder scaling with the ambient preset -->
<Heading level={3}>1.25 × the ambient preset</Heading>
<Heading level={3} size={14}>14px — the number is verbatim</Heading>
<Heading level={3} size="large">18px — named steps resolve --jx-size-*</Heading>

<!-- theme: the PARTIAL pole — a dark island re-inks through the
     seam's --foreground fallback; the em ladder is theme-free -->
<Heading level={3} theme="dark">dark ink, same rung</Heading>
one real query() case
<script lang="ts">
  import Heading from '@ui/heading.svelte';
  import { query } from '@lib/universal-props-query.svelte';
</script>

<!-- below 48rem the base (14px) applies; at 48rem+ the md case
     (18px) wins and the h3 steps up -->
<Heading level={3} size={query({ md: 18 }, 14)}>responsive voice</Heading>

heading · query()

responsive voice

Media keys are min-width: below 48rem the base applies — 14px; at 48rem and wider the md case wins — 18px. The number lane goes bare (results infer); it's the STRING lanes that need both generics. Resize across 48rem.

heading-query-demo.svelte svelte
<script lang="ts">
  import Heading from '@ui/heading.svelte';
  import { query } from '@lib/universal-props-query.svelte';
</script>

<!-- below 48rem the base (14px) applies; at 48rem+ the md case
     (18px) wins and the h3 steps up -->
<Heading level={3} size={query({ md: 18 }, 14)}>responsive voice</Heading>
TokenDefault
the em ladder1.875 / 1.5 / 1.25 / 1.125 / 1em
--jx-weight-bold700
--jx-leading-tight1.25
var(--jx-ty-ink, var(--foreground))--foreground fallback
--heading-size-1 … --heading-size-5unset (fallbacks = own values)
--jx-size-effectivestamped on explicit size

a11y

Accessibility

Native heading semantics — the document outline is real. The id prop is the addressing surface anchor links and aria-labelledby point at.

Keyboard

KeyAction
—Not focusable — document structure, not a control

ARIA

AttributeValueDescription
h1–h6native elementsThe platform announces the level and builds the outline; skipping levels is the caller's outline decision, never silently repaired.
idpassthroughOptional explicit address — the document addressing surface (anchor navigation, aria-labelledby).
data-jx-headingclamped levelHook attribute carrying the rendered level (1–6) — the clamped truth, never the raw prop.
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h