registry:ui · Layout
separator — <hr> is the separator
The W3C already built this one: <hr> carries thematic-break semantics, announcements, and styling for free. Only the vertical posture — splitting inline peers — has no native element, so it takes the ARIA route: a div with role=separator. The ink paints no color: the default variant, fused, is the backdrop's own contrast ghost, every shaped variant rides the same engine, and solid is the one plain-fill escape.
$npx jixoai-ui add separator
https://ui.jixoai.com/r/separator.json
overview
Overview
Two postures, one ink law, zero color: the platform carries the semantics, the family paints physics, and the eight axes ride along unread — the fleet's zero-reader class, measured.
The postures are the platform's. Horizontal renders the native <hr> — thematic-break semantics and the
separator announcement for free, zero ARIA authored; only the vertical posture has no
native element, so it takes the WAI-ARIA route: a div with role=separator and aria-orientation the component owns (landed
AFTER the attribute spread — not overridable). Length stays your layout's job on
purpose: block-stretch horizontal, inline-peer vertical, no length API.
The ink paints no color (Owner ruling, 2026-09-01). The default variant is NAMED
fused — the backdrop's own CONTRAST GHOST, a backdrop-filter: contrast(0.5) strip
that reads as a tonal shift over any ground; dashed (6/4), dense (3/3), dotted and
wavy are MASKS over that same strip; fade rides the BLEND engine — an alpha-ramped
white gradient under mix-blend-mode: difference, peak α capped at 0.6
(the exact-mid blind spot is difference's, and documented, not fixable); solid is the
ONE additive exception — ghost off, plain var(--border) (Owner amendment,
2026-09-08). Six of seven variants therefore read NO token at all — theme-free by
mechanism, adaptive over any ground in any scope. Solid is the family's only token
read, and its alias is ROOT-PINNED: --jx-border: var(--border) resolves
at the root, so the light ink oklch(0 0 0) propagates pre-substituted — a scoped
.dark island cannot re-derive it (measured frozen), and at root-level dark the
UNPINNED solid FLIPS oklch(0 0 0) → oklch(1 0 0) (measured in place and by a
body-level move — the earlier "frozen everywhere" holds were the theming-demo
stage's data-theme="light" pin). THREE TIERS, all measured at the paint: root
dark flips (live :root substitution), mid-tree .dark islands cannot,
data-theme stages locally pin.
The eight axes are the fleet's ZERO-READER CLASS (the stack structural class,
measured): density is a managed stamp, size stamps --jx-size-effective with nothing behind it reading, and shape / radius /
color / elevation / motion supply unread (grep receipts over ui/separator/) — the supply chain to composed descendants is the point,
not consumption. Kinship: stack (gaps instead
of rules — the layout alternative), menu and button-group (the strip living inside grouped
chrome), timeline (the ruled scale).
separator
Horizontal renders the native hr; vertical renders the ARIA div and stretches its container's cross axis.
A paragraph of ordinary copy above the rule.
And copy below it — the thematic break reads natively.
<Separator /> <!-- hr: the contrast ghost -->
<Separator orientation="vertical" /> <!-- ARIA div: inline peer split -->
<Separator variant="dashed" /> <!-- 6/4 dashes over the ghost -->
<Separator variant="wavy" /> <!-- the SVG sine mask -->
<Separator variant="fade" /> <!-- blend: transparent→dark→transparent -->
<Separator variant="solid" /> <!-- the --border plain-fill escape -->
<!-- length is layout: -->
<Separator class="my-6" />
<div class="h-5"><Separator orientation="vertical" /></div>ink engine
The ink engine — no color, only physics
A separator paints no color (Owner ruling, 2026-09-01): border-color is for borders. The default variant is NAMED fused — the backdrop's own CONTRAST GHOST, a backdrop-filter: contrast(0.5) strip that reads as a tonal shift over any ground. Dashed, dense, dotted and wavy are MASKS over that same strip; fade rides the BLEND engine — an alpha-ramped white gradient under mix-blend-mode: difference, inverting the backdrop toward mid exactly as its alpha ramps: transparent → light → dark → light → transparent. The one exception: solid (Owner amendment, 2026-09-08) turns the ghost off and paints plain var(--border) — the escape for grounds where the ghost's exact-mid blind spot or a patterned backdrop defeats subtraction.
separator · ink engine
the contrast ghost (default)
the plain-fill escape — var(--border), ghost off
6/4 dashes
3/3 dense dashes
a chain of dots
the SVG sine mask
blend: transparent → dark → transparent
the ghost and its masks track the whole ramp; the fade's blend eases toward exact mid-gray — its one blind spot — and stays a tonal shift everywhere else. No color token anywhere.
<script lang="ts">
import Separator from '@ui/separator.svelte';
</script>
<div class="flex w-full max-w-lg flex-col gap-4">
<div class="flex flex-col gap-1.5">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">fused</span>
<Separator />
<span class="text-muted-foreground text-[12px]">the contrast ghost (default)</span>
</div>
<div class="flex flex-col gap-1.5">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">solid</span>
<Separator variant="solid" />
<span class="text-muted-foreground text-[12px]">the plain-fill escape — var(--border), ghost off</span>
</div>
<div class="flex flex-col gap-1.5">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">dashed</span>
<Separator variant="dashed" />
<span class="text-muted-foreground text-[12px]">6/4 dashes</span>
</div>
<div class="flex flex-col gap-1.5">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">dense</span>
<Separator variant="dense" />
<span class="text-muted-foreground text-[12px]">3/3 dense dashes</span>
</div>
<div class="flex flex-col gap-1.5">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">dotted</span>
<Separator variant="dotted" />
<span class="text-muted-foreground text-[12px]">a chain of dots</span>
</div>
<div class="flex flex-col gap-1.5">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">wavy</span>
<Separator variant="wavy" />
<span class="text-muted-foreground text-[12px]">the SVG sine mask</span>
</div>
<div class="flex flex-col gap-1.5">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">fade</span>
<Separator variant="fade" />
<span class="text-muted-foreground text-[12px]">blend: transparent → dark → transparent</span>
</div>
</div>
<!-- the auto-adaptive proof: the ghost and its masks track the whole
light→dark→light ramp; no color token anywhere -->
<div
class="flex w-full max-w-lg flex-col gap-4 border border-border p-4"
style="background: linear-gradient(90deg, oklch(0.98 0 0), oklch(0.35 0 0), oklch(0.98 0 0))"
>
<Separator />
<Separator variant="dashed" />
<Separator variant="fade" />
</div>demo
Length is layout
The component draws the line; the consumer decides how long it is. Horizontal rules stretch to their container (or any width class); vertical rules stretch the container's cross axis — put one in a fixed-height flex row and it fills it.
separator · length
<script lang="ts">
import Separator from '@ui/separator.svelte';
</script>
<div class="flex w-full max-w-md flex-col gap-5">
<div class="flex flex-col">
<span class="text-muted-foreground text-[11px]">full width — the default stretch</span>
<Separator />
</div>
<div class="flex flex-col">
<span class="text-muted-foreground text-[11px]">class="w-1/2" — any width class</span>
<Separator class="w-1/2" />
</div>
<div class="flex flex-col">
<span class="text-muted-foreground text-[11px]">class="my-6" — length is also rhythm</span>
<Separator class="my-6" />
</div>
<div class="flex h-8 items-stretch gap-4 text-[13px]">
<span>h-8 row</span>
<Separator orientation="vertical" />
<span>the rule fills the cross axis</span>
</div>
</div>W3C foundation
What the platform gives
No ARIA to maintain on the horizontal path — the browser announces <hr> as a separator to assistive tech. The vertical path is the WAI-ARIA separator pattern, aria-orientation included.
<Separator /> <!-- hr: the contrast ghost -->
<Separator orientation="vertical" /> <!-- ARIA div: inline peer split -->
<Separator variant="dashed" /> <!-- 6/4 dashes over the ghost -->
<Separator variant="wavy" /> <!-- the SVG sine mask -->
<Separator variant="fade" /> <!-- blend: transparent→dark→transparent -->
<Separator variant="solid" /> <!-- the --border plain-fill escape -->
<!-- length is layout: -->
<Separator class="my-6" />
<div class="h-5"><Separator orientation="vertical" /></div>types
Types
Two postures: the native hr for thematic breaks, the ARIA div for inline peer splits.
separator · types
the native hr — thematic break between blocks
<script lang="ts">
import Separator from '@ui/separator.svelte';
</script>
<div class="flex flex-wrap items-start gap-6">
<div class="flex min-w-56 flex-col gap-3 border border-border p-4">
<span class="font-nav text-primary text-[11px] uppercase tracking-[0.24em]">horizontal (default)</span>
<Separator />
<span class="text-muted-foreground text-[12.5px]">the native hr — thematic break between blocks</span>
</div>
<div class="flex min-w-56 flex-col items-center gap-3 border border-border p-4">
<span class="font-nav text-primary self-start text-[11px] uppercase tracking-[0.24em]">vertical</span>
<div class="flex h-10 items-stretch gap-4 text-[13px]">
<span>first</span>
<Separator orientation="vertical" />
<span>second</span>
</div>
<span class="text-muted-foreground self-start text-[12.5px]">role=separator div — splits inline peers, stretches the cross axis</span>
</div>
</div>usage
Usage
One prop, no length API on purpose — length is your layout's job.
<Separator /> <!-- hr: the contrast ghost -->
<Separator orientation="vertical" /> <!-- ARIA div: inline peer split -->
<Separator variant="dashed" /> <!-- 6/4 dashes over the ghost -->
<Separator variant="wavy" /> <!-- the SVG sine mask -->
<Separator variant="fade" /> <!-- blend: transparent→dark→transparent -->
<Separator variant="solid" /> <!-- the --border plain-fill escape -->
<!-- length is layout: -->
<Separator class="my-6" />
<div class="h-5"><Separator orientation="vertical" /></div>theming
Theming
No color decision of its own — the ink is physics, not palette: the contrast ghost adapts to whatever ground it crosses, the blend fade inverts it, and the one plain fill (solid) simply reads the --border token. Length comes from layout.
| Token | Default | Source |
|---|---|---|
| contrast ghost | backdrop-filter: contrast(0.5) | structural |
| masks (dashed/dense/dotted/wavy) | mask over the ghost strip | structural |
| blend fade | mix-blend-mode: difference | structural |
| solid fill | var(--border) | color |
api
API
Props from the Separator Props interface — everything else rides through as native hr attributes.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| orientation | 'horizontal' | 'vertical' | 'horizontal' | horizontal renders the native hr; vertical renders the role=separator div. The mask axis swaps with it. |
| variant | 'fused' | 'solid' | 'dashed' | 'dense' | 'dotted' | 'wavy' | 'fade' | 'fused' · Own default, not ambient | The ink geometry: fused is the bare contrast ghost (the named default); dashed (6/4), dense (3/3), dotted and wavy are masks over it; fade rides the blend engine; solid is the plain-fill var(--border) escape — the subtraction-ink exception (Owner 2026-09-08). Own default, not ambient (ink geometry is never a paint-zone rung). |
| class | string | '' | Class passthrough — width/height/margin live here, by design. |
| style | string | null | — | Inline style passthrough, composed AFTER the family's carrier stamp (the #4 seam law: never clobbered, never dropped). |
| ...rest | HTMLAttributes<HTMLHRElement> | spread | Every other attribute lands on the element (vertical spreads onto the div). |
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) |
axes
The eight axes on separator
The fleet's ZERO-READER CLASS, measured (the stack structural class): density is a managed stamp (data-density, nothing reads it), size stamps --jx-size-effective with no consumer behind it, and shape / radius / color / elevation / motion supply unread — grep receipts over ui/separator/. The one token read in the whole family is solid's var(--border) fill (the additive exception), and the theme split is BY MECHANISM, measured: six variants are physics (theme-free everywhere), one rides a :root alias — frozen under a scoped .dark island, FLIPPED by root-level dark when unpinned, locally PINNED by data-theme demo stages (three tiers). The supply chain to composed descendants is the point, not consumption.
| Property | Type | Default | Description |
|---|---|---|---|
| density | '2xs' | 'xs' | 'sm' | 'default' | 'lg' | 'auto' | number (+ the five legacy spellings) | 'auto' | MANAGED STAMP, ZERO-READER CLASS (the stack structural class) — the resolved rung is stamped on the element (data-density, measured 'sm' under density="small") and the ambient scope channel keeps flowing for composed descendants, but NOTHING in the family reads it: zero density-carrier readers in ui/separator/ (grep receipt), the strip's 1px is engine-fixed. Number unit: coefficient. |
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | ROOT STAMP, ZERO-READER — the §11 carrier stamps --jx-size-effective on the element and the query() seat below rides THAT STAMP as its live receipt (13px → 18px across 48rem, measured); the strip itself never scales (blockSize 1px, engine-fixed) and zero size-effective readers exist (grep receipt). Number unit: px. |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' | SUPPLY-ONLY — zero shape-channel readers (grep receipt: no corner-shape or factor consumer in ui/separator/). Number unit: none. |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | SUPPLY-ONLY — zero radius-effective readers (grep receipt); the masks do their own geometry (the dotted chain, the SVG wave) and a 1px strip has no corner to round. Number unit: px. |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' | ZERO-READ BY RULING — the INK LAW (Owner, 2026-09-01): a separator paints no COLOR, border-color is for borders. Six of seven variants read no token AT ALL (subtraction/blend physics); solid is the one token read in the family — the additive exception's var(--border) fill (grep receipt: tokens['--jx-border'] ×2 in the atoms, everything else token-free). Number unit: hue degrees. |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' | THE MECHANISM SPLIT, MEASURED (the frozen-ink seam's separator instance) — SIX variants are MECHANISM-FREE physics: the contrast ghost and the masks read the BACKDROP, not a token (auto-adaptive, flips nothing, freezes nothing), and the blend fade inverts by physics. ONE variant rides the TOKEN layer, and the ride is SCOPED: solid's fill is --jx-border, a :root-declared alias (--jx-border: var(--border)) whose var() substitutes AT THE ROOT — the light oklch(0 0 0) propagates pre-resolved, so a mid-tree .dark island CANNOT re-derive it (measured: the island flipped inherited --border to oklch(1 0 0) while the fill held oklch(0 0 0) — frozen); only root-level dark re-derives the alias (html.dark → oklch(1 0 0), measured). Root-pinned values cannot re-derive MID-TREE — and at root dark the unpinned solid flips while data-theme=light demo stages locally PIN (three tiers, the task-74 pin-finder; the W-next #7 shape narrowed to its mid-tree clause). The resolved dark theme still stamps class:dark for composed descendants. No number lane. |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' | SUPPLY-ONLY — zero elevation-carrier readers (grep receipt); a 1px strip carries no shadow tier. Number unit: dp. |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' | SUPPLY-ONLY, STATIC INK — zero motion-carrier readers (grep receipt); the family authors no animation and no transition (the ink is instantaneous physics — nothing to tween). Number unit: coefficient. |
Receipts: the omission census (the component's bare hr carries data-jx-separator="fused" + data-orientation + class only — no density attr, no style attr, no role; vertical lands role=separator + aria-orientation AFTER the spread, not overridable), the managed stamp (data-density="sm" under density="small"), the variant ladder (blockSize 1px base / 2px dotted / 6px wavy; backdrop-filter contrast(0.5) on the ghost and its masks, none on solid and fade; difference blend on fade only), the ghost's auto-adaptivity (the strip pixel = contrast(0.5) of its ground, EXACT at both ends: ground 218 → strip 173 on a light ground (−45) and ground 58 → strip 93 on a dark ground (+35) — the fixed-point law 0.5C + 0.25 to the digit, sign flip pixel-measured at two device scale factors), the theme split (under a scoped .dark island the inherited --border flipped to oklch(1 0 0) while solid's fill HELD oklch(0 0 0) — the :root alias substitutes before inheritance — and at root dark the UNPINNED solid flips oklch(1 0 0) in place (the task-74 pin-finder; the earlier element-holds read superseded); the ghost's backdrop-filter byte-identical across both) and the query seat (the --jx-size-effective carrier flips 18px → 13px across the 48rem viewport key on the element's own style attr — the seat rides a full-width ground so the stamp is measurable at page scale) were measured on this page's served DOM (probe, task 40); the unread rows carry grep receipts over ui/separator/. LAW #19 id landscape: duplicate ids NONE page-wide; the keyed variants gallery mounts 7/7. The query() seat below rides the md viewport key (48rem) on the size lane.
<script lang="ts">
import Separator from '@ui/separator.svelte';
import { query } from '@lib/universal-props-query.svelte';
</script>
<!-- the md key is the registered VIEWPORT scale (48rem): below it the
base (13px) stamps, at 48rem+ the md case (18px) wins — the
carrier flips on the element while nothing in the family reads
it (the zero-reader class): the stamp IS the receipt -->
<Separator size={query({ md: 18 }, 13)} />The md key is the registered VIEWPORT scale (48rem): below it the base (13px) stamps; at 48rem and wider the md case wins (18px at a 1280 viewport, measured) — the carrier flips on the element's style attr while no family rule consumes it (the zero-reader class): the stamp is the receipt. The number lane goes bare.
Separator · universal props
<!-- the eight-axis surface on the ink strip -->
<Separator density="small" />
<Separator size={14} variant="dashed" />a11y
Accessibility
Horizontal needs no ARIA at all — the browser announces hr natively; vertical carries the WAI-ARIA separator pattern.
ARIA
| Attribute | Value | Description |
|---|---|---|
| hr | native | Announced as a separator/thematic break by the platform — zero wiring owed |
| role | separator | On the vertical path only (component-owned, not overridable) |
| aria-orientation | "vertical" | Set with the role on the vertical path |