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

registry:ui · General

anchor — real links, read-only spy

A nav landmark (Anchor) of real fragment links (AnchorItem parts): navigation and smooth scrolling stay native, and JavaScript only reads the scroll position to mark aria-current=location.

install — anchor

$npx jixoai-ui add anchor

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

real fragment links aria-current=location DOM-delegated spy one shared pick engine

overview

Overview

Two composed parts, one read-only behavior: the nav landmark, the fragment links, and the active-link pick.

Anchor is the heading-anchor rail (antd's Anchor) as a composed family. The Anchor root renders the nav landmark; each AnchorItem child renders one real fragment link. You compose the items — the family ships no link list and invents no targets.

The root's one behavior beyond the landmark is the active-link pick: a rAF-throttled scrollspy marks the last target whose top sits at or past the viewport-top line (offset, default 96px — below sticky headers). Targets derive from the root's OWN DOM (every child a[href^="#"]) on every read: no registration, so keyed reorders and conditional items cannot corrupt the spy, and a childList MutationObserver re-derives them when items enter or leave.

A click keeps native fragment navigation and adds two reversible leases: scroll-margin-top = offset lands the heading clear of sticky headers, and a temporary tabindex="-1" moves focus onto it (restored on blur). The pick runs the shared @lib/scroll-spy — anchor answers the simpler which-section question; the toc family runs the full reading-progress engine. For the axis grammar, see the universal props concept page.

usage

Usage

Compose AnchorItem links inside Anchor and point each href at an existing page fragment.

usage
<script lang="ts">
  import Anchor from '@ui/anchor/anchor.svelte';
  import AnchorItem from '@ui/anchor/anchor-item.svelte';
</script>

<Anchor label="on this page">
  <AnchorItem href="#what">what it does</AnchorItem>
  <AnchorItem href="#pick">the line pick</AnchorItem>
</Anchor>

<!-- the spy reads its targets from the root's OWN DOM (child
     a[href^="#"]) — no registration; offset (default 96) drops the
     pick line below sticky headers -->

anchor

Scroll the band below — the anchor rail beside the workbench marks the section crossing the viewport-top line; the page rail in the site chrome tracks the same fragments.

what it does

A composed list of in-page anchors: clicking navigates the fragment natively (smooth scrolling rides the theme's scroll-behavior), and hovering and focusing behave like any link. The root owns ONE behavior beyond the landmark: the active-link pick.

the line pick

The pick is the LAST target whose top sits at or past the viewport-top line (offset, default 96px for sticky headers). Targets come from the root's own DOM: every child a[href^="#"] joins the spy — no registration, no order dependence.

anchor vs toc

toc measures how much of each block fills the viewport (IoM weights) and draws the rule tracker; anchor answers the simpler which-section question with the same shared pick engine — zero coupling beyond the target ids existing.

filler depth 1 — keeps the last section reachable past the offset line

filler depth 2 — keeps the last section reachable past the offset line

filler depth 3 — keeps the last section reachable past the offset line

filler depth 4 — keeps the last section reachable past the offset line

filler depth 5 — keeps the last section reachable past the offset line

filler depth 6 — keeps the last section reachable past the offset line

filler depth 7 — keeps the last section reachable past the offset line

filler depth 8 — keeps the last section reachable past the offset line

filler depth 9 — keeps the last section reachable past the offset line

filler depth 10 — keeps the last section reachable past the offset line

filler depth 11 — keeps the last section reachable past the offset line

filler depth 12 — keeps the last section reachable past the offset line

filler depth 13 — keeps the last section reachable past the offset line

filler depth 14 — keeps the last section reachable past the offset line

anchor-usage.svelte svelte
<script lang="ts">
  import Anchor from '@ui/anchor/anchor.svelte';
  import AnchorItem from '@ui/anchor/anchor-item.svelte';
</script>

<Anchor label="on this page">
  <AnchorItem href="#what">what it does</AnchorItem>
  <AnchorItem href="#pick">the line pick</AnchorItem>
</Anchor>

<!-- the spy reads its targets from the root's OWN DOM (child
     a[href^="#"]) — no registration; offset (default 96) drops the
     pick line below sticky headers -->

api

Props

The root carries the landmark and the pick line; each item is an ordinary composable anchor. The eight style axes resolve on the root and supply downward — the generated Universal props section below is the shared surface.

Anchor

PropertyTypeDefaultDescription
label string'on this page'Accessible name of the navigation landmark.
offset number96Viewport-top pick line in pixels; also the scroll-margin-top lease on landing.
children * Snippet—The AnchorItem links.
...rest HTMLAttributesspreadForwarded to the nav element; class joins the rail atoms.

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)

AnchorItem

PropertyTypeDefaultDescription
href * string—Target fragment, #section-id.
child Snippet—Replacement-element escape; receives the merged anchor props — class with the active paint, href, aria-current, rest.
children Snippet—Visible link label.
...rest HTMLAnchorAttributesspreadForwarded to the anchor element.

axes

The eight axes on anchor

anchor carries all eight lanes as a first-time no-own surface (migration census, W3 close D5): an explicit lane stamps its carrier on the nav and supplies downward — AnchorItem parts and nested components read the supply, the broadcast protocol (吃也供, supply-and-consume). Density is the one axis whose paint the rail itself consumes; the other seven stamp-and-supply carriers its paint does not read (theme's .dark bridge flips the raw token layer only — see the theme row).

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

PropertyTypeDefaultDescription
density data-density rung · --jx-density-coefficientautoThe one axis whose paint the rail itself consumes. Named steps small · medium · large stamp the rung on the nav and re-scope the channels the paint reads — --jx-stack (rail gap), --jx-hit (link min-height), --jx-inset (inline padding), --jx-text / --jx-line (label). A number is a coefficient: it multiplies the same channels and leaves the rung ambient. The five legacy rung spellings (xs · 2xs · sm · default · lg) also address lanes directly.
size --jx-size-effective → root font-sizeautoStamps the carrier on the nav. The labels do not follow — they read the density channels --jx-text / --jx-line, not em — so scale the rail with density. Size supplies downward to nested component consumers. Steps small · medium · large; a number is px.
shape --jx-shape-effective · --jx-radius-factor-effectiveautoCarriers stamp on the nav, but the rail paints no corners — nothing consumes them on this family. Steps round · scoop · bevel · notch · square · squircle; no number lane.
radius --jx-radius-effectiveautoSame absence: a left-spine rail has no corner geometry. Steps small · medium · large; a number is px.
color --jx-color-effectiveautoThe carrier stamps, but the rail's ink reads the semantic tokens directly — --jx-foreground, --jx-muted-foreground, --jx-primary for the active spine — so a hue lane retunes nothing here. Steps primary · secondary · error · warn · success · info; a number is hue degrees.
theme the .dark class bridgeautoStamp-side on this family: a resolved dark puts .dark on the nav and flips the raw token layer (--foreground, --muted-foreground) at that scope — but the rail's ink reads the root-anchored --jx-* aliases, declared only at :root and the stylex theme scopes, which a plain .dark never re-substitutes. The rail follows the site theme, not this lane. Steps light · dark · system; auto inherits the tree.
elevation --jx-elevation-effectiveautoThe carrier stamps; the rail paints no shadow. Steps level-1 · level0 · level1 · level2 · level3 · level4 · level5; a number is exact dp.
motion --jx-motion-effectiveautoThe carrier stamps; the rail's only transition is the fixed --motion-150 / --motion-ease-out color fade — no motion-kernel consumption. Steps reduced · subtle · normal · expressive; a number is a coefficient.

Deviations, cited: the eight-axis adoption itself is the census row — anchor was one of the 13 sweep holes migrated in the W3 close (D5), which retired its declaration-only density posture (openspec/changes/explicit-props/research/migration-census.md). No §13 renames apply: offset and label are family props, not axis names.

TokenDefaultSource
--jx-stackrung scale × coefficientdensity
--jx-hitrung scale × coefficientdensity
--jx-insetrung scale × coefficientdensity
--jx-textrung scale × coefficientdensity
--jx-linerung scale × coefficientdensity
--jx-hairline1pxstructural
the eight axes on anchor
<!-- density: named steps re-scope the rail's spacing channels -->
<Anchor label="ambient rail">…</Anchor>
<Anchor label="compact rail" density="small">…</Anchor>
<Anchor label="roomy rail" density="large">…</Anchor>

<!-- theme: supply-side here — a resolved dark stamps the .dark
     class bridge on the nav; the rail's ink follows the site theme -->
<Anchor label="dark rail" theme="dark">…</Anchor>

<!-- query(): below the lg viewport rung (64rem) the rail rides
     small; at ≥64rem it steps to large. Resize and watch. The
     explicit generics pin the case AND the base to the lane (the
     bare form infers QueryResult<string> — a type error). -->
<Anchor label="responsive rail" density={query<{ lg: DensityLane }, DensityLane>({ lg: 'large' }, 'small')}>…</Anchor>

anchor · the eight axes

auto — ambient scope, stamps nothing
theme="dark" — .dark stamps the nav; the rail's ink stays site-themed
density={query<{ lg: DensityLane }, DensityLane>({ lg: 'large' }, 'small')}

SSR paints the base (small); at the lg viewport rung (≥64rem) the rail steps to large — resize across 64rem and watch.

anchor-axes.svelte svelte
<!-- density: named steps re-scope the rail's spacing channels -->
<Anchor label="ambient rail">…</Anchor>
<Anchor label="compact rail" density="small">…</Anchor>
<Anchor label="roomy rail" density="large">…</Anchor>

<!-- theme: supply-side here — a resolved dark stamps the .dark
     class bridge on the nav; the rail's ink follows the site theme -->
<Anchor label="dark rail" theme="dark">…</Anchor>

<!-- query(): below the lg viewport rung (64rem) the rail rides
     small; at ≥64rem it steps to large. Resize and watch. The
     explicit generics pin the case AND the base to the lane (the
     bare form infers QueryResult<string> — a type error). -->
<Anchor label="responsive rail" density={query<{ lg: DensityLane }, DensityLane>({ lg: 'large' }, 'small')}>…</Anchor>

a11y

Accessibility

Native links in a named navigation landmark; the scrollspy writes aria-current and never moves focus.

Keyboard

KeyAction
TabMoves through the fragment links in composed order; the spy never steals focus
EnterNavigates to the target fragment; focus rides onto the heading (tabindex=-1, restored on blur)

ARIA

AttributeValueDescription
aria-label'on this page' by defaultNames the navigation landmark.
aria-current'location' on the active itemAnnounces the section the scrollspy picked; scrolling alone never moves focus.
href#fragmentReal fragment hrefs keep native navigation, deep links, and middle-click.

Density is also the hit-surface axis here: the link's minimum target height (--jx-hit) measures 32 / 40 / 48px at small / default / large — every rung clears the 24px WCAG 2.5.8 AA target floor.

jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h