# @hyzer-labs/ui — full component reference > Every component's props, styling hooks, and accessibility notes in one file. > Generated from the same data that renders the documentation site. Index of every documentation page: https://design.hyzer.sh/llms.txt Conventions for coding agents: https://design.hyzer.sh/agents.md ## Imports ```ts import { Button, Modal, Combobox } from '@hyzer-labs/ui'; // components import { defineConfig } from '@hyzer-labs/ui/config'; // token engine import { reveal, fade } from '@hyzer-labs/ui/motion'; // motion import { intersect } from '@hyzer-labs/ui/observers'; // observers import IconSearch from '@hyzer-labs/ui/icons/search'; // one glyph at a time import { contrastRatio } from '@hyzer-labs/ui/utils'; // utilities import type { Intent } from '@hyzer-labs/ui/types'; // types import '@hyzer-labs/ui/tokens.css'; // required import '@hyzer-labs/ui/theme'; // optional reference theme import '@hyzer-labs/ui/reset.css'; // optional structural reset ``` ## Content ### Accordion A disclosure component using native
/ elements, supporting single and multiple open modes with keyboard navigation. Item titles accept plain strings or snippets. Docs: https://design.hyzer.sh/docs/components/accordion ```ts import { Accordion } from "@hyzer-labs/ui" ``` #### Props | Prop | Type | Default | Notes | | --- | --- | --- | --- | | `items` | `AccordionItem[]` | `—` | Required. See AccordionItem below. | | `type` | `'single' \| 'multiple'` | `'single'` | | | `defaultOpen` | `string \| string[]` | `[]` | | | `collapsible` | `boolean` | `true` | false keeps one panel open in single mode. | | `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `3` | | | `panel` | `Snippet<[AccordionItem]>` | `—` | Required. Renders each panel. | | `icon` | `Snippet` | `—` | Replaces the default chevron. | | `onToggle` | `(openIds: string[]) => void` | `—` | | | `class` | `string` | `—` | Merged after the hz-accordion class. | #### AccordionItem | Prop | Type | Default | Notes | | --- | --- | --- | --- | | `id` | `string` | `—` | Required. Must be unique. | | `title` | `string \| Snippet` | `—` | Required. String for plain text; snippet for inner markup. | | `disabled` | `boolean` | `false` | | #### Styling Root class: `hz-accordion` | Attribute | Values | Notes | | --- | --- | --- | | `data-state` | `'open' \| 'closed'` | On .hz-accordion-item; rotates the icon. | | `data-type` | `'single' \| 'multiple'` | Mirrors the prop. The behaviour is native
grouping, not CSS, so nothing in the theme reads this — it is yours. | | `[aria-disabled]` | `'true'` | Disabled styling hangs off aria-disabled on .hz-accordion-trigger — not off the data-disabled that also appears on the item. Tabs does the opposite; match the component you are styling. | | Part | Values | Notes | | --- | --- | --- | | `.hz-accordion-item` | `child element` | The
element. | | `.hz-accordion-trigger` | `child element` | The . | | `.hz-accordion-heading` | `child element` | The heading inside the summary, at your headingLevel. | | `.hz-accordion-icon` | `child element` | The disclosure chevron. | | `.hz-accordion-panel` | `child element` | The content. The component ships no display/height/overflow on it on purpose, so it is free for you to animate. | #### Accessibility Built on native `
`, so no ARIA is needed for disclosure semantics. Arrow keys move between summaries, and Home/End jump to the first and last. Disabled items have `aria-disabled='true'` and block interaction. Summaries wrap a real heading (`headingLevel`) so panels join the document outline. ### Blockquote A semantic quote: a figure wrapping a blockquote, with an optional visible attribution and an optional machine-readable source URL. Docs: https://design.hyzer.sh/docs/components/blockquote ```ts import { Blockquote } from "@hyzer-labs/ui" ``` #### Props | Prop | Type | Default | Notes | | --- | --- | --- | --- | | `children` | `Snippet` | `—` | Required. The quoted content. | | `cite` | `string \| Snippet` | `—` | Visible attribution, rendered in a outside the quote. | | `citeUrl` | `string` | `—` | Source URL — sets the blockquote cite attribute; never rendered as text. | | `align` | `'start' \| 'center' \| 'end'` | `'start'` | Aligns the attribution row under the quote; the quote body is untouched. | | `intent` | `Intent` | `—` | See Foundation → Colors & Intent. | | `intentScope` | `'line' \| 'full'` | `'line'` | Only relevant with intent set. line colors just the accent line; full also colors the quote text. Attribution stays muted either way. | | `class` | `string` | `—` | Merged after the hz-blockquote class. | #### Styling Root class: `hz-blockquote` | Attribute | Values | Notes | | --- | --- | --- | | `data-align` | `'start' \| 'center' \| 'end'` | Aligns the attribution row only — the quote body is deliberately left alone. Default start. | | `data-intent` | `any registered intent` | Present only when intent is set. Colors only the accent line (border-inline-start) — no intent leaves the accent line at its default border color. | | `data-intent-scope` | `'line' \| 'full'` | Present only when intent is set (mirrors data-intent — meaningless without an intent to scope). line (default) matches data-intent alone; full also colors the quote text. The attribution row is never affected. | | Custom property | Values | Notes | | --- | --- | --- | | `--hz-blockquote-border-width` | ` — default var(--hz-border-width-heavy)` | Thickness of the inline-start accent line. Defaults to the heaviest border-width token; override with any length or a lighter token. The attribution row indent tracks it automatically. | | `--hz-blockquote-font-size` | ` — default var(--hz-font-size-xl)` | Font size of the quote body. Read, never declared, so it can be set per instance or inherited from any ancestor — the docs site tunes it down to body size for its doctrine notes. | | Part | Values | Notes | | --- | --- | --- | | `.hz-blockquote-quote` | `child element` | The quote itself, carrying the accent bar. | | `.hz-blockquote-attribution` | `child element` | The attribution line. | | `.hz-blockquote-cite` | `child element` | The cite element. | #### Accessibility The root is always a `
` wrapping a `
`. When `cite` is provided, the attribution renders in a `
` outside the quote, so screen readers do not announce it as part of the quotation itself. No ARIA is added: the native `figure`/`blockquote`/`figcaption`/`cite` elements carry all the semantics. The decorative em-dash before the attribution is a theme `::before` pseudo-element, so it never enters the accessible name. ### Button A button with solid, outline, ghost, and soft variants, plus intent colors, sizes, a loading state, and icon slots. Docs: https://design.hyzer.sh/docs/components/button ```ts import { Button } from "@hyzer-labs/ui" ``` #### Props | Prop | Type | Default | Notes | | --- | --- | --- | --- | | `variant` | `'solid' \| 'outline' \| 'ghost' \| 'soft'` | `'solid'` | | | `intent` | `Intent` | `'primary'` | The full intent registry, not a hand-picked subset — see Foundation → Colors & Intent. | | `size` | `'sm' \| 'md' \| 'lg' \| 'full'` | `'md'` | full fills its container at the md height/padding; not combinable with sm/lg. | | `disabled` | `boolean` | `false` | | | `loading` | `boolean` | `false` | | | `loadingLabel` | `string` | `'Loading'` | Screen-reader-only text announced while loading; not rendered visually. | | `href` | `string` | `—` | Renders as a plain (no role="button") when set — it genuinely navigates. | | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | | | `ariaLabel` | `string` | `—` | Required for icon-only buttons. | | `onclick` | `(e: MouseEvent) => void` | `—` | Swallowed while disabled or loading. | | `class` | `string` | `—` | Merged after the hz-button class. | | `children` | `Snippet` | `—` | | | `iconStart` | `Snippet` | `—` | An icon snippet with no children renders the compact circular icon-only form. | | `iconEnd` | `Snippet` | `—` | | #### Styling Root class: `hz-button` | Attribute | Values | Notes | | --- | --- | --- | | `data-variant` | `'solid' \| 'outline' \| 'ghost' \| 'soft'` | Default solid. | | `data-intent` | `'neutral' \| any registered intent` | The full vocabulary, not a hand-picked subset — register an intent and Button stamps it. Sets --hz-button-accent. | | `data-size` | `'sm' \| 'md' \| 'lg' \| 'full'` | Default md. full fills its container at the md height/padding — not combinable with sm/lg. | | `data-state` | `'disabled' \| 'loading' \| absent` | Absent when interactive — every hover and active rule is gated on :not([data-state]), so styling a state cannot be undone by a hover. disabled wins over loading. | | `data-icon-only` | `present when icon-only` | Derived: an icon with no children. Renders the circle form. | | Custom property | Values | Notes | | --- | --- | --- | | `--hz-button-accent` | ` — default var(--hz-intent-primary)` | The accent every variant derives from. Override it to restyle solid, outline, ghost, and soft at once — and it is how you wire up an intent you registered yourself. | | `--hz-button-on-accent` | ` — default var(--hz-color-surface)` | Text colour on a solid fill. The surface role rather than white, so it flips with the mode and keeps solid text ≥ 4.5:1 in both. | | `--hz-button-tint` | ` — 14% light, 28% dark` | Soft-variant background tint strength — the same recipe and hook pattern as --hz-badge-tint. | #### Accessibility Use `ariaLabel` for icon-only buttons (no visible text). The loading state sets `aria-busy="true"` and renders a screen-reader-only "Loading" label. Disabled state sets `aria-disabled="true"`. ### Card A content container with optional media, actions, horizontal layout, and clickable-overlay support. Docs: https://design.hyzer.sh/docs/components/card ```ts import { Card } from "@hyzer-labs/ui" ``` #### Props | Prop | Type | Default | Notes | | --- | --- | --- | --- | | `padding` | `'none' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Set 'none' to opt out and control padding entirely from your own class. | | `rounded` | `'none' \| 'sm' \| 'md' \| 'lg'` | `'md'` | | | `href` | `string` | `—` | Makes the whole card clickable. | | `ariaLabel` | `string` | `—` | Required when href is set. | | `horizontal` | `boolean` | `false` | | | `mediaPosition` | `'start' \| 'end'` | `'start'` | | | `class` | `string` | `—` | Merged after the hz-card class. | | `media` | `Snippet` | `—` | | | `children` | `Snippet` | `—` | | | `actions` | `Snippet` | `—` | | #### Styling Root class: `hz-card` | Attribute | Values | Notes | | --- | --- | --- | | `data-padding` | `'none' \| 'sm' \| 'md' \| 'lg'` | Lands on .hz-card-content, not the root — which is how media bleeds edge-to-edge while text stays inset. Default md. | | `data-rounded` | `'none' \| 'sm' \| 'md' \| 'lg'` | No full rung, unlike the shared Rounded scale. Default md. | | `data-horizontal` | `present when horizontal` | Media beside content above 640px. | | `data-clickable` | `present when href is set` | The whole card becomes a target; the theme hangs its hover affordances off this together with a treatment class. | | `data-media-position` | `'start' \| 'end'` | Mirrors the prop. The effect is DOM order, not CSS order — so reading order and visual order cannot disagree. | | Custom property | Values | Notes | | --- | --- | --- | | `--hz-card-media-size` | ` — default 40%` | The media track in horizontal mode; media fills it. Never declared, so set it yourself on .hz-card or an ancestor. | | Part | Values | Notes | | --- | --- | --- | | `.hz-card--outlined` | `opt-in class` | A theme treatment, not a prop: pass it via class. Card ships no variant prop because the look is the theme’s call, not the API’s. | | `.hz-card--elevated` | `opt-in class` | The shadow treatment. Same deal. | | `.hz-card-title` | `opt-in class` | Card never emits this — you bring the heading element at whatever level the page needs, and the class only styles it. Headings aren’t load-bearing for Card the way they are for Modal or Accordion, so this never became API. | | `.hz-card-media` | `child element` | The media wrapper. | | `.hz-card-content` | `child element` | The padded region. | | `.hz-card-body` | `child element` | The default slot wrapper. | | `.hz-card-actions` | `child element` | The actions row. | | `.hz-card-link` | `child element` | The overlay anchor, present only when clickable. Its own focus ring would be clipped on rounded cards, so the theme draws the ring on .hz-card itself while this anchor holds focus. | #### Accessibility When `href` is set, the entire card is wrapped in a link, so supply `ariaLabel` with a descriptive name. Inner interactive elements remain clickable above the overlay via z-index. ### Carousel An accessible, manually rotated carousel: a draggable slide track, labeled slides, previous and next controls, arrow-key steering, and a live region announcing changes. It does not auto-rotate, by design. Docs: https://design.hyzer.sh/docs/components/carousel ```ts import { Carousel } from "@hyzer-labs/ui" ``` #### Props | Prop | Type | Default | Notes | | --- | --- | --- | --- | | `items` | `T[]` | `—` | Required. Generic — each item renders via the slide snippet. | | `ariaLabel` | `string` | `—` | Required. Names the carousel region. | | `index` | `number (bindable)` | `0` | | | `loop` | `boolean` | `false` | Wrap from the last slide to the first and back. | | `draggable` | `boolean` | `true` | Pointer drag to slide (drag wraps when loop is set — seamlessly, when seamless is also set). Off leaves keyboard, buttons, and dots working. | | `controls` | `'visible' \| 'focus'` | `'visible'` | focus keeps the prev/next buttons and indicator in the DOM and fully operable, hidden only visually until :hover/:focus-within reveals the whole row together — the WCAG 2.5.7 non-dragging alternative, always reachable. | | `seamless` | `boolean` | `false` | Opt-in continuous boundary wrap: every ±1 loop step — drag, buttons, dots, arrow keys — settles through a hidden clone instead of sweeping back through the row. Only meaningful with loop; an inert no-op without it. | | `indicator` | `'counter' \| 'dots'` | `'counter'` | The "1 / 3" counter, or clickable slide-picker dots. | | `prevLabel` | `string` | `'Previous slide'` | | | `nextLabel` | `string` | `'Next slide'` | | | `slideLabel` | `(item, index) => string` | `—` | Accessible name per slide; defaults to "{n} of {total}". | | `dotLabel` | `(index, count) => string` | `—` | Accessible name per dot; defaults to "Go to slide {n} of {total}". | | `onchange` | `(index: number) => void` | `—` | | | `slide` | `Snippet<[T, number]>` | `—` | Required. Renders one slide. | | `class` | `string` | `—` | Merged after the hz-carousel class. | #### Styling Root class: `hz-carousel` | Attribute | Values | Notes | | --- | --- | --- | | `data-active` | `present on the active slide and dot` | On .hz-carousel-slide and .hz-carousel-dot. Off-screen slides carry inert instead — they are clipped by the viewport, not hidden — so target :not([inert]) for the visible one. | | `data-dragging` | `present while a drag is underway` | On .hz-carousel-track. The settle transition is suppressed while it is present so the track follows the pointer 1:1; a grab/grabbing cursor pairs with it. | | `data-controls` | `'visible' \| 'focus'` | On the root, always stamped, both values. Presentation only: the controls markup is identical either way. focus visually hides the whole control row until :hover/:focus-within reveals it together — the row stays in the DOM, in the a11y tree, and fully operable throughout (the WCAG 2.5.7 non-dragging alternative to drag). Default 'visible'. | | `data-seamless` | `present only when seamless && loop` | On the root. Absent when seamless is set without loop — an inert no-op — so the hook reflects effective behavior, never advertising a wrap that cannot happen. | | `data-clone` | `present on a wrap-settle clone slide` | On .hz-carousel-slide. Rendered only mid-wrap, seamless loop, a distance-1 boundary crossing (drag, buttons, an adjacent-wrap dot, or arrow keys) — an inert, aria-hidden copy of the opposite-end slide the track settles into before silently resetting to the real target. Never counted in count, "{n} of {total}", or the dot rail; never focusable. | | Custom property | Values | Notes | | --- | --- | --- | | `--hz-carousel-dot-size` | ` — default 0.5rem` | Dot diameter (the painted size; the tap target is larger). Declared on .hz-carousel-dot itself, so set it there — declaring it on .hz-carousel will not reach, since the local declaration beats your inherited value. | | `--hz-carousel-focus-min-height` | ` — default 12rem` | Minimum height of .hz-carousel-viewport, data-controls='focus' only. The revealed control row is an absolutely positioned overlay with no reserved layout space, so on a short carousel it can cover most of the slide — this keeps slide content clear of the row regardless of the slide's own height. data-controls='visible' needs no reserved space (its row sits in normal flow below the viewport) and is unaffected. | | Part | Values | Notes | | --- | --- | --- | | `.hz-carousel-viewport` | `child element` | The clip window and live region. | | `.hz-carousel-track` | `child element` | The sliding row of slides. Its transform is an inline style; the transition and the drag cursor live here. | | `.hz-carousel-slide` | `child element` | One slide; off-screen ones are inert. | | `.hz-carousel-controls` | `child element` | The control row. | | `.hz-carousel-prev` | `on a Button` | Previous control; also a .hz-button. Its ::before carries the 44px touch target. | | `.hz-carousel-next` | `on a Button` | Next control; also a .hz-button. Its ::before carries the 44px touch target. | | `.hz-carousel-dots` | `child element` | The dot rail. | | `.hz-carousel-dot` | `child element` | One dot. Its ::before is a transparent tap target larger than the painted dot. | | `.hz-carousel-status` | `child element` | The "1 / 3" counter. | #### Accessibility Built on the APG carousel pattern. The region and each slide carry `aria-roledescription`, and slides are named ('2 of 5'-style by default, or set your own with `slideLabel`). There is no auto-rotation, so the viewport is an `aria-live="polite"` region and slide changes announce themselves. Arrow keys, Home, and End move between slides while focus is inside the carousel. `controls="focus"` hides the control row visually only. It never uses `display`, `visibility`, `aria-hidden`, or `inert`, so the row stays reachable by Tab and appears on hover. That is the WCAG 2.5.7 alternative to the drag gesture. ### CodeBlock A headless, read-only code viewer with a copy button, an opt-in title and language header, decorative line numbers, and a Show-more collapse for long listings. It ships no syntax highlighter, so bring your own through the language class hook (client autoloaders) or the children escape hatch (build-time highlighters like Shiki). Docs: https://design.hyzer.sh/docs/components/code-block ```ts import { CodeBlock } from "@hyzer-labs/ui" ``` #### Props | Prop | Type | Default | Notes | | --- | --- | --- | --- | | `code` | `string` | `—` | Required. The source text. Copy, the line count, and the gutter always read from this, never from `children` or highlighter-injected markup. | | `children` | `Snippet` | `—` | Escape hatch for pre-highlighted content, usually a build-time highlighter's own `
` via `{@html}` (Shiki, for example). Replaces the default `
` entirely; `code` is still required. |
| `title` | `string` | `—` | A filename or label. Renders the header bar, together with `language`. |
| `language` | `string` | `—` | Stamps `class="language-"` on the default code (the hook browser-side highlighters look for) and shows a visible, non-interactive tag in the header. |
| `lineNumbers` | `boolean` | `false` | Opt-in decorative line-number gutter. Never part of copy or selection. |
| `copyable` | `boolean` | `true` | Whether the built-in copy button renders. |
| `collapsible` | `boolean` | `false` | Clamp tall listings behind a Show-more/less toggle. |
| `collapsedLines` | `number` | `16` | Rows shown while collapsed. The toggle appears only when the listing is longer than this. |
| `class` | `string` | `—` | Merged after the hz-code-block class. |

#### Styling

Root class: `hz-code-block`

| Attribute | Values | Notes |
| --- | --- | --- |
| `data-language` | ` — present when language is set` | Mirrors the language prop; the default code also carries language- for client autoloaders, and the header shows a matching tag. |
| `data-has-title` | `present when title is set` | Marks the titled form. Independent of data-language — the header can render from either. |
| `data-highlighted` | `present when the children escape hatch is used` | Marks a block whose code node is consumer-supplied pre-highlighted markup. Target it to drop the default code-surface fill and let a build-time highlighter's own background sit cleanly. |
| `data-collapsible` | `present when the listing exceeds collapsedLines` | Reflects effective behaviour, not the raw prop — a short listing never gets the toggle, so this is absent for it. |
| `data-line-numbers` | `present when lineNumbers is on` | Marks the gutter form. |

| Custom property | Values | Notes |
| --- | --- | --- |
| `--hz-code-block-bg` | ` — default var(--hz-color-surface-muted)` | The code surface fill; the collapse fade fades into it. Suppressed under data-highlighted so a build-time highlighter's own background shows. |
| `--hz-code-block-padding` | ` — default 1rem` | Padding inside the code region (pre) — reaches a consumer-supplied pre too. |
| `--hz-code-block-fade-height` | ` — default 3rem` | Height of the collapse fade over the clamped edge. |

| Part | Values | Notes |
| --- | --- | --- |
| `.hz-code-block-header` | `child element` | The header bar; present when title or language is set. |
| `.hz-code-block-title` | `child element` | The filename/label text. |
| `.hz-code-block-lang` | `child element` | The non-interactive language tag; present only when language is set. |
| `.hz-code-block-clip` | `child element` | The code region — the collapsible viewport, focusable when it renders the default code node. |
| `.hz-code-block-gutter` | `child element` | The aria-hidden, non-selectable line-number column; present only with lineNumbers. |
| `.hz-code-block-copy` | `on a Button` | Rides through Button’s class prop, so it also carries .hz-button and its data-attrs. |
| `.hz-code-block-expand` | `on a Button` | The Show-more/less toggle; also a .hz-button. |
| `code.language-` | `on the default inner code` | The class a client autoloader targets; present only when language is set and the children escape hatch is not used. Not an hz-owned class. |

#### Accessibility

The copy button is a real labeled `