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

design ruling · recipes

where wrapping stops

Four shadcn items are deliberately NOT components here — each ruling from the batch-4 design review: the platform already owns it (aspect-ratio), composition IS the product (data-table, sidebar), or the honest surface is a semantic table (chart until an SVG-primitive lib earns its place). The recipes below are the contract, and they are runnable — the watermark one runs live in the workbench.

aspect-ratio = CSS data-table = composition chart = semantic first sidebar = sheet + nav

recipe: watermark

The recipe runs live: the stage is a watermarked surface — try to select or click through it (the layer is pointer-events:none), then type in the Playground and watch the tile re-render. The recipe card below and this drawer share one source.

the protected surface — select this text, click the button: input passes through the overlay untouched.

brand primaries under the layer
watermark-usage.svelte svelte
<!-- encodeURIComponent escapes the WHOLE SVG (spaces, quotes, CJK) —
     dynamic text needs XML entity escaping on top (& < >) -->
const wmSvg =
  '<svg xmlns="http://www.w3.org/2000/svg" width="120" height="86">' +
  '<text x="50%" y="50%" fill="currentColor" font-size="13" font-family="monospace" ' +
  'text-anchor="middle" transform="rotate(-22 60 43)">"jixoai"</text></svg>';
const watermarkRecipe =
  '<div class="watermarked"\n' +
  '  style="--wm-tile: url(\'data:image/svg+xml,\' + encodeURIComponent(wmSvg))">\n' +
  '  …content…\n' +
  '  <div class="wm-layer" aria-hidden="true"></div>\n' +
  '</div>\n' +
  '<style>\n' +
  '  .watermarked { position: relative; }\n' +
  '  .wm-layer {\n' +
  '    position: absolute; inset: 0;\n' +
  '    pointer-events: none;\n' +
  '    color: var(--foreground); opacity: 0.12;\n' +
  '    background-repeat: repeat; background-size: 120px;\n' +
  '    background-image: var(--wm-tile);\n' +
  '  }\n' +
  '</style>';

recipe

aspect-ratio — the platform owns it

aspect-ratio
<!-- the platform owns it: aspect-ratio is a CSS property -->
<img src="/shot.png" alt="build output" style="aspect-ratio: 16 / 9; width: 100%; object-fit: cover" />

<!-- or the frame pattern for lazy media -->
<div style="aspect-ratio: 16 / 9; width: 100%">
  <iframe … title="demo" style="width: 100%; height: 100%"></iframe>
</div>

recipe

data-table — semantics from table.svelte, state from you

A wrapper that decides your column model, pagination and selection state would be a framework in disguise. The recipe: your state layer (TanStack or plain Svelte) computes rows; table.svelte renders real thead/tbody with aria-sort headers.

data-table
<!-- data-table is a COMPOSITION, not a component: table.svelte gives
     the semantics; your state layer gives sorting/selection -->
<script lang="ts">
  // e.g. with TanStack Table (or any state layer): compute the rows,
  // render <Table> with real th[aria-sort] buttons
</script
<Table>
  {#snippet head()}
    <th aria-sort={sort.dir === 'asc' ? 'ascending' : 'descending'}>
      <button type="button" onclick={() => sort.toggle('name')}>name ↕</button>
    </th>
  {/snippet}
  {#snippet body()}
    {#each rows as row (row.id)} <tr><td>{row.name}</td></tr> {/each}
  {/snippet}
</Table>

recipe

chart — semantic first, SVG primitives later

Bars are a table row's width away — zero JS, screen-reader-first. Line/axis/legend charting deserves a real SVG-primitive registry:lib designed for it; wrapping a chart library would fight the terminal token system.

chart
<!-- v1 recipe: semantic table + CSS bars — zero JS, screen-reader-first -->
<table>
  <caption>bundle size by page (KB)</caption>
  <tbody>
    {#each pages as p (p.name)}
      <tr><th scope="row">{p.name}</th>
          <td><div style="width: {p.kb / max * 100}%" class="bar"></div> {p.kb}</td></tr>
    {/each}
  </tbody>
</table>
<style>.bar { height: 10px; background: var(--primary); }</style>

<!-- richer charting (lines/axes/legends) is a future SVG-primitive
     registry:lib, not a wrapper around a chart library -->

recipe

sidebar — sheet + your nav

The 40-file sidebar framework is an app shell decision, not a component. The recipe: a left sheet, your links, aria-current from your router's truth.

sidebar
<!-- sidebar = sheet + your nav, not a 40-file framework -->
<Sheet bind:open title="Contents" side="left" width="18rem">
  {#each sections as s (s.id)}
    <a class="side-link" href="#{s.id}" aria-current={s.id === active ? 'page' : undefined}
      onclick={() => (open = false)}>{s.label}</a>
  {/each}
</Sheet>

antd mapping · recipe

watermark — the pointer-events:none overlay

The anti-exfiltration overlay as a CSS recipe: a repeated rotated-text SVG data-URI tile (encodeURIComponent — zero libraries, no canvas) on an absolutely-positioned layer with pointer-events:none. The watermark deters and marks provenance — it is not DRM (a DOM layer is removable by a determined user). This sample is LIVE: it tracks the workbench input above.

watermark
<!-- encodeURIComponent escapes the WHOLE SVG (spaces, quotes, CJK) —
     dynamic text needs XML entity escaping on top (& < >) -->
const wmSvg =
  '<svg xmlns="http://www.w3.org/2000/svg" width="120" height="86">' +
  '<text x="50%" y="50%" fill="currentColor" font-size="13" font-family="monospace" ' +
  'text-anchor="middle" transform="rotate(-22 60 43)">"jixoai"</text></svg>';
const watermarkRecipe =
  '<div class="watermarked"\n' +
  '  style="--wm-tile: url(\'data:image/svg+xml,\' + encodeURIComponent(wmSvg))">\n' +
  '  …content…\n' +
  '  <div class="wm-layer" aria-hidden="true"></div>\n' +
  '</div>\n' +
  '<style>\n' +
  '  .watermarked { position: relative; }\n' +
  '  .wm-layer {\n' +
  '    position: absolute; inset: 0;\n' +
  '    pointer-events: none;\n' +
  '    color: var(--foreground); opacity: 0.12;\n' +
  '    background-repeat: repeat; background-size: 120px;\n' +
  '    background-image: var(--wm-tile);\n' +
  '  }\n' +
  '</style>';

antd mapping · recipe

image preview — dialog, not a lightbox framework

The ruling: zoom/preview is a dialog composition, not a component feature. The recipe: a thumbnail button opens a dialog holding the same src at full width; no galleries, thumbnail navigation, or gesture zoom in v1.

image-preview
<button type="button" onclick={() => (open = true)} aria-label="enlarge image">
  <Image src={src} {alt} width={320} height={180} />
</button>
<Dialog bind:open title={alt}>
  <img {src} {alt} style="width: 100%; height: auto" />
</Dialog>

antd mapping · recipe

flex / grid — Tailwind is the API

antd's Row/Col/Flex map to native CSS through Tailwind — no component, no props taxonomy. gutter→gap, span/offset→grid fractions, justify/align→the utilities of the same names; responsive via sm:/lg: instead of xs/md props.

flex / grid
<!-- antd: <Row gutter={16}><Col span={8}>…</Col></Row> -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">…</div>

<!-- antd: <Flex justify="space-between" align="center">…</Flex> -->
<div class="flex items-center justify-between">…</div>

antd mapping · cross-link

segmented → toggle-group type=single

antd's Segmented maps to toggle-group's single mode — the full semantic mapping lives on the toggle-group page (semantic mapping, not 1:1 paint: no sliding indicator imitation). This entry keeps every antd concept mapping reachable from one place.

antd mapping · recipe

list — ul/ol + the state atoms

antd's List is a layout opinion plus state management. The recipe composes the states we already own: the list element is yours (ul/ol by semantics), empty is Empty, loading is Spin, rows are rows — pagination/selection/virtualization are separate concerns that never belong inside a list skin.

list
{#if rows.length === 0}
  <Empty title="no items" />
{:else}
  <ol role="list">
    {#each rows as row (row.id)}
      <li class="flex items-center justify-between gap-4">
        <div>{row.title}</div>
        <PressButton variant="outline">open</PressButton>
      </li>
    {/each}
  </ol>
{/if}
<!-- loading wraps either branch: <Spin label="loading">…</Spin> -->

antd mapping · recipe

autoComplete → combobox

antd's AutoComplete maps to the combobox with four honest notes: free input corresponds to the allowCustom posture; strict options correspond to the committed mode; combobox's search filtering is NOT mentions/token editing (see the mentions boundary below); and the native autocomplete attribute is a browser concept — never conflate it with antd's component of the same name.

autoComplete
<!-- antd: <AutoComplete options={opts} /> -->
<Combobox {options} placeholder="type to filter…" />

antd mapping · recipe

typography — HTML elements + tokens, not a component family

Title maps to real h1–h6; Text/Paragraph map to real span/p with the token classes; Ellipsis is CSS (text-overflow / line-clamp) with the rule that visual truncation NEVER removes the accessible full text; Highlight is the native <mark>. No Typography component family — semantics belong to the elements.

typography
<!-- Title level={3} → a real heading -->
<h3 class="font-nav text-[1.22rem] tracking-tight">…</h3>

<!-- Text ellipsis — full text stays accessible -->
<p class="truncate" title="…the full text…">…</p>
<p class="line-clamp-2">…full text, visually clamped…</p>

<!-- Highlight -->
Search hits render as <mark>match</mark> — native emphasis semantics.

antd boundary

mentions — not covered, by ruling

Mentions (caret tracking, trigger characters, token insertion/deletion, IME coordination) is a different state machine from a whole-field combobox. The combobox is an adjacent foundation, NOT a mentions replacement — recorded as an honest boundary, no fake 1:1 recipe.

boundary: no component, no mapping — until a real use case funds the deep design.

design contract · deferred

tour — deferred with its design contract on record

Deferred to its own deep-design batch (the ruling). The contract when it lands: anchor-name injection as a reversible per-instance LEASE on the target (set on open/step, restored on close/unmount — the same class of wiring as popovertarget, not style-writing); the highlight is CSS-anchor + a target-sized transparent hole + one huge box-shadow (no geometry JS, no four-block mask); popover=manual + role=dialog + aria-modal=false — non-modal, page scrollable, default scrim pointer-events:none; per-step target re-resolution with deterministic skip when unavailable; Escape/Skip ends and restores the invoker focus.

this card IS the contract — tour lands only against it, in its own batch.

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

Theme

Brand hue

0°

Auto cycle · 24h