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

registry:ui · Data display

descriptions — composed dt/dd pairs, never a table

The enterprise detail view as a composed dl: Descriptions (columns/bordered HOW-props) + DescriptionsItem (term → dt, children → dd — rich cells are just children; childless items render the em dash).

install — descriptions

$npx jixoai-ui add descriptions

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

dl · never a table term prop → dt · children → dd columns via container query bordered hairline frame vertical · responsive · extra recipes

overview

Overview

The enterprise detail view, W3C-first: a dl IS a description list — dt/dd pairs in source order over a grid, one DescriptionsItem per pair. The bordered look is CSS on the same dl; columns and bordered are HOW-props — they change how the grid paints, never what renders.

One DescriptionsItem per pair: the term prop renders the dt, children render the dd — rich cells are just children (badges, links, markup), and a childless Item renders the em dash, never a blank cell. columns=N splits rows into N pairs per row, clamped 1–4 and responsive down to one pair per row on narrow containers — the container query reads the dl's OWN container, never the viewport, so any layout column inherits the law.

The anatomy rides the density ruler's ambient channels — the term's micro-label voice and the value's body voice re-tier with the ambient scope while the eight-axis carriers the root stamps have zero family readers: the chrome's theme voices are stylex aliases frozen at the :root pole (the avatar twin). The per-axis receipts are below.

usage

Usage

The composition contract in one sample: import the family from the registry barrel (@ui/descriptions/index — per-part targets exist per file). There is no items[] prop and no value snippet — rich cells are plain children of the Item.

usage
<Descriptions columns={2} bordered>
  <DescriptionsItem term="build">4f2a1c</DescriptionsItem>
  <DescriptionsItem term="owner">@gaubee</DescriptionsItem>
  <DescriptionsItem term="status"><!-- rich cells are just children -->
    <Badge>passing</Badge>
  </DescriptionsItem>
  <DescriptionsItem term="notes" /><!-- no children ⇒ the em dash -->
</Descriptions>

descriptions

One Item per pair: the term prop renders the dt, children render the dd — the status badge is just children, the childless notes Item falls back to the em dash.

build
4f2a1c
checks
12 passed · 0 failed
owner
@gaubee
region
iad1 · washington
status
passing
notes
—
descriptions-usage.svelte svelte
<Descriptions columns={2} bordered>
  <DescriptionsItem term="build">4f2a1c</DescriptionsItem>
  <DescriptionsItem term="owner">@gaubee</DescriptionsItem>
  <DescriptionsItem term="status"><!-- rich cells are just children -->
    <Badge>passing</Badge>
  </DescriptionsItem>
  <DescriptionsItem term="notes" /><!-- no children ⇒ the em dash -->
</Descriptions>

lane shapes

Description layouts

Descriptions is a semantic dl: one or more term/value pairs per row, the bordered treatment optional. This canvas composes its drawer from the stage markup itself — the code shown is the code running.

descriptions · types

owner
gaubee
status
live
scope
public
descriptions-types.svelte svelte
<script lang="ts">
import Descriptions from '@ui/descriptions/index';
import DescriptionsItem from '@ui/descriptions/index';
</script>

<div class={cx(rt.deGridMd2)}>
  <div class={cx(rt.panel)}><Descriptions><DescriptionsItem term="owner">gaubee</DescriptionsItem></Descriptions></div>
  <div class={cx(rt.panel)}><Descriptions columns={2} bordered><DescriptionsItem term="status">live</DescriptionsItem><DescriptionsItem term="scope">public</DescriptionsItem></Descriptions></div>
</div>

examples

Examples

Ability-named recipes: vertical terms, responsive columns, extra header actions.

All three compose public structure — the Item's grid, the dl's container query, and a wrapper around the list. Nothing here reaches into the registry.

with vertical terms

antd's layout=vertical, composed: each Item re-runs its own grid as one column — the dt stacks above its dd. Same dl, same semantics, the term reads as a field label instead of a row lead. This drawer composes from the stage markup itself.

owner
@gaubee
region
iad1 · washington
status
passing
build
4f2a1c
runtime
node 24 · bun
notes
—
descriptions-item.svelte svelte
<!--
  jixoai DescriptionsItem (registry/files/ui/descriptions/descriptions-item.svelte,
  composition-first-apis, 2026-08-25).
  One term/value pair: the `term` prop renders the dt, children render
  the dd — rich cells are just children (badges, links, markup). A
  childless Item renders the em dash, never a blank cell. The bordered
  hairline paint (cell bottom edge, muted term background + rule)
  derives from the root's `bordered` through context — one decision,
  painted everywhere.
  tailwindless one-shot Wave 1b batch A (2026-09-17): the paint rides
  the family's stylex ATOMS (descriptions.stylex.ts) joined through
  cx() below.
-->
<script lang="ts">
  import type { Snippet } from 'svelte';
  import type { HTMLAttributes } from 'svelte/elements';
  import { getContext } from 'svelte';
  import { DESCRIPTIONS_KEY, type DescriptionsApi } from './descriptions.svelte';
  import { descriptionsStyles } from './descriptions.stylex';

  // the payload's own join (the separator serialize law): every
  // stylex.create member is an OBJECT in dev and the joined string in
  // shipped payloads — composition goes through THIS joiner (all
  // string values except $$css, space-joined).
  const cx = (
    ...styles: ({ readonly [key: string]: string | object } | undefined | string)[]
  ): string =>
    styles
      .filter(Boolean)
      .map((style) =>
        typeof style === 'string'
          ? style
          : Object.entries(style ?? {}).flatMap(([key, value]) =>
              key !== '$$css' && typeof value === 'string' ? [value] : [],
            ).join(' '),
      )
      .join(' ');

  interface Props extends HTMLAttributes<HTMLDivElement> {
    /** the dt label */
    term: string;
    class?: string;
    children?: Snippet;
  }

  let { term, class: className = '', children, ...rest }: Props = $props();

  const descriptions = getContext<DescriptionsApi>(DESCRIPTIONS_KEY);
</script>

<div
  data-jx-desc-cell=""
  class={cx(descriptionsStyles.cell, descriptions.bordered && descriptionsStyles.cellBordered, className)}
  {...rest}
>
  <dt
    data-jx-desc-term=""
    class={cx(descriptionsStyles.term, descriptions.bordered && descriptionsStyles.termBordered)}
  >{term}</dt>
  <dd data-jx-desc-value="" class={cx(descriptionsStyles.value)}>
    {#if children}{@render children()}{:else}—{/if}
  </dd>
</div>

with responsive columns

Drag the frame across the 640px line: the SAME three-column dl re-lays out into one pair per row — the container query reads the dl's own container, never the viewport, so any layout column inherits the law.

build
4f2a1c
checks
12 passed
region
iad1
owner
@gaubee
runtime
node 24
status
passing
descriptions-responsive-usage.svelte svelte
<!-- the container query is the component's own law
     (descriptions.css: @container (max-width: 640px) ⇒ one pair per row).
     The consumer only owns the container's width: -->
<div style="width: {frameWidth}px">
  <Descriptions columns={3} bordered>
    <DescriptionsItem term="build">4f2a1c</DescriptionsItem>
    <!-- … -->
  </Descriptions>
</div>

with extra header actions

antd's extra slot, composed honestly: a header row (title + actions) wraps the dl instead of living inside it — non-dl content never enters the list, the actions read back through the echo.

deploy · iad1 · production

build
4f2a1c
runtime
node 24
checks
12 passed · 0 failed
owner
@gaubee

last action: —

descriptions-extra-usage.svelte svelte
<!-- the extra slot composes AROUND the dl: a header row
     (title + actions) over the list. antd's title/extra props would put
     non-dl content INSIDE the dl — the wrapper keeps the semantics clean
     (recorded in the change's followups). -->
<section class="border border-border">
  <header class="flex items-center justify-between gap-3 px-4 py-3 border-b border-border">
    <p class="m-0 font-nav text-xs uppercase tracking-[0.12em]">deploy · iad1</p>
    <div class="flex gap-2">
      <PressButton variant="outline" onclick={redeploy}>redeploy</PressButton>
      <PressButton variant="ghost" onclick={rollback}>rollback</PressButton>
    </div>
  </header>
  <Descriptions columns={2} bordered>
    <DescriptionsItem term="build">4f2a1c</DescriptionsItem>
    <!-- … -->
  </Descriptions>
</section>

props

Props

Props extend the native HTML dl attributes — minus color, whose name the eight-axis surface owns (the §1 native collision rule); the entries below are descriptions-specific additions, and everything else rides the rest object through to the dl.

Properties

PropertyTypeDefaultDescription
columns number1Term/value pairs per row — clamped to 1–4 (truncated, never rounded up). The count stamps --jx-desc-cols on the root; the dl folds to ONE pair per row under 640px of CONTAINER width (the component's own container query — any layout column inherits the law). A HOW-prop: it changes how the grid paints, never what renders.
bordered booleanOwn default, not ambientThe hairline "bordered" antd look — CSS on the same dl, never a table in disguise: the root grows the card-ground frame and the Items paint their cell bottom edges plus the muted term background + inline-end rule. Rides CONTEXT down to the Items — one decision, painted everywhere.
children * Snippet—The DescriptionsItem pairs, in source order — the dl preserves description-list semantics, so nothing but dt/dd groups belongs inside.
style string''Inline style — joined AFTER the §11 axis carriers and the --jx-desc-cols stamp (the merge law: caller wins).

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 descriptions

The anatomy rides the density ruler's ambient channels: the term/value padding is --jx-gap/--jx-inset, the value's body voice is --jx-text/--jx-line, the term's micro-label is --jx-text-secondary/--jx-line-secondary — the named rung re-tiers the whole grid (measured 10/11/12/14 · 11/12/13/15 · 8/8/12/16 across xs/sm/default/lg; the number coefficient composes at :root — inert). size, shape, radius, color, elevation and motion stamp carriers nothing here reads (negative-grep receipt). Theme is the avatar frozen-pole twin: the chrome's five stylex aliases declare only at the :root,.xbpgcew pole + the theme class — the .dark bridge lands and nothing repaints (documented absence, W-next #1). Census: batch A (migration-census.md).

PropertyTypeDefaultDescription
size 'small' | 'medium' | 'large' | 'auto' | numberambient scopeSUPPLY-ONLY — the root stamps --jx-size-effective plus an inline font-size, and every voice on the grid is density-channel-anchored (--jx-text / --jx-text-secondary), so the stamp reaches only unstyled flow under the root (the dt/dd never inherit it). The §1 collision applies: the dl never receives a size attribute. Number unit: px.
shape 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto'ambient scopeSUPPLY-ONLY — stamps --jx-shape-effective + --jx-radius-factor-effective; zero readers (grep receipt: zero carrier reads in ui/descriptions/). A detail grid has no corners of its own; the bordered frame is square-edged by the card law. Number unit: none.
radius 'small' | 'medium' | 'large' | 'auto' | numberambient scopeSUPPLY-ONLY — stamps --jx-radius-effective; zero readers (the same grep receipt). The bordered frame stays square; nothing computes the §3 concentric corner. Number unit: px.
density 'small' | 'medium' | 'large' | 'xs' | '2xs' | 'sm' | 'default' | 'lg' | 'auto' | numberambient scopeCONSUMED through the ambient scope (the no-own axis slot — no provider and no explicit prop resolve auto, stamp nothing, and the ambient css scope channel keeps flowing): the grid re-tiers end to end. Padding rides --jx-gap/--jx-inset, the value's body voice --jx-text/--jx-line, the term's micro-label --jx-text-secondary/--jx-line-secondary — measured 10/11/12/14px terms · 11/12/13/15px values · 8/8/12/16px padding across xs/sm/default/lg. The NUMBER lane is inert (the coefficient composes at the :root rung scopes; the wrapper never re-declares — measured: a 3× coefficient leaves 13px/12px unmoved). Number unit: coefficient.
color 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | stringambient scopeSUPPLY-ONLY — stamps --jx-color-effective; zero family readers (grep receipt). A detail grid is content, not a painted object: the dt reads the muted alias, the dd the foreground alias, the bordered frame the border/card aliases — all theme voices, none of them the hue axis. Number unit: hue degrees.
theme 'light' | 'dark' | 'system' | 'auto'ambient scopeTHEME-FROZEN (the avatar twin, the documented absence W-next #1) — the root lands the .dark class bridge, and NOTHING repaints: every chrome voice is a stylex alias (--jx-border, --jx-card, --jx-muted, --jx-muted-foreground, --jx-foreground) whose declaration substitutes at the :root,.xbpgcew pole (built-CSS grep: each alias declares exactly twice — the pole + the theme class; never plain .dark). A documented absence: when the raw layer's dark values want in, the alias re-declaration is the named fix. light and system stamp nothing. No number lane.
elevation 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | numberambient scopeSUPPLY-ONLY — stamps --jx-elevation-effective; zero family readers (grep receipt). The bordered frame is a hairline on the card ground, no shadow layer; depth composes by the entity laws around the dl, never inside it. Number unit: dp.
motion 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | numberambient scopeSUPPLY-ONLY — stamps --jx-motion-effective; no family css reads it (grep receipt). A detail grid is a static read: nothing transitions, nothing moves — the reduced-motion law costs nothing here. Number unit: coefficient.

Deviations, cited: the adoption is the census batch A row (explicit-props W3-D1 — the data-display families join the eight-axis surface; openspec/changes/explicit-props/research/migration-census.md). The §1 collision rule: the interface Omits 'color', the dl never receives an axis name. `bordered` keeps its literal slot (own false — booleans are a closed domain); `columns` is the family's own HOW-prop.

descriptions · the ruler and the frozen island

density default — 13px values, 12px secondary terms, 12px padding
build
4f2a1c
owner
@gaubee
density="lg" — the touch tier steps the whole grid (15px, 16px padding)
build
4f2a1c
owner
@gaubee
theme="dark" — the bridge lands; the chrome stays frozen (nothing repaints)
build
4f2a1c
owner
@gaubee
color="error" — stamped, unread: content is not a painted object
build
4f2a1c
owner
@gaubee

The number lanes stamp and re-base nothing here — the declaring element for every channel this family reads is the ambient scope (:root), the wrapper never re-declares (the declaring-element law). Measure the voices, not the coefficient: 10 / 11 / 12 / 14px terms, 11 / 12 / 13 / 15px values, 8 / 8 / 12 / 16px padding.

descriptions-axes.svelte svelte
<script lang="ts">
import Descriptions from '@ui/descriptions/index';
import DescriptionsItem from '@ui/descriptions/index';
</script>

<div class={cx(rt.gridSm2, rt.wFull)}>
  <div class={cx(rt.panel)}>
    <span class={cx(rt.note11)}>density default — 13px values, 12px secondary terms, 12px padding</span>
    <Descriptions bordered>
      <DescriptionsItem term="build">4f2a1c</DescriptionsItem>
      <DescriptionsItem term="owner">@gaubee</DescriptionsItem>
    </Descriptions>
  </div>
  <div class={cx(rt.panel)}>
    <span class={cx(rt.note11)}>density="lg" — the touch tier steps the whole grid (15px, 16px padding)</span>
    <Descriptions bordered density="lg">
      <DescriptionsItem term="build">4f2a1c</DescriptionsItem>
      <DescriptionsItem term="owner">@gaubee</DescriptionsItem>
    </Descriptions>
  </div>
  <div class={cx(rt.panel)}>
    <span class={cx(rt.note11)}>theme="dark" — the bridge lands; the chrome stays frozen (nothing repaints)</span>
    <Descriptions bordered theme="dark">
      <DescriptionsItem term="build">4f2a1c</DescriptionsItem>
      <DescriptionsItem term="owner">@gaubee</DescriptionsItem>
    </Descriptions>
  </div>
  <div class={cx(rt.panel)}>
    <span class={cx(rt.note11)}>color="error" — stamped, unread: content is not a painted object</span>
    <Descriptions bordered color="error">
      <DescriptionsItem term="build">4f2a1c</DescriptionsItem>
      <DescriptionsItem term="owner">@gaubee</DescriptionsItem>
    </Descriptions>
  </div>
</div>
<p class={cx(rt.mt8, rt.note12, rt.inkMuted70)}>
  The number lanes stamp and re-base nothing here — the declaring element for every
  channel this family reads is the ambient scope (:root), the wrapper never
  re-declares (the declaring-element law). Measure the voices, not the coefficient:
  10 / 11 / 12 / 14px terms, 11 / 12 / 13 / 15px values, 8 / 8 / 12 / 16px padding.
</p>
one real query() case
<script lang="ts">
  import Descriptions, { DescriptionsItem } from '@ui/descriptions/index';
  import { query } from '@lib/universal-props-query.svelte';
  import type { DensityLane } from '@lib/defaults.svelte';
</script>

<Descriptions density={query<{ lg: DensityLane }, DensityLane>({ lg: 'large' }, 'small')} bordered>
  <DescriptionsItem term="build">4f2a1c</DescriptionsItem>
  <DescriptionsItem term="owner">@gaubee</DescriptionsItem>
</Descriptions>

descriptions · query()

build
4f2a1c
owner
@gaubee

Media keys are min-width: below 64rem the base applies — the small rung, the pointer-lane grid; at 64rem and wider the lg case wins and the grid steps to the touch tier (12px/8px padding below, 15px/16px above — measured). Resize across 64rem.

descriptions-query-demo.svelte svelte
<script lang="ts">
  import Descriptions, { DescriptionsItem } from '@ui/descriptions/index';
  import { query } from '@lib/universal-props-query.svelte';
  import type { DensityLane } from '@lib/defaults.svelte';
</script>

<Descriptions density={query<{ lg: DensityLane }, DensityLane>({ lg: 'large' }, 'small')} bordered>
  <DescriptionsItem term="build">4f2a1c</DescriptionsItem>
  <DescriptionsItem term="owner">@gaubee</DescriptionsItem>
</Descriptions>
TokenDefaultSource
--jx-desc-colscolumns prop (clamped 1–4)component
--jx-gap8 / 8 / 12 / 16px across xs / sm / default / lgdensity
--jx-inset8 / 8 / 12 / 16px across xs / sm / default / lgdensity
--jx-text11 / 12 / 13 / 15px across xs / sm / default / lgdensity
--jx-text-secondary10 / 11 / 12 / 14px across xs / sm / default / lgdensity
--jx-linethe dd's leading rungdensity
--jx-line-secondarythe dt's leading rungdensity

a11y

Accessibility

The dl preserves description-list semantics — wrappers add chrome, never list content; the term names the property, the value answers it.

ARIA

AttributeValueDescription
dlDescriptions rootPreserves description-list semantics — wrappers add chrome, never list content.
dttermNames each property.
ddvalueContains the corresponding value.
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h