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

registry:ui · Layout

hero-section — the Broadside hero, opened

The landing hero with opened content: title/badges/terminal/secondary/copy are snippets (the caller composes Badge children; the default copy CTA rides the copyCommand payload; the em accent paint lives in family css).

install — hero-section

$npx jixoai-ui add hero-section

https://ui.jixoai.com/r/hero-section.json

title / badges snippets copy CTA default + override terminal snippet slot staggered reveal

overview

Overview

Composition-first: structure and chrome are the component's; content is authored. The hero answers its own box, measures in centimetres, and supplies its lanes to the children it hosts.

HeroSection renders one section — eyebrow, the h1 title, the 16cm lead, the badge row, the copy-command CTA and an optional second-column aside — with the content authored through snippets: title (any em inside carries the accent paint via the :where([data-jx-hero-title]) em rule), badges (compose Badge parts), secondary, and terminal. The copy CTA is a DEFAULT: hand it copyCommand and it renders a press-button that copies its own label; provide a copy snippet and the default (and the prop) become unused — snippet-conditional, pinned by the interface.

Two laws make it place-anywhere. The section is a NAMED CONTAINER (container: jx-hero), so the row form engages at ≥64rem of the hero's own CONTENT — an embedded hero answers its own box, not the viewport. And the measures are CENTIMETRES (lead 16cm, title 22cm, aside 10.5–13cm) — the ergonomic print-measure idiom, every cap riding min(100%, …). The entrance is a time-based CSS cascade (six steps, 0–260ms, fill-mode: backwards — plays at first paint, no JS) with a prefers-reduced-motion kill.

The eight axes resolve on the section as a FIRST-TIME all-no-own contract (census batch D): the hero's cm typography and cascade are its own laws, so six axes stamp-and-supply — but the supply is not decorative. Density's re-based channels and the other carriers are inherited by the COMPOSED children (Badge parts, the CTA, the terminal-card) — the supply chain is the point. Theme is the partial pole with a twist the emission form decides: the raw-var accent voices flip under a dark island, the defineVars-alias muted lead freezes in place. Per-axis below; the shared grammar lives on the universal props page. A composed family: pattern-hero-set mounts it (grep receipt), and the h1 it mints carries the data-jx-hero-title hook — the component-owned-title law.

usage

Usage

Snippets own content; strings stay payload. The em inside the title snippet carries the accent paint; copyCommand is snippet-conditional.

HeroSection usage
<script lang="ts">
  import HeroSection from '@ui/hero-section.svelte';
  import Badge from '@ui/badge.svelte';
  import PressButton from '@ui/press-button.svelte';
  import TerminalCard from '@ui/terminal-card.svelte';
</script>

<HeroSection
  eyebrow="my-app · v1"
  summary="One paragraph of lead context at the 16cm ergonomic measure."
  copyCommand="npx jixoai-ui init --hue 200"
>
  {#snippet title()}Ship terminals anywhere. <em>One hue.</em>{/snippet}
  {#snippet badges()}
    <Badge>OKLCH tokens</Badge>
    <Badge>Svelte 5</Badge>
  {/snippet}
  {#snippet secondary()}
    <PressButton variant="outline" href="/docs.html">Get started</PressButton>
  {/snippet}
  {#snippet terminal()}
    <TerminalCard barTitle="quick-start — zsh"
      command="npx jixoai-ui init --hue 200"
      outputs={['theme installed', 'hue applied']} />
  {/snippet}
</HeroSection>

hero-section

A complete composed hero in the stacked (below-tier) form — the docs column cannot offer the 64rem of content the wide tier needs, so this stage shows the terminal card stacked under the lead, rendered in full (the canvas's scroll cap is lifted for this composition demo). The wide form lives below. The title snippet carries its own accent em, the badges snippet composes Badge parts, the default copy CTA flips to its copied surface on press.

your-app · v0

Ship the registry into your repo. Keep the source.

A compact instance with the real composition rules — the copy stays yours, the law stays ours. The CTA copies the init command; the terminal demo composes from the same registry files.

registry copy CTA terminal demo
browse components
quick-start — zsh

$npx jixoai-ui init --hue 210

theme installed

hue applied · 210

hero-section-usage.svelte svelte
<script lang="ts">
  import HeroSection from '@ui/hero-section.svelte';
  import Badge from '@ui/badge.svelte';
  import PressButton from '@ui/press-button.svelte';
  import TerminalCard from '@ui/terminal-card.svelte';
</script>

<HeroSection
  eyebrow="my-app · v1"
  summary="One paragraph of lead context at the 16cm ergonomic measure."
  copyCommand="npx jixoai-ui init --hue 200"
>
  {#snippet title()}Ship terminals anywhere. <em>One hue.</em>{/snippet}
  {#snippet badges()}
    <Badge>OKLCH tokens</Badge>
    <Badge>Svelte 5</Badge>
  {/snippet}
  {#snippet secondary()}
    <PressButton variant="outline" href="/docs.html">Get started</PressButton>
  {/snippet}
  {#snippet terminal()}
    <TerminalCard barTitle="quick-start — zsh"
      command="npx jixoai-ui init --hue 200"
      outputs={['theme installed', 'hue applied']} />
  {/snippet}
</HeroSection>

container tier

The wide form — 64rem of the hero's own content

The row form answers the hero's own box, not the viewport: at ≥64rem of CONTENT the grid takes its second column and the terminal aside bottom-aligns against the lead. This docs column cannot offer that width, so the sample below pans sideways in its own scroll lane — an honest presentation of a container tier the page's column can never reach.

hero-section · wide form

your-app · v0

Ship the registry into your repo. Keep the source.

The same composition at full width — the lead keeps its 16cm ergonomic measure while the aside column tracks 10.5–13cm beside it.

registry copy CTA terminal demo
browse components
quick-start — zsh

$npx jixoai-ui init --hue 210

theme installed

hue applied · 210

hero-section-wide-demo.svelte svelte
<script lang="ts">
  import HeroSection from '@ui/hero-section.svelte';
  import Badge from '@ui/badge.svelte';
  import PressButton from '@ui/press-button.svelte';
  import TerminalCard from '@ui/terminal-card.svelte';
</script>

<!-- the row form engages at ≥64rem of the hero's own CONTENT box —
     pan a narrow host sideways to see it (container query, not media) -->
<div class="overflow-x-auto">
  <div class="min-w-[67rem]">
    <HeroSection
      eyebrow="your-app · v0"
      summary="The same composition at full width — the lead keeps its 16cm ergonomic measure."
      copyCommand="npx jixoai-ui init --hue 210"
    >
      {#snippet title()}Ship the registry into your repo. <em>Keep the source.</em>{/snippet}
      {#snippet badges()}
        <Badge>registry</Badge>
        <Badge>copy CTA</Badge>
        <Badge variant="outline">terminal demo</Badge>
      {/snippet}
      {#snippet secondary()}
        <PressButton variant="outline" href="/docs/components.html">browse components</PressButton>
      {/snippet}
      {#snippet terminal()}
        <TerminalCard
          barTitle="quick-start — zsh"
          command="npx jixoai-ui init --hue 210"
          outputs={['theme installed', 'hue applied · 210']}
        />
      {/snippet}
    </HeroSection>
  </div>
</div>

pan the lane sideways — its thin scrollbar is the affordance (the site scrollbar law). The tier boundary is a CONTAINER query on the hero section itself, so embedding the hero in any ≥64rem box takes the row form regardless of the viewport.

composition

What the snippets own

Structure and chrome are the component's; content is authored. Strings survive only where they are payload or plain text — everything that changes WHAT renders is a snippet.

  • title snippet — the whole h1 content; any <em> inside carries the accent paint (component css :where() rule — the split-em styling the old titleLead/titleAccent props carried)
  • badges snippet — compose Badge parts (badges: string[] is dead); the row keeps the mono uppercase strip layout
  • copyCommand — the payload: the default CTA's label AND its clipboard target; pressing it flips the press-button to the copied surface for 1.4s
  • copy snippet — replaces the default copy CTA (the command string is still yours to use however you render it)
  • terminal snippet — usually a terminal-card, and OPTIONAL: the hero only owns the column and the bottom alignment (@min-[64rem]/jx-hero container-tier grid; without the snippet the wide form keeps the right side empty — whitespace is the focus)
  • secondary snippet — outline CTAs after the copy button; omit it and the row holds the CTA alone

api

Props

The table renders from the GENERATED meta + curation; the eight axis rows split into the shared section beneath. 20 meta props − 8 ambient axes = 12 rows; no EXTRA lane — no family prop shares an axis name.

Properties

PropertyTypeDefaultDescription
eyebrow * string—Tracked uppercase label above the title — the accent voice: ink rides --primary-text (flips under .dark).
summary * string—The lead paragraph, capped at the 16cm ergonomic measure (min(100%, …) so narrow hosts never overflow); ink --jx-muted-foreground.
copyCommand string''The clipboard payload AND the default CTA's label. SNIPPET-CONDITIONAL: required exactly when the default copy CTA renders; provide a #copy snippet instead and this is unused (and may be omitted).
copyLabel string'copy'aria affordance of the default copy CTA (default 'copy' — localize freely).
title Snippet—The h1 content, rendered inside the data-jx-hero-title hook; any <em> inside carries the accent paint (the :where() components-layer rule — consumer utilities still win). Plain text is also legal.
badges Snippet—The badge-row content — compose Badge parts (badges: string[] is dead). The row wraps at the kernel gaps; the uppercase label voice is the component's.
copy Snippet—Replaces the default copy CTA wholesale — the command string stays yours to render however.
terminal Snippet—The OPTIONAL second-column aside (usually a terminal-card). The hero owns only the column and its bottom alignment at the ≥64rem container tier; without the snippet the wide form keeps the right side empty — whitespace is the focus.
secondary Snippet—Extra outline CTAs after the copy button; omit and the row holds the CTA alone.
class string''Forwarded to the section root, after the family atoms.
style string''Inline style passthrough — composed AFTER the family's carrier stamp (the #4 seam law: never clobbered, never dropped).
rest unknown (spread passthrough)—Every other attribute passes through to the section untouched (Omit<HTMLAttributes<HTMLElement>, 'title' | 'color'> — both native names cleared for the snippet and the axis).

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 hero-section

A FIRST-TIME all-no-own contract (census batch D, W3-D1): the hero's cm typography, kernel spacing and entrance cascade are its own laws, so all eight carriers stamp unread — but the supply is the point, because the hero HOSTS compositions. The guests (Badge parts, the CTA, the terminal-card) inherit the re-based channels. Theme is the one axis that moves the hero's own paint — partially, and the split follows the emission form: raw-var accent voices flip, the defineVars-alias muted lead freezes. The carriers stamp the section root (the promoted root is self-carried), greppable in the raw SSR.

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

PropertyTypeDefaultDescription
size 'small' | 'medium' | 'large' | 'auto' | number'auto'SUPPLY-ONLY, MEASURED INERT ON THE PAINT — the §11 stamp sets the section's font-size inline (size={14} moves the section's computed voice to exactly 14px), and NOTHING follows: every hero voice is fixed-scale typography — the title clamp(2.4rem, 6cqi, 4.4rem) is rem-floor/container-fluid/rem-cap, the eyebrow and lead ride the --text-* px steps — none is em-of-parent (the measured contrast to heading's em ladder). Number unit: px.
shape 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto''auto'SUPPLY-ONLY — stamps --jx-shape-effective and --jx-radius-factor-effective; no family css reads them (grep receipt: zero readers, zero corner declarations in ui/hero-section/). The hero draws no boxes. Number unit: none.
radius 'small' | 'medium' | 'large' | 'auto' | number'auto'SUPPLY-ONLY — stamps --jx-radius-effective; no family css reads it (grep receipt: zero readers, zero border-radius in the family). Number unit: px.
density 'small' | 'medium' | 'large' | 'auto' | number (+ the five legacy spellings)'auto'SUPPLY-ONLY ON SELF, CONSUMED BY COMPOSITION — the named rung stamps data-density on the section, and the hero's OWN atoms read none of the re-based channels (grep receipt: --jx-text/--jx-line/--jx-gap/--jx-stack/--jx-media-gutter — zero hits; the hero rides the fixed --text-* micro voices and the --space-*/--jx-unit kernel). But the COMPOSED children — Badge parts, the PressButton CTA, the terminal-card — inherit the re-based channels, and the rung scope IS the section (the stamp sits on the root the guests live in — no wrapper paradox). The component's own header names it: the supply chain is the point. Number unit: coefficient.
color 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string'auto'SUPPLY-ONLY — stamps --jx-color-effective; no family css reads it (grep receipt: zero readers). Hue that paints composes from --brand-hue: the eyebrow's --primary-text and the title-em's --primary (both flip per THEME, not per hue lane), and the copied CTA re-tones through the jx-hue-success injection class (pinned by test/hue-injection.spec.ts). Number unit: hue degrees.
theme 'light' | 'dark' | 'system' | 'auto''auto'THE PARTIAL POLE, SPLIT BY THE DECLARING-SCOPE LAW — measured, and the split runs along the CSS emission form. The two voices written as RAW var() strings flip: the eyebrow's color: var(--primary-text) and the title-em's var(--primary) (hero-section.css) — substitution runs at the consuming element, so the stamped .dark re-substitutes them. The muted voices FREEZE: summary and badges ride tokens['--jx-muted-foreground'], a stylex defineVars member whose alias is declared (and therefore resolved) at the stylex :root theme scope — the section inherits the already-substituted light value, and a scoped .dark cannot re-base it (probe receipt: --muted-foreground flips on the section, --jx-muted-foreground does not). So a dark hero island flips the ACCENT and keeps the page's muted lead ink — a dark hero wants a dark host surface. Geometry, micro-typography and cascade are theme-free regardless (grep receipt: those are the only raw theme tokens in ui/hero-section/). light and system stamp nothing — tree inheritance.
elevation 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number'auto'SUPPLY-ONLY — stamps --jx-elevation-effective; no family css reads it (grep receipt: zero readers, zero box-shadow in the family). The brand statement is flat. Number unit: dp.
motion 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number'auto'SUPPLY-ONLY ON THE AXIS — the carrier --jx-motion-effective is unread (grep receipt: zero --jx-motion-* reads). The entrance cascade is the family's OWN law, read verbatim from the motion KERNEL: --motion-hero 480ms + --ease-hero cubic-bezier(0.22,1,0.36,1), per-step --jx-hero-delay seams (0/60/120/160/200/260ms) and --jx-hero-rise offsets, fill-mode backwards (plays at first paint, no JS), and the prefers-reduced-motion kill (hero-section.css). Animation, not transition. Number unit: coefficient.

Deviations, cited: the adoption is the census batch D row (the long tail — openspec/changes/explicit-props/research/migration-census.md); the carriers set is pinned by test/docs-ambient-vocabulary.spec.ts (hero-section in expectedCarriers). The §1 collision rule: Omit<HTMLAttributes<HTMLElement>, 'title' | 'color'> clears both native names at stake — the title snippet and the hue lane — and every axis name is the family's own destructured lane.

the eight axes on hero-section
<!-- theme: the PARTIAL pole — a dark island flips the ACCENT
     voices (eyebrow --primary-text, title em --primary, both raw var()
     strings); the muted lead/badges freeze on the defineVars alias
     (resolved at the stylex :root scope — the declaring-element law) -->
<HeroSection eyebrow="night-shift · v2" summary="Dark accent, same bones." theme="dark" copyCommand="npx jixoai-ui init">
  {#snippet title()}Same hero. <em>New ink.</em>{/snippet}
</HeroSection>

<!-- density: SUPPLY-ONLY on the hero's own atoms (the cm voices and
     the kernel spacing hold) — the composed children inherit the
     re-based channels. The supply chain is the point. -->
<HeroSection eyebrow="your-app · v0" summary="The sm rung: my atoms hold, my guests breathe tighter." density="sm" copyCommand="npx jixoai-ui init">
  {#snippet title()}Supply-side density.{/snippet}
  {#snippet badges()}
    <Badge>sm rung</Badge>
    <Badge variant="outline">composed child</Badge>
  {/snippet}
</HeroSection>

hero-section · the partial pole and the supply chain

auto — the ambient profile

day-shift · v1

Light bones. Accent ink.

The lead and badges ride the muted voice; the eyebrow and the title em ride the accent.

muted voice
theme="dark" — the accent voices flip; the muted alias freezes (measured)

night-shift · v2

Dark island. Same bones.

The eyebrow and title em flip to the dark accent; my lead ink stays the page's muted voice.

muted voice
size auto — the fixed voices

your-app · v0

Fixed-scale type.

The title's clamp is rem-floor, cqi-fluid, rem-cap — no em of the section's font-size.

size={14} — the box answered; the type didn't (measured)

your-app · v0

The stamp lands; nothing follows.

The section's computed font-size moves to exactly 14px; the title clamp and the px micro voices hold.

density="sm" — the hero's atoms hold; the composed guests re-base (the supply chain is the point)

your-app · v0

Supply-side density.

My own cm voices and kernel spacing hold; the Badge parts and the copy CTA inherit the sm rung's channels.

sm rung composed child

The dark panel flips exactly the accent voices — eyebrow and title-em, the two rules written as raw var() strings — while the summary's muted ink is byte-frozen: it rides the defineVars alias, resolved at the stylex :root scope, so the scoped .dark cannot re-substitute it (probe receipt: --muted-foreground flips on the section, --jx-muted-foreground does not). The title clamp, the paddings and the cascade stay identical throughout. The size pair is the inertness probe: the stamped section's computed font-size is exactly 14px, and the title's is unmoved — the rem/cqi voices give the stamp nothing to scale. The density panel renders data-density="sm" into the raw SSR; the hero's own type is unchanged while the composed guests step down (the CTA and the badges inherit the rung's channels — measured 13→12px and 12→11px).

hero-section-axes.svelte svelte
<!-- theme: the PARTIAL pole — a dark island flips the ACCENT
     voices (eyebrow --primary-text, title em --primary, both raw var()
     strings); the muted lead/badges freeze on the defineVars alias
     (resolved at the stylex :root scope — the declaring-element law) -->
<HeroSection eyebrow="night-shift · v2" summary="Dark accent, same bones." theme="dark" copyCommand="npx jixoai-ui init">
  {#snippet title()}Same hero. <em>New ink.</em>{/snippet}
</HeroSection>

<!-- density: SUPPLY-ONLY on the hero's own atoms (the cm voices and
     the kernel spacing hold) — the composed children inherit the
     re-based channels. The supply chain is the point. -->
<HeroSection eyebrow="your-app · v0" summary="The sm rung: my atoms hold, my guests breathe tighter." density="sm" copyCommand="npx jixoai-ui init">
  {#snippet title()}Supply-side density.{/snippet}
  {#snippet badges()}
    <Badge>sm rung</Badge>
    <Badge variant="outline">composed child</Badge>
  {/snippet}
</HeroSection>
one real query() case
<script lang="ts">
  import HeroSection from '@ui/hero-section.svelte';
  import { query } from '@lib/universal-props-query.svelte';
  import type { ThemeLane } from '@lib/defaults.svelte';
</script>

<!-- below 48rem the base (light) applies; at 48rem+ the md case
     wins and the accent voices flip -->
<HeroSection
  eyebrow="responsive · v1"
  summary="Light below 48rem; the md case flips the ink voices above it."
  copyCommand="npx jixoai-ui init"
  theme={query<{ md: ThemeLane }, ThemeLane>({ md: 'dark' }, 'light')}
>
  {#snippet title()}Viewport-conditional ink. <em>Same bones.</em>{/snippet}
</HeroSection>

hero-section · query()

responsive · v1

Viewport-conditional ink. Same bones.

Light below 48rem; the md case flips the ink voices above it. Resize across 48rem.

md → dark

Media keys are min-width: below 48rem the base applies — the ambient light profile; at 48rem and wider the md case wins and the accent voices flip. The first paint is the BASE by §9.1 (SSR renders before any media can answer), so the flip engages after hydration and then tracks the viewport live. The theme lane is a STRING lane: both generics are load-bearing (the heading page's query case shows the number-lane bare form).

hero-section-query-demo.svelte svelte
<script lang="ts">
  import HeroSection from '@ui/hero-section.svelte';
  import { query } from '@lib/universal-props-query.svelte';
  import type { ThemeLane } from '@lib/defaults.svelte';
</script>

<!-- below 48rem the base (light) applies; at 48rem+ the md case
     wins and the accent voices flip -->
<HeroSection
  eyebrow="responsive · v1"
  summary="Light below 48rem; the md case flips the ink voices above it."
  copyCommand="npx jixoai-ui init"
  theme={query<{ md: ThemeLane }, ThemeLane>({ md: 'dark' }, 'light')}
>
  {#snippet title()}Viewport-conditional ink. <em>Same bones.</em>{/snippet}
</HeroSection>
TokenDefault
the container tier@container jx-hero (min-width: 64rem)
the cm measure idiomlead 16cm · title 22cm · aside 10.5–13cm
the title clampclamp(2.4rem, 6cqi, 4.4rem)
--motion-hero / --ease-hero480ms · cubic-bezier(0.22, 1, 0.36, 1)
--jx-hero-delay / --jx-hero-rise0/60/120/160/200/260ms · 10–14px
the ink voices, by emission formraw var() flips · defineVars alias freezes
--jx-shell-w90rem

a11y

Accessibility

Landmark chrome: one h1 through the data-jx-hero-title hook, a real button for the copy CTA (the press-button's hit lane rides the fleet floor — 7U/28px guardrail above the WCAG 2.5.8 AA 24px minimum), decorative SVGs hidden, and the entrance cascade killed under prefers-reduced-motion.

Keyboard

KeyAction
TabMoves focus through the copy CTA and secondary CTAs in reading order
Enter / SpaceActivates the copy CTA — command hits the clipboard, surface flips to copied

ARIA

AttributeValueDescription
aria-labelcopyLabelAccessible name for the default copy CTA ("copy" / localized)
aria-hiddentrueOn decorative copy-check SVGs
heading structureh1The title snippet renders inside the h1 via the data-jx-hero-title hook — keep it one per page; skipping levels below it is the caller's outline decision
prefers-reduced-motioncascade killThe entrance animation is removed entirely (hero-section.css) — reduced-motion users get the final state at first paint
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h