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

registry:ui · Layout

card — the structural surface

THE STRUCTURAL KERNEL (2026-09-09): the head/body/foot bands, the stamped presence, the structural separators, the body-only scroll law — and the inline ruler everything rents (passive text enters the content axis by TRACK, interactive clusters ride the card edge flush). Structure is a STICKER: every rule anchors on the data-jx-card attribute family, so ANY element stamped with it — the planar Card root, a Dialog's interior host — acquires the whole ruler server-side by CSS alone; the named column lines and the jx-card container are public API. The content faces are the family parts: CardHeader (title/custom head), CardBody (the scroll cell — ONE gutter-compensation formula), CardFooter (the foot's seat law). Floating surfaces grow no flesh of their own — Dialog's interior speaks this dialect, its × riding the end-action seat reserved for it.

install — card

$npx jixoai-ui add card

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

head · body · foot inline ruler actions slot scroll law

usage

Usage

The card keeps the zones and the snippet transports; the content faces are composition — CardHeader for the head, CardFooter for the foot.

Card usage
<script lang="ts">
  import Card from '@ui/card.svelte';
  import CardHeader from '@ui/card-header.svelte';
  import CardFooter from '@ui/card-footer.svelte';
</script>

<!-- the bare card: default head face + body; no foot, no action seat -->
<Card title="Deploy hooks.">
  <p>Body content — the only scrollable zone.</p>
</Card>

<!-- the action seat: the inline-end slot dialog's × is designed for -->
<Card title="Session">
  {#snippet actions()}
    <IconButton text="Session options" iconOnly />
  {/snippet}
  <p>Body.</p>
</Card>

<!-- the foot: raw transport, the standard face is CardFooter (start
     seat / auto one ButtonGroup / raw end slot). PITFALL (issue #7):
     bare buttons in a RAW foot do NOT row themselves — they ride the
     ruler's content span, one per row, end-justified (the fail-soft
     default; before it they squeezed into the 14px inset track,
     clipped to slivers). Wrap actions in CardFooter for the carved
     cluster, or place your own grid lines if you truly own the ruler -->
<Card title="Cart">
  {#snippet foot()}
    <CardFooter label="cart actions">
      {#snippet start()}
        <span>3 items</span>
      {/snippet}
      <PressButton>Clear</PressButton>
      <PressButton variant="fill">Checkout</PressButton>
    </CardFooter>
  {/snippet}
  <p>Line items.</p>
</Card>

card

The structural surface, live: toggle the foot zone, the inline-end action seat, and the body's scroll authority. Presence is stamped on the host (data-sep-head / data-sep-foot); absent zones never render — their rows collapse.

Session

The body zone is the only scroll ring is declared to fit (scroll={false}): the authority and the gutter retire together.

3 items
card-usage.svelte svelte
<Card title="Session"
  scroll={false}>
  {#snippet foot()}
    <CardFooter label="card actions">
      {#snippet start()}<span>3 items</span>{/snippet}
      <PressButton>Cancel</PressButton>
      <PressButton variant="fill">Save</PressButton>
    </CardFooter>
  {/snippet}
  <p>The body zone is the only scroll ring.</p>
</Card>

law

Foot flexibility — both edges are first-class

The seats place against the rented inline ruler: text seats (start, end) enter the content axis — their 14px inset is a ruler TRACK, not a utility — while the button cluster spans the end inset to ride the card edge flush (its buttons carry the rhythm internally, dialog's footer economy verbatim).

card · foot seats

start + grouped actions

Meta text on the content axis; the cluster rides the edge flush.

3 items · $42.00

raw end slot

The end slot replaces the grouped arrangement — a text seat at the content axis, 14px shy of the edge.

auto-saved 2m ago
card-foot-demo.svelte svelte
<script lang="ts">
  import Card from '@ui/card.svelte';
  import CardFooter from '@ui/card-footer.svelte';
  import PressButton from '@ui/press-button/press-button.svelte';
</script>

<Card title="start + grouped actions">
  {#snippet foot()}
    <CardFooter label="cart actions">
      {#snippet start()}<span>3 items · $42.00</span>{/snippet}
      <PressButton>Clear</PressButton>
      <PressButton variant="fill">Checkout</PressButton>
    </CardFooter>
  {/snippet}
  <p>Meta text on the content axis; the cluster rides the edge flush.</p>
</Card>

<Card title="raw end slot">
  {#snippet foot()}
    <CardFooter>
      {#snippet end()}<span>auto-saved 2m ago</span>{/snippet}
    </CardFooter>
  {/snippet}
  <p>The end slot replaces the grouped arrangement — a text seat at the content axis, 14px shy of the edge.</p>
</Card>
CardFooter slots
<script lang="ts">
  import Card from '@ui/card.svelte';
  import CardHeader from '@ui/card-header.svelte';
  import CardFooter from '@ui/card-footer.svelte';
</script>

<!-- the bare card: default head face + body; no foot, no action seat -->
<Card title="Deploy hooks.">
  <p>Body content — the only scrollable zone.</p>
</Card>

<!-- the action seat: the inline-end slot dialog's × is designed for -->
<Card title="Session">
  {#snippet actions()}
    <IconButton text="Session options" iconOnly />
  {/snippet}
  <p>Body.</p>
</Card>

<!-- the foot: raw transport, the standard face is CardFooter (start
     seat / auto one ButtonGroup / raw end slot). PITFALL (issue #7):
     bare buttons in a RAW foot do NOT row themselves — they ride the
     ruler's content span, one per row, end-justified (the fail-soft
     default; before it they squeezed into the 14px inset track,
     clipped to slivers). Wrap actions in CardFooter for the carved
     cluster, or place your own grid lines if you truly own the ruler -->
<Card title="Cart">
  {#snippet foot()}
    <CardFooter label="cart actions">
      {#snippet start()}
        <span>3 items</span>
      {/snippet}
      <PressButton>Clear</PressButton>
      <PressButton variant="fill">Checkout</PressButton>
    </CardFooter>
  {/snippet}
  <p>Line items.</p>
</Card>

composition

In a card grid — the foot mode

CardGrid's foot prop declares the third shared row: headers align, bodies fill to the tallest, feet align at the band bottoms. The integer cell placement resolves in every wrapped band.

card · grid foot mode

usage

One install, three files: the surface, the head face, the foot face.

npx jixoai-ui add card

anatomy

Head zone, edge-riding separator, the scroll ring, the foot seat.

the longer body

This body is deliberately taller — the other two cards' bodies stretch to match it through the shared 1fr row, and every foot lands on the same band bottom line.

rev 2
card-grid-demo.svelte svelte
<script lang="ts">
  import Card from '@ui/card.svelte';
  import CardFooter from '@ui/card-footer.svelte';
  import CardGrid from '@ui/card-grid.svelte';
  import PressButton from '@ui/press-button/press-button.svelte';
</script>

<CardGrid foot min="240px">
  <Card title="usage">
    {#snippet foot()}
      <CardFooter>{#snippet end()}<span>npx jixoai-ui add card</span>{/snippet}</CardFooter>
    {/snippet}
    <p>One install, three files: the surface, the head face, the foot face.</p>
  </Card>
  <Card title="anatomy">
    {#snippet foot()}
      <CardFooter><PressButton variant="fill">Save</PressButton></CardFooter>
    {/snippet}
    <p>Head zone, edge-riding separator, the scroll ring, the foot seat.</p>
  </Card>
  <Card title="the longer body">
    {#snippet foot()}
      <CardFooter>
        {#snippet start()}<span>rev 2</span>{/snippet}
        <PressButton>diff</PressButton>
      </CardFooter>
    {/snippet}
    <p>
      This body is deliberately taller — the other two cards' bodies stretch to match it
      through the shared 1fr row, and every foot lands on the same band bottom line.
    </p>
  </Card>
</CardGrid>
CardGrid foot mode
<CardGrid foot min="220px">
  <Card title="one">
    {#snippet foot()}<CardFooter>{#snippet end()}<span>meta</span>{/snippet}</CardFooter>{/snippet}
    <p>Body fills to the tallest; the foot aligns at the band bottom.</p>
  </Card>
  <Card title="two">
    {#snippet foot()}<CardFooter><PressButton variant="fill">Save</PressButton></CardFooter>{/snippet}
    <p>Second card.</p>
  </Card>
</CardGrid>

types

Types

Three postures: the bare card, the chrome-less body card, and the full zone trio.

card · variants

bare · title + body

Deploy hooks.

No foot, no action seat — the stamps stay honest.

chrome-less · no head

Neither title nor a head snippet: no head zone, no head separator.

full · head + body + foot

Session

The zone trio a card-grid foot band equalizes.

3 items
card-types-demo.svelte svelte
<script lang="ts">
  import Card from '@ui/card.svelte';
  import CardFooter from '@ui/card-footer.svelte';
  import PressButton from '@ui/press-button/press-button.svelte';
</script>

<Card title="Deploy hooks.">
  <p>No foot, no action seat — the stamps stay honest.</p>
</Card>

<Card>
  <p>Neither title nor a head snippet: no head zone, no head separator.</p>
</Card>

<Card title="Session">
  {#snippet foot()}
    <CardFooter>
      {#snippet start()}<span>3 items</span>{/snippet}
      <PressButton variant="fill">Save</PressButton>
    </CardFooter>
  {/snippet}
  <p>The zone trio a card-grid foot band equalizes.</p>
</Card>

a11y

Accessibility

The card is a generic section container; the separators are decorative chrome, the zones carry no semantics of their own.

Keyboard

KeyAction
Escape / closeNot applicable — no close affordance ships; the action seat is the consumer’s own control

ARIA

AttributeValueDescription
root<section>A generic container; give it an accessible name via its content when the region is navigable
separatorsaria-hiddenThe structural lines are decorative — hidden from AT
action seatconsumer-ownedWhatever sits in the actions slot keeps its own role, name, and activation behavior

theming

Theming

The inline ruler, verbatim: the root owns five named tracks ([inset] 14px · [start seat] auto · [fill] minmax(10px,1fr) · [end seat] auto · [inset] 14px); head/foot zones rent them via subgrid. Zones paint nothing; no face carries an inline padding utility — tracks paint the axis. The card is not density-scaled.

TokenDefaultSource
text seats (head title, foot start/end)content axis (track)structural
edge riders (actions slot, foot cluster)flush, span to -1structural
body cellpy-3.5 + inline compensationcomponent
--card-foregroundthemecolor

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 card is the §3 concentric ANCHOR: an explicit radius supplies --jx-radius-effective while the ruler's inline inset track supplies --jx-inset-effective — descendants at radius='auto' compute max(0px, R − P), and shape='squircle' doubles the resolved corner through the §14 factor ladder (reversing on degrade).

Card · universal props

radius 20 · the concentric anchor

children at radius="auto" compute max(0px, 20 − 14) = 6px

shape squircle · radius 20

the §14 factor doubles the resolved corner (and reverses on degrade)

named step

var(--jx-radius-large) — the §12 indirection, zero inline values

size 14 · density small

one number moves the family; the rung scopes the channels

card-universal.svelte svelte
<!-- the concentric anchor: radius 20 + the 14px ruler inset →
     the auto-radius button computes max(0px, 20 − 14) = 6px -->
<Card title="anchor" radius={20}>
  <PressButton radius="auto">concentric child</PressButton>
</Card>
<Card title="squircle" shape="squircle" radius={20}>×2 through the factor ladder</Card>
<Card title="named step" radius="large">var(--jx-radius-large) — zero inline values</Card>

api

API

Card keeps the bands and the snippet transports; CardHeader / CardBody / CardFooter are the content faces — usable standalone too (their own ruler mirrors carry them outside any band).

Properties

PropertyTypeDefaultDescription
title string—Heading of the default head face (CardHeader). Omit with no head snippet for a chrome-less card.
head Snippet—Raw head override — enters at the content axis (typically wraps CardHeader).
actions Snippet—The inline-end action seat — dialog × position, edge-riding flush. Absent, the slot never renders; no close button ships.
foot Snippet—Raw full override of the foot zone; the standard face is CardFooter.
scroll booleantrueBody scroll authority; false retires it together with the gutter reservation.
class string''Root utilities appended after the law’s own.
children * Snippet—The body — the only scrollable zone.

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)

Properties

PropertyTypeDefaultDescription
title string—CardHeader: the default title row (h2, py-2.5 — the inline inset is the ruler’s track); yields to children.
children Snippet—CardHeader: custom head content at the content axis — owns its own block geometry.
class string''CardHeader: appended to the face — the FLUSH escape hatch: class='col-start-1' pins the column start to the grid's first line (flush past the inset track) while the × seat keeps its row (the search-field head idiom).

Properties

PropertyTypeDefaultDescription
start Snippet—CardFooter: the inline-start TEXT seat — the content axis.
end Snippet—CardFooter: the inline-end TEXT seat; replaces the grouped arrangement.
label string'Actions'CardFooter: the ButtonGroup accessible name (neutral since the kernel — the footer family serves every carrier).
children Snippet—CardFooter: action buttons — the CLUSTER seat: one leadingSeam ButtonGroup riding the card edge flush.

Properties

PropertyTypeDefaultDescription
scroll booleantrueCardBody: the band scroll authority (the host never scrolls) — the cell is the ring with a stable both-edges gutter; false retires the authority and the gutter together.
children * Snippet—CardBody: the content — enters at the cell content axis, the single-sourced gutter-compensation formula padding it.
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h