{"site":"https://design.hyzer.sh","imports":"import { Button, Modal, Combobox } from '@hyzer-labs/ui';   // components\nimport { defineConfig } from '@hyzer-labs/ui/config';        // token engine\nimport { reveal, fade } from '@hyzer-labs/ui/motion';        // motion\nimport { intersect } from '@hyzer-labs/ui/observers';        // observers\nimport IconSearch from '@hyzer-labs/ui/icons/search';        // one glyph at a time\nimport { contrastRatio } from '@hyzer-labs/ui/utils';        // utilities\nimport type { Intent } from '@hyzer-labs/ui/types';          // types\n\nimport '@hyzer-labs/ui/tokens.css';    // required\nimport '@hyzer-labs/ui/theme';         // optional reference theme\nimport '@hyzer-labs/ui/reset.css';     // optional structural reset","components":[{"name":"Accordion","group":"Content","route":"/docs/components/accordion","description":"A disclosure component using native <details>/<summary> elements, supporting single and multiple open modes with keyboard navigation. Item titles accept plain strings or snippets.","importLine":"import { Accordion } from \"@hyzer-labs/ui\"","props":[{"name":"items","type":"AccordionItem[]","default":"—","note":"Required. See AccordionItem below."},{"name":"type","type":"'single' | 'multiple'","default":"'single'"},{"name":"defaultOpen","type":"string | string[]","default":"[]"},{"name":"collapsible","type":"boolean","default":"true","note":"false keeps one panel open in single mode."},{"name":"headingLevel","type":"2 | 3 | 4 | 5 | 6","default":"3"},{"name":"panel","type":"Snippet<[AccordionItem]>","default":"—","note":"Required. Renders each panel."},{"name":"icon","type":"Snippet","default":"—","note":"Replaces the default chevron."},{"name":"onToggle","type":"(openIds: string[]) => void","default":"—"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-accordion class."}],"types":[{"name":"AccordionItem","props":[{"name":"id","type":"string","default":"—","note":"Required. Must be unique."},{"name":"title","type":"string | Snippet","default":"—","note":"Required. String for plain text; snippet for inner markup."},{"name":"disabled","type":"boolean","default":"false"}]}],"hooks":{"root":"hz-accordion","attrs":[{"name":"data-state","values":"'open' | 'closed'","note":"On .hz-accordion-item; rotates the icon."},{"name":"data-type","values":"'single' | 'multiple'","note":"Mirrors the prop. The behaviour is native <details> grouping, not CSS, so nothing in the theme reads this — it is yours."},{"name":"[aria-disabled]","values":"'true'","note":"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."}],"parts":[{"name":".hz-accordion-item","values":"child element","note":"The <details> element."},{"name":".hz-accordion-trigger","values":"child element","note":"The <summary>."},{"name":".hz-accordion-heading","values":"child element","note":"The heading inside the summary, at your headingLevel."},{"name":".hz-accordion-icon","values":"child element","note":"The disclosure chevron."},{"name":".hz-accordion-panel","values":"child element","note":"The content. The component ships no display/height/overflow on it on purpose, so it is free for you to animate."}]},"a11yNote":"Built on native `<details>`, so no ARIA is needed for disclosure semantics. Arrow keys move between summaries, and Home/End jump to the first and last.\n\nDisabled items have `aria-disabled='true'` and block interaction. Summaries wrap a real heading (`headingLevel`) so panels join the document outline."},{"name":"Blockquote","group":"Content","route":"/docs/components/blockquote","description":"A semantic quote: a figure wrapping a blockquote, with an optional visible attribution and an optional machine-readable source URL.","importLine":"import { Blockquote } from \"@hyzer-labs/ui\"","props":[{"name":"children","type":"Snippet","default":"—","note":"Required. The quoted content."},{"name":"cite","type":"string | Snippet","default":"—","note":"Visible attribution, rendered in a <cite> outside the quote."},{"name":"citeUrl","type":"string","default":"—","note":"Source URL — sets the blockquote cite attribute; never rendered as text."},{"name":"align","type":"'start' | 'center' | 'end'","default":"'start'","note":"Aligns the attribution row under the quote; the quote body is untouched."},{"name":"intent","type":"Intent","default":"—","note":"See Foundation → Colors & Intent.","noteHref":"/docs/foundation/colors#intent"},{"name":"intentScope","type":"'line' | 'full'","default":"'line'","note":"Only relevant with intent set. line colors just the accent line; full also colors the quote text. Attribution stays muted either way."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-blockquote class."}],"types":[],"hooks":{"root":"hz-blockquote","attrs":[{"name":"data-align","values":"'start' | 'center' | 'end'","note":"Aligns the attribution row only — the quote body is deliberately left alone. Default start."},{"name":"data-intent","values":"any registered intent","note":"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."},{"name":"data-intent-scope","values":"'line' | 'full'","note":"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."}],"props":[{"name":"--hz-blockquote-border-width","values":"<length> — default var(--hz-border-width-heavy)","note":"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."},{"name":"--hz-blockquote-font-size","values":"<length> — default var(--hz-font-size-xl)","note":"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."}],"parts":[{"name":".hz-blockquote-quote","values":"child element","note":"The quote itself, carrying the accent bar."},{"name":".hz-blockquote-attribution","values":"child element","note":"The attribution line."},{"name":".hz-blockquote-cite","values":"child element","note":"The cite element."}]},"a11yNote":"The root is always a `<figure>` wrapping a `<blockquote>`. When `cite` is provided, the attribution renders in a `<figcaption><cite>` outside the quote, so screen readers do not announce it as part of the quotation itself.\n\nNo ARIA is added: the native `figure`/`blockquote`/`figcaption`/`cite` elements carry all the semantics.\n\nThe decorative em-dash before the attribution is a theme `::before` pseudo-element, so it never enters the accessible name."},{"name":"Button","group":"Content","route":"/docs/components/button","description":"A button with solid, outline, ghost, and soft variants, plus intent colors, sizes, a loading state, and icon slots.","importLine":"import { Button } from \"@hyzer-labs/ui\"","props":[{"name":"variant","type":"'solid' | 'outline' | 'ghost' | 'soft'","default":"'solid'"},{"name":"intent","type":"Intent","default":"'primary'","note":"The full intent registry, not a hand-picked subset — see Foundation → Colors & Intent.","noteHref":"/docs/foundation/colors#intent"},{"name":"size","type":"'sm' | 'md' | 'lg' | 'full'","default":"'md'","note":"full fills its container at the md height/padding; not combinable with sm/lg."},{"name":"disabled","type":"boolean","default":"false"},{"name":"loading","type":"boolean","default":"false"},{"name":"loadingLabel","type":"string","default":"'Loading'","note":"Screen-reader-only text announced while loading; not rendered visually."},{"name":"href","type":"string","default":"—","note":"Renders as a plain <a> (no role=\"button\") when set — it genuinely navigates."},{"name":"type","type":"'button' | 'submit' | 'reset'","default":"'button'"},{"name":"ariaLabel","type":"string","default":"—","note":"Required for icon-only buttons."},{"name":"onclick","type":"(e: MouseEvent) => void","default":"—","note":"Swallowed while disabled or loading."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-button class."},{"name":"children","type":"Snippet","default":"—"},{"name":"iconStart","type":"Snippet","default":"—","note":"An icon snippet with no children renders the compact circular icon-only form."},{"name":"iconEnd","type":"Snippet","default":"—"}],"types":[],"hooks":{"root":"hz-button","attrs":[{"name":"data-variant","values":"'solid' | 'outline' | 'ghost' | 'soft'","note":"Default solid."},{"name":"data-intent","values":"'neutral' | any registered intent","note":"The full vocabulary, not a hand-picked subset — register an intent and Button stamps it. Sets --hz-button-accent."},{"name":"data-size","values":"'sm' | 'md' | 'lg' | 'full'","note":"Default md. full fills its container at the md height/padding — not combinable with sm/lg."},{"name":"data-state","values":"'disabled' | 'loading' | absent","note":"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."},{"name":"data-icon-only","values":"present when icon-only","note":"Derived: an icon with no children. Renders the circle form."}],"props":[{"name":"--hz-button-accent","values":"<color> — default var(--hz-intent-primary)","note":"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."},{"name":"--hz-button-on-accent","values":"<color> — default var(--hz-color-surface)","note":"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."},{"name":"--hz-button-tint","values":"<percentage> — 14% light, 28% dark","note":"Soft-variant background tint strength — the same recipe and hook pattern as --hz-badge-tint."}]},"a11yNote":"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\"`."},{"name":"Card","group":"Content","route":"/docs/components/card","description":"A content container with optional media, actions, horizontal layout, and clickable-overlay support.","importLine":"import { Card } from \"@hyzer-labs/ui\"","props":[{"name":"padding","type":"'none' | 'sm' | 'md' | 'lg'","default":"'md'","note":"Set 'none' to opt out and control padding entirely from your own class."},{"name":"rounded","type":"'none' | 'sm' | 'md' | 'lg'","default":"'md'"},{"name":"href","type":"string","default":"—","note":"Makes the whole card clickable."},{"name":"ariaLabel","type":"string","default":"—","note":"Required when href is set."},{"name":"horizontal","type":"boolean","default":"false"},{"name":"mediaPosition","type":"'start' | 'end'","default":"'start'"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-card class."},{"name":"media","type":"Snippet","default":"—"},{"name":"children","type":"Snippet","default":"—"},{"name":"actions","type":"Snippet","default":"—"}],"types":[],"hooks":{"root":"hz-card","attrs":[{"name":"data-padding","values":"'none' | 'sm' | 'md' | 'lg'","note":"Lands on .hz-card-content, not the root — which is how media bleeds edge-to-edge while text stays inset. Default md."},{"name":"data-rounded","values":"'none' | 'sm' | 'md' | 'lg'","note":"No full rung, unlike the shared Rounded scale. Default md."},{"name":"data-horizontal","values":"present when horizontal","note":"Media beside content above 640px."},{"name":"data-clickable","values":"present when href is set","note":"The whole card becomes a target; the theme hangs its hover affordances off this together with a treatment class."},{"name":"data-media-position","values":"'start' | 'end'","note":"Mirrors the prop. The effect is DOM order, not CSS order — so reading order and visual order cannot disagree."}],"props":[{"name":"--hz-card-media-size","values":"<length|percentage> — default 40%","note":"The media track in horizontal mode; media fills it. Never declared, so set it yourself on .hz-card or an ancestor."}],"parts":[{"name":".hz-card--outlined","values":"opt-in class","note":"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."},{"name":".hz-card--elevated","values":"opt-in class","note":"The shadow treatment. Same deal."},{"name":".hz-card-title","values":"opt-in class","note":"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."},{"name":".hz-card-media","values":"child element","note":"The media wrapper."},{"name":".hz-card-content","values":"child element","note":"The padded region."},{"name":".hz-card-body","values":"child element","note":"The default slot wrapper."},{"name":".hz-card-actions","values":"child element","note":"The actions row."},{"name":".hz-card-link","values":"child element","note":"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."}]},"a11yNote":"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."},{"name":"Carousel","group":"Content","route":"/docs/components/carousel","description":"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.","importLine":"import { Carousel } from \"@hyzer-labs/ui\"","props":[{"name":"items","type":"T[]","default":"—","note":"Required. Generic — each item renders via the slide snippet."},{"name":"ariaLabel","type":"string","default":"—","note":"Required. Names the carousel region."},{"name":"index","type":"number (bindable)","default":"0"},{"name":"loop","type":"boolean","default":"false","note":"Wrap from the last slide to the first and back."},{"name":"draggable","type":"boolean","default":"true","note":"Pointer drag to slide (drag wraps when loop is set — seamlessly, when seamless is also set). Off leaves keyboard, buttons, and dots working."},{"name":"controls","type":"'visible' | 'focus'","default":"'visible'","note":"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."},{"name":"seamless","type":"boolean","default":"false","note":"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."},{"name":"indicator","type":"'counter' | 'dots'","default":"'counter'","note":"The \"1 / 3\" counter, or clickable slide-picker dots."},{"name":"prevLabel","type":"string","default":"'Previous slide'"},{"name":"nextLabel","type":"string","default":"'Next slide'"},{"name":"slideLabel","type":"(item, index) => string","default":"—","note":"Accessible name per slide; defaults to \"{n} of {total}\"."},{"name":"dotLabel","type":"(index, count) => string","default":"—","note":"Accessible name per dot; defaults to \"Go to slide {n} of {total}\"."},{"name":"onchange","type":"(index: number) => void","default":"—"},{"name":"slide","type":"Snippet<[T, number]>","default":"—","note":"Required. Renders one slide."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-carousel class."}],"types":[],"hooks":{"root":"hz-carousel","attrs":[{"name":"data-active","values":"present on the active slide and dot","note":"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."},{"name":"data-dragging","values":"present while a drag is underway","note":"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."},{"name":"data-controls","values":"'visible' | 'focus'","note":"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'."},{"name":"data-seamless","values":"present only when seamless && loop","note":"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."},{"name":"data-clone","values":"present on a wrap-settle clone slide","note":"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."}],"props":[{"name":"--hz-carousel-dot-size","values":"<length> — default 0.5rem","note":"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."},{"name":"--hz-carousel-focus-min-height","values":"<length> — default 12rem","note":"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."}],"parts":[{"name":".hz-carousel-viewport","values":"child element","note":"The clip window and live region."},{"name":".hz-carousel-track","values":"child element","note":"The sliding row of slides. Its transform is an inline style; the transition and the drag cursor live here."},{"name":".hz-carousel-slide","values":"child element","note":"One slide; off-screen ones are inert."},{"name":".hz-carousel-controls","values":"child element","note":"The control row."},{"name":".hz-carousel-prev","values":"on a Button","note":"Previous control; also a .hz-button. Its ::before carries the 44px touch target."},{"name":".hz-carousel-next","values":"on a Button","note":"Next control; also a .hz-button. Its ::before carries the 44px touch target."},{"name":".hz-carousel-dots","values":"child element","note":"The dot rail."},{"name":".hz-carousel-dot","values":"child element","note":"One dot. Its ::before is a transparent tap target larger than the painted dot."},{"name":".hz-carousel-status","values":"child element","note":"The \"1 / 3\" counter."}]},"a11yNote":"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`).\n\nThere 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.\n\n`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."},{"name":"CodeBlock","group":"Content","route":"/docs/components/code-block","description":"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).","importLine":"import { CodeBlock } from \"@hyzer-labs/ui\"","props":[{"name":"code","type":"string","default":"—","note":"Required. The source text. Copy, the line count, and the gutter always read from this, never from `children` or highlighter-injected markup."},{"name":"children","type":"Snippet","default":"—","note":"Escape hatch for pre-highlighted content, usually a build-time highlighter's own `<pre>` via `{@html}` (Shiki, for example). Replaces the default `<pre><code>` entirely; `code` is still required."},{"name":"title","type":"string","default":"—","note":"A filename or label. Renders the header bar, together with `language`."},{"name":"language","type":"string","default":"—","note":"Stamps `class=\"language-<language>\"` on the default code (the hook browser-side highlighters look for) and shows a visible, non-interactive tag in the header."},{"name":"lineNumbers","type":"boolean","default":"false","note":"Opt-in decorative line-number gutter. Never part of copy or selection."},{"name":"copyable","type":"boolean","default":"true","note":"Whether the built-in copy button renders."},{"name":"collapsible","type":"boolean","default":"false","note":"Clamp tall listings behind a Show-more/less toggle."},{"name":"collapsedLines","type":"number","default":"16","note":"Rows shown while collapsed. The toggle appears only when the listing is longer than this."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-code-block class."}],"types":[],"hooks":{"root":"hz-code-block","attrs":[{"name":"data-language","values":"<string> — present when language is set","note":"Mirrors the language prop; the default code also carries language-<value> for client autoloaders, and the header shows a matching tag."},{"name":"data-has-title","values":"present when title is set","note":"Marks the titled form. Independent of data-language — the header can render from either."},{"name":"data-highlighted","values":"present when the children escape hatch is used","note":"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."},{"name":"data-collapsible","values":"present when the listing exceeds collapsedLines","note":"Reflects effective behaviour, not the raw prop — a short listing never gets the toggle, so this is absent for it."},{"name":"data-line-numbers","values":"present when lineNumbers is on","note":"Marks the gutter form."}],"props":[{"name":"--hz-code-block-bg","values":"<color> — default var(--hz-color-surface-muted)","note":"The code surface fill; the collapse fade fades into it. Suppressed under data-highlighted so a build-time highlighter's own background shows."},{"name":"--hz-code-block-padding","values":"<length> — default 1rem","note":"Padding inside the code region (pre) — reaches a consumer-supplied pre too."},{"name":"--hz-code-block-fade-height","values":"<length> — default 3rem","note":"Height of the collapse fade over the clamped edge."}],"parts":[{"name":".hz-code-block-header","values":"child element","note":"The header bar; present when title or language is set."},{"name":".hz-code-block-title","values":"child element","note":"The filename/label text."},{"name":".hz-code-block-lang","values":"child element","note":"The non-interactive language tag; present only when language is set."},{"name":".hz-code-block-clip","values":"child element","note":"The code region — the collapsible viewport, focusable when it renders the default code node."},{"name":".hz-code-block-gutter","values":"child element","note":"The aria-hidden, non-selectable line-number column; present only with lineNumbers."},{"name":".hz-code-block-copy","values":"on a Button","note":"Rides through Button’s class prop, so it also carries .hz-button and its data-attrs."},{"name":".hz-code-block-expand","values":"on a Button","note":"The Show-more/less toggle; also a .hz-button."},{"name":"code.language-<language>","values":"on the default inner code","note":"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."}]},"a11yNote":"The copy button is a real labeled `<button>`. Its \"Copied\" state lasts about 2 seconds and is announced once through a polite live region. If the clipboard is denied or unavailable, nothing happens and nothing is announced, so you never get a false \"copied\" (WCAG 4.1.3).\n\nIn its default rendering the code region is a focusable, named `role=\"group\"`, so you can scroll an overflowing listing with the arrow keys (WCAG 2.1.1). When `children` supplies pre-highlighted markup, the region gives up its own tab stop to your focusable `<pre>` (Shiki's carries `tabindex=\"0\"`) rather than creating a second one.\n\nThe language tag is plain visible text with no interactive role. It is never the region's accessible name, so nothing is announced twice. The line-number gutter is `aria-hidden` and left out of selection, so a screen-reader user and a mouse copy both get clean source.\n\nIf you supply a highlighter's markup through `children`, its token colors (a Shiki theme, say) are yours to check for contrast. CodeBlock adds no color to that markup."},{"name":"Divider","group":"Content","route":"/docs/components/divider","description":"A thematic separator: a native hr when bare, and a labeled role=separator element when it wraps a centered text label.","importLine":"import { Divider } from \"@hyzer-labs/ui\"","props":[{"name":"children","type":"Snippet","default":"— (label)","note":"Optional. Absent → bare <hr>; present → the labeled form."},{"name":"variant","type":"'solid' | 'dashed' | 'dotted'","default":"'solid'"},{"name":"spacing","type":"LayoutPadding","default":"'md'","note":"Block margin: 'none' | 'sm' | 'md' | 'lg' | 'near' | 'away' — the shared layout scale; near/away tighten inside data-density-shift regions."},{"name":"lineWidth","type":"'thin' | 'thick'","default":"'thin'","note":"Maps 1:1 to the --hz-border-width-thin/-thick tokens."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-divider class."}],"types":[],"hooks":{"root":"hz-divider","attrs":[{"name":"data-variant","values":"'solid' | 'dashed' | 'dotted'","note":"Line style. Default solid."},{"name":"data-spacing","values":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","note":"Block margin from the space scale. near and away are the density distances, so dividers respond to a density shift like the layout family. Default md."},{"name":"data-line-width","values":"'thin' | 'thick'","note":"1:1 with --hz-border-width-thin/-thick."},{"name":"data-labeled","values":"present on the labeled form","note":"The labeled form is a div with rules; the plain form is a bare hr. The theme tells them apart by element (hr.hz-divider vs div.hz-divider), so match that if you restyle."}],"parts":[{"name":".hz-divider-label","values":"child element","note":"The label. Its flanking lines are ::before/::after, so they are not separately targetable."}]},"a11yNote":"The bare form is a native `<hr>`: implicit `role=\"separator\"`, horizontal, non-focusable, and needing no ARIA.\n\nThe labeled form keeps `role=\"separator\"` on the wrapping `<div>`, so the thematic break survives the switch away from `<hr>`. The label text is the separator's accessible name and stays in the tree, so screen readers announce a separator followed by its label, though the exact wording varies by reader.\n\nThe flanking rules are decorative `::before`/`::after` pseudo-elements, never part of the accessibility tree."},{"name":"Hero","group":"Content","route":"/docs/components/hero","description":"A section component for page heroes supporting center, split, and overlay layouts. Text slots accept plain strings or snippets; in the overlay layout, media becomes the full-bleed background.","importLine":"import { Hero } from \"@hyzer-labs/ui\"","props":[{"name":"layout","type":"'center' | 'split' | 'overlay'","default":"'center'"},{"name":"height","type":"'auto' | 'screen' | 'half'","default":"'auto'"},{"name":"align","type":"'start' | 'center' | 'end'","default":"'center'"},{"name":"reverseOnMobile","type":"boolean","default":"false","note":"Split layout only: media renders above content while the split is stacked."},{"name":"headingLevel","type":"1 | 2 | 3 | 4 | 5 | 6","default":"1"},{"name":"ariaLabel","type":"string","default":"—","note":"Accessible name when no title is provided."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-hero class."},{"name":"eyebrow","type":"string | Snippet","default":"—","note":"String for plain text; snippet for inner markup."},{"name":"title","type":"string | Snippet","default":"—"},{"name":"subtitle","type":"string | Snippet","default":"—"},{"name":"actions","type":"Snippet","default":"—"},{"name":"media","type":"Snippet","default":"—","note":"Beside/below content in center/split; becomes the background in overlay."}],"types":[],"hooks":{"root":"hz-hero","attrs":[{"name":"data-layout","values":"'center' | 'split' | 'overlay'","note":"In overlay the media becomes the background. split composes Split underneath, so .hz-split-layout is in reach. Default center."},{"name":"data-height","values":"'auto' | 'screen' | 'half'","note":"Uses dvh where supported. Default auto."},{"name":"data-align","values":"'start' | 'center' | 'end'","note":"Default center."},{"name":"data-reverse-on-mobile","values":"present when reversed","note":"Split layout only: wraps the media above the content on narrow screens."}],"parts":[{"name":".hz-hero-background","values":"child element","note":"The full-bleed media, overlay layout only."},{"name":".hz-hero-content","values":"child element","note":"The text column."},{"name":".hz-hero-eyebrow","values":"child element","note":"The eyebrow line."},{"name":".hz-hero-title","values":"child element","note":"The heading, at your headingLevel."},{"name":".hz-hero-subtitle","values":"child element","note":"The subtitle, capped at 60ch."},{"name":".hz-hero-actions","values":"child element","note":"The actions row."},{"name":".hz-hero-media","values":"child element","note":"The media wrapper in center and split layouts."}]},"a11yNote":"Hero renders a `<section>` with `aria-labelledby` pointing at the title element, or `aria-label` when there is no title. Use `headingLevel` to keep your page's heading hierarchy correct. If the page already has an h1, hero titles inside it are usually level 2."},{"name":"Icons","group":"Content","route":"/docs/components/icons","description":"The full Lucide icon set (ISC) ships as generated per-icon Svelte components, loaded one glyph at a time. Each icon is decorative by default and labeled when you pass ariaLabel.","importLine":"import IconSearch from \"@hyzer-labs/ui/icons/search\"","props":[{"name":"size","type":"number","default":"24","note":"Width and height in px."},{"name":"strokeWidth","type":"number","default":"2"},{"name":"intent","type":"Intent","default":"—","note":"Colors the glyph from the shared intent vocabulary.","noteHref":"/docs/foundation/colors#intent"},{"name":"ariaLabel","type":"string","default":"—","note":"Absent or empty means decorative, so the icon gets aria-hidden=\"true\"."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-icon class."}],"types":[],"hooks":{"root":"hz-icon","attrs":[{"name":"data-intent","values":"any registered intent","note":"Present only when the intent prop is set; the glyph inherits currentColor otherwise."}]}},{"name":"Link","group":"Content","route":"/docs/components/link","description":"An accessible anchor component with variant styles, external link support, and icon slots. Links inherit the surrounding text size.","importLine":"import { Link } from \"@hyzer-labs/ui\"","props":[{"name":"href","type":"string","default":"—","note":"Required."},{"name":"external","type":"boolean","default":"false"},{"name":"externalIcon","type":"boolean","default":"true","note":"Auto external glyph; iconEnd replaces it, false suppresses it."},{"name":"variant","type":"'default' | 'subtle' | 'nav'","default":"'default'"},{"name":"ariaCurrent","type":"'page' | 'step' | 'true'","default":"—"},{"name":"ariaLabel","type":"string","default":"—"},{"name":"onclick","type":"(e: MouseEvent) => void","default":"—"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-link class."},{"name":"children","type":"Snippet","default":"—"},{"name":"iconStart","type":"Snippet","default":"—"},{"name":"iconEnd","type":"Snippet","default":"—"}],"types":[],"hooks":{"root":"hz-link","attrs":[{"name":"data-variant","values":"'default' | 'subtle' | 'nav'","note":"Its own union, not the shared Variant. subtle inherits colour; nav is quiet until hover or aria-current. No size hook by design — a link inherits its surrounding text size."},{"name":"[aria-current]","values":"'page' | 'step' | 'true'","note":"Load-bearing for styling, not just semantics: the active nav-link rule is [data-variant=\"nav\"][aria-current]."},{"name":"data-external","values":"present on external links","note":"Accompanies target=\"_blank\" and the visually-hidden \"opens in new tab\" hint. The reference theme styles the glyph instead, so this one is yours."}],"parts":[{"name":".hz-link-external-icon","values":"child element","note":"The auto-rendered external glyph. Absent when you supply your own iconEnd."}]},"a11yNote":"External links automatically add `target=\"_blank\"`, `rel=\"noopener noreferrer\"`, a decorative external glyph, and a visually-hidden \"(opens in new tab)\" string. `ariaCurrent` sets `aria-current` on the anchor for nav links."},{"name":"Metatags","group":"Content","route":"/docs/components/metatags","description":"The head tags one page needs for search results and link previews: title, description, canonical, Open Graph, and an X card. Site-level values are props too, so a small wrapper of your own holds them while each page passes only what changes.","importLine":"import { Metatags } from \"@hyzer-labs/ui\"","props":[{"name":"siteUrl","type":"string","default":"—","note":"Absolute site origin, such as \"https://example.com\". Any relative url, canonical, or image needs it to resolve to the absolute URL Open Graph requires."},{"name":"url","type":"string","default":"—","note":"This page: a root-relative path (\"/pricing\") or an absolute URL. In SvelteKit this is page.url.pathname, passed by your own wrapper — see the \"Per-site wrapper\" demo above."},{"name":"title","type":"string","default":"—","note":"The page title. Composed with siteName into one string shared by <title>, og:title, and twitter:title."},{"name":"siteName","type":"string","default":"—","note":"The site's name. Emits og:site_name and is the title suffix."},{"name":"titleSeparator","type":"string","default":"' | '","note":"Sits between title and siteName."},{"name":"description","type":"string","default":"—","note":"Sets the description meta tag plus og:description and twitter:description. An empty string counts as unset."},{"name":"image","type":"string","default":"—","note":"Preview image, a path or an absolute URL, resolved the same way as url."},{"name":"imageAlt","type":"string","default":"—","note":"Alt text for the preview image. Setting image without it prints a console warning in development."},{"name":"type","type":"string","default":"'website'","note":"Sets og:type, emitted as given with no validation. The Open Graph protocol's type list is the reference; 'article' is the other common one."},{"name":"canonical","type":"string","default":"url","note":"Canonical override for a page reachable at more than one address, path or absolute."},{"name":"twitterCard","type":"'summary' | 'summary_large_image'","default":"derived from image","note":"summary_large_image once an image resolves, summary otherwise."},{"name":"children","type":"Snippet","default":"—","note":"Rendered last, inside the same head block — for tags this component does not manage (twitter:site, robots, JSON-LD, and the rest), not for overriding the ones it does."}],"types":[],"a11yNote":"This component has no ARIA, no roles, and no focus behavior. Nothing here is interactive, but two accessibility duties still apply. `<title>` is the page's accessible name, both in the browser tab and in a screen reader's window list, so this component never emits an empty or invented one: give it a `title`, a `siteName`, or both. `og:image:alt` and `twitter:image:alt` give the preview image a text alternative in every client that shows one, so setting `image` without `imageAlt` prints a development-only warning.\n\nSet a page's title once, either through this component or through the page's own `<svelte:head>`, never both."},{"name":"Table","group":"Content","route":"/docs/components/table","description":"A data table with client sorting, row selection, a sticky header, built-in empty and loading states, and an opt-in stacked mode for narrow widths. Real <table> semantics throughout.","importLine":"import { Table } from \"@hyzer-labs/ui\"","props":[{"name":"items","type":"T[]","default":"—","note":"Required."},{"name":"columns","type":"TableColumn<T>[]","default":"—","note":"Required. Each column's `key` is a property name on T — the default cell reads `row[key]`. See TableColumn below."},{"name":"caption","type":"string | Snippet","default":"—","note":"Visible caption. `caption` or `ariaLabel` is required — logs a warning in development if neither is set. Caption wins if both are given (no aria-label)."},{"name":"ariaLabel","type":"string","default":"—","note":"See `caption`."},{"name":"sort","type":"TableSort | null","default":"null","note":"Bindable. See TableSort below."},{"name":"clientSort","type":"boolean","default":"true","note":"false: Table renders `items` as given and only reports/marks sort state — you reorder `items` yourself."},{"name":"selectable","type":"boolean","default":"false","note":"Prepends a checkbox column."},{"name":"selected","type":"SvelteSet<string>","default":"new SvelteSet()","note":"Bindable — row ids (via getRowId) currently selected."},{"name":"getRowId","type":"(row: T, index: number) => string","default":"stringified index","note":"Row identity for selection. Supply a real id so selection survives re-sorting."},{"name":"rowLabel","type":"(row: T) => string","default":"—","note":"Accessible name for a row's checkbox; falls back to the first column's cell text."},{"name":"stickyHeader","type":"boolean","default":"false"},{"name":"bordered","type":"boolean","default":"true","note":"Frame and row rules. Set false inside a card or panel that already draws the edges; the divider under the header stays either way."},{"name":"loading","type":"boolean","default":"false"},{"name":"loadingRows","type":"number","default":"3"},{"name":"stack","type":"'sm' | 'md' | 'lg'","default":"—","note":"Stacks below the named width. Off by default (scroll wrap only); 'sm' (640px) suits most tables — reserve md/lg for wide or many-column tables that need to shed the table layout earlier."},{"name":"cell","type":"Snippet<[T, TableColumn<T>]>","default":"—","note":"Default is `String(row[column.key])`."},{"name":"empty","type":"Snippet","default":"—","note":"Default is \"No rows\"."},{"name":"class","type":"string","default":"—","note":"Merged after hz-table-wrap."}],"types":[{"name":"TableColumn<T>","props":[{"name":"key","type":"string","default":"—","note":"Required. A property name on a row of T (the default cell renders `row[key]`) and the column id (sort target, stacked-mode data-label)."},{"name":"header","type":"string","default":"—","note":"Required. Header cell text (also the stacked-mode label)."},{"name":"sortable","type":"boolean","default":"false"},{"name":"sortBy","type":"(row: T) => string | number","default":"row[key]","note":"The escape hatch for mixed-type or computed sort values."},{"name":"align","type":"'start' | 'center' | 'end'","default":"'start'","note":"Logical — flips under RTL."},{"name":"width","type":"string","default":"—","note":"CSS width for the column's <col>."}]},{"name":"TableSort","props":[{"name":"key","type":"string","default":"—"},{"name":"direction","type":"'asc' | 'desc'","default":"—"}]}],"hooks":{"root":"hz-table hz-table-wrap","attrs":[{"name":"data-bordered","values":"present unless bordered={false}","note":"On .hz-table-wrap. The reference theme keys the frame and the row rules off it, so `bordered={false}` leaves an edgeless table for a card or panel that draws its own edges. The heavier divider under the header stays either way, since a borderless table still needs its header separated from its body."},{"name":"data-sticky","values":"present when stickyHeader","note":"On .hz-table-wrap. Pins thead against the wrap’s own scroll — cap the wrap’s max-height (e.g. via your own class through Table’s `class` prop, which lands on the wrap) to see it scroll."},{"name":"data-stack","values":"'sm' | 'md' | 'lg'","note":"On .hz-table-wrap. Mirrors the stack prop — stacked below the named --hz-width-* threshold, a real table at/above it. Absent (default): never stacks. 'sm' (640px) is the recommended default — genuinely narrow viewports only."},{"name":"data-selected","values":"present on a selected row","note":"On tbody tr. Pairs with aria-selected=\"true\" — both present only when selected."},{"name":"data-align","values":"'start' | 'center' | 'end'","note":"On th/td, mirroring a column’s align. Logical values, so start/end flip under RTL with no extra rule."},{"name":"aria-sort","values":"'ascending' | 'descending'","note":"Not a data-* hook, but load-bearing for styling the active-column indicator: present only on the sorted column’s th, never on the others."}],"parts":[{"name":".hz-table-sort","values":"child element","note":"The sort trigger — a real button wrapping a sortable column’s header text, with the active-column chevron alongside it."},{"name":".hz-table-empty","values":"child element","note":"The full-width cell rendered when items is empty and not loading."},{"name":".hz-table-skeleton","values":"child element","note":"A loading placeholder row — on tbody tr; aria-hidden, so it never reaches assistive tech."},{"name":".hz-table--striped","values":"opt-in class","note":"Zebra striping. Pass it via class — like Card’s treatment classes, this is a theme look, not a prop."}]},"a11yNote":"This is a static data table, not an APG grid pattern. Sort buttons and checkboxes are ordinary native controls in the natural tab order, so there is no roving grid navigation.\n\n`aria-sort` (`ascending`/`descending`) sits only on the sorted column's `th`, never on the others. Select-all announces the partial state through the native `indeterminate` property, not an ARIA attribute.\n\nLoading skeleton rows carry `aria-hidden`, so they never reach assistive tech.\n\nReal `role`/`scope` semantics are stamped explicitly (`table`/`row`/`columnheader`/`cell`, plus `scope`), so they survive the stacked mode's CSS display overrides."},{"name":"Tabs","group":"Content","route":"/docs/components/tabs","description":"An accessible tab interface with roving tabindex, arrow-key navigation, and horizontal or vertical orientation. Tab labels accept plain strings or snippets.","importLine":"import { Tabs } from \"@hyzer-labs/ui\"","props":[{"name":"items","type":"TabItem[]","default":"—","note":"Required. See TabItem below."},{"name":"ariaLabel","type":"string","default":"—","note":"Required. Labels the tablist."},{"name":"defaultTab","type":"string","default":"—","note":"ID of the initially active tab."},{"name":"orientation","type":"'horizontal' | 'vertical'","default":"'horizontal'"},{"name":"activation","type":"'auto' | 'manual'","default":"'auto'","note":"auto activates on arrow-key focus; manual waits for Enter/Space."},{"name":"panel","type":"Snippet<[TabItem]>","default":"—","note":"Required. Renders each panel."},{"name":"onChange","type":"(activeId: string) => void","default":"—"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-tabs class."}],"types":[{"name":"TabItem","props":[{"name":"id","type":"string","default":"—","note":"Required. Must be unique."},{"name":"label","type":"string | Snippet","default":"—","note":"Required. String for plain text; snippet for inner markup."},{"name":"disabled","type":"boolean","default":"false"}]}],"hooks":{"root":"hz-tabs","attrs":[{"name":"data-orientation","values":"'horizontal' | 'vertical'","note":"Vertical moves the list border and the active indicator to the inline edge. Default horizontal."},{"name":"data-state","values":"'active' | 'inactive'","note":"On .hz-tabs-trigger. The panel carries it too, but its visibility runs off the native hidden attribute."},{"name":"data-disabled","values":"present on disabled tabs","note":"On .hz-tabs-trigger. Disabled tabs stay focusable (aria-disabled, not the native attribute)."}],"parts":[{"name":".hz-tabs-list","values":"child element","note":"The tablist. Scrolls horizontally."},{"name":".hz-tabs-trigger","values":"child element","note":"One tab. The theme owns the whole button reset here, so an unthemed Tabs renders native buttons. The active indicator is an inset box-shadow, deliberately: the list scrolls, and any overlap trick that adds layout size produces a phantom scrollbar. Keep that property if you restyle it."},{"name":".hz-tabs-panel","values":"child element","note":"One panel."}]},"a11yNote":"Implements the WAI-ARIA tabs pattern: `role='tablist'/'tab'/'tabpanel'`, `aria-selected`, and roving `tabindex`. Tab is one stop for the whole tablist, and arrow keys move between triggers, with Home/End jumping to the first/last.\n\nPanels are only their own tab stop when they contain no focusable content. `ariaLabel` is required to name the tablist.\n\nDisabled tabs stay focusable via arrows but carry `aria-disabled='true'` and never activate."},{"name":"Alert","group":"Feedback & Status","route":"/docs/components/alert","description":"An inline feedback banner on the shared intent scale, with an optional heading and dismiss button. Announcement semantics are opt-in, and the Form error summary is built from one.","importLine":"import { Alert } from \"@hyzer-labs/ui\"","props":[{"name":"children","type":"Snippet","default":"—","note":"Required. The alert body."},{"name":"title","type":"string | Snippet","default":"—","note":"Optional heading; labels the alert via aria-labelledby."},{"name":"headingLevel","type":"2 | 3 | 4 | 5 | 6","default":"3","note":"An alert is nearly always a callout inside a section rather than a section of its own, so the default keeps it below your page headings. Raise or lower it to match the surrounding document."},{"name":"intent","type":"Intent","default":"'neutral'","note":"See Foundation → Colors & Intent.","noteHref":"/docs/foundation/colors#intent"},{"name":"rounded","type":"'none' | 'sm' | 'md' | 'lg' | 'full'","default":"'md'","note":"The shared Rounded scale — 1:1 with the --hz-radius-* tokens."},{"name":"icon","type":"Snippet","default":"—","note":"Decorative; rendered aria-hidden."},{"name":"onDismiss","type":"() => void","default":"—","note":"Renders the dismiss button. Visibility is your state — the Alert never hides itself."},{"name":"dismissLabel","type":"string","default":"'Dismiss'"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-alert class."}],"types":[],"hooks":{"root":"hz-alert","attrs":[{"name":"data-intent","values":"'neutral' | any registered intent","note":"Drives the accent bar, tint, and title colour. Spans the intent registry, so an intent you register lands here too."},{"name":"data-rounded","values":"'none' | 'sm' | 'md' | 'lg' | 'full'","note":"1:1 with the --hz-radius-* scale. Default md."},{"name":"data-dismissible","values":"present when dismissible","note":"Marks the dismissible form. Always stamped so you can target it, though the reference theme ships no rule for it."}],"props":[{"name":"--hz-alert-tint","values":"<percentage> — 10% light, 22% dark","note":"How strongly the intent colour mixes into the surface. Retuning it means re-checking the soft pairings on Contrast & Accessibility."},{"name":"--hz-alert-border-width","values":"<length> — default var(--hz-border-width-heavy)","note":"Thickness of the inline-start accent bar. Defaults to the heaviest border-width token; override with any length or a lighter token."}],"parts":[{"name":".hz-alert-icon","values":"child element","note":"The intent icon."},{"name":".hz-alert-body","values":"child element","note":"Title and content wrapper."},{"name":".hz-alert-title","values":"child element","note":"The heading, at your headingLevel."},{"name":".hz-alert-content","values":"child element","note":"The message body."},{"name":".hz-alert-dismiss","values":"child element","note":"The dismiss button."}]},"a11yNote":"A statically rendered Alert is plain content: no role, no live region. The optional `title` names it via `aria-labelledby`.\n\nFor alerts inserted after load, pass `role=\"status\"` (polite) or `role=\"alert\"` (assertive, use sparingly) via the rest props. A live role on static content is dead weight, so it's never a default.\n\nThe dismiss button is a real labeled `<button>`. Dismissal is your state change, so consider where focus should land.\n\nThere is deliberately no Toast component. A timed self-dismissing overlay is hard to make accessible. Under WCAG 2.2.1 the timing has to be adjustable, extendable, or pausable. A message that vanishes on its own often goes unannounced as well. The library prefers dismissal the reader chooses, so content never disappears out from under them. An inline Alert with `role=\"status\"` covers the need accessibly."},{"name":"Badge","group":"Feedback & Status","route":"/docs/components/badge","description":"A small inline status chip with intent coloring, soft, solid, and outline variants, the shared rounded scale, and an optional dismiss button. It is the building block for selected-option chips.","importLine":"import { Badge } from \"@hyzer-labs/ui\"","props":[{"name":"children","type":"Snippet","default":"—","note":"Required. The badge content."},{"name":"intent","type":"Intent","default":"'neutral'","note":"See Foundation → Colors & Intent.","noteHref":"/docs/foundation/colors#intent"},{"name":"variant","type":"'soft' | 'solid' | 'outline'","default":"'soft'"},{"name":"size","type":"'sm' | 'md'","default":"'md'"},{"name":"rounded","type":"'none' | 'sm' | 'md' | 'lg' | 'full'","default":"'full'","note":"The shared Rounded scale — 1:1 with the --hz-radius-* tokens."},{"name":"onDismiss","type":"() => void","default":"—","note":"Renders the trailing remove button (the chip form)."},{"name":"dismissLabel","type":"string","default":"'Remove'","note":"In lists, pass a per-item label — \"Remove\" alone is ambiguous."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-badge class."}],"types":[],"hooks":{"root":"hz-badge","attrs":[{"name":"data-intent","values":"'neutral' | any registered intent","note":"Spans the intent registry."},{"name":"data-variant","values":"'soft' | 'solid' | 'outline'","note":"Default soft."},{"name":"data-size","values":"'sm' | 'md'","note":"Two rungs only, unlike Button. Default md."},{"name":"data-rounded","values":"'none' | 'sm' | 'md' | 'lg' | 'full'","note":"Default full — the pill."},{"name":"data-dismissible","values":"present when dismissible","note":"Marks the chip form. Always stamped so you can target it."}],"props":[{"name":"--hz-badge-tint","values":"<percentage> — 14% light, 28% dark","note":"Soft-variant background tint strength."}],"parts":[{"name":".hz-badge-dismiss","values":"child element","note":"The remove button."}]},"a11yNote":"A badge is plain inline text with no role and no label, so it announces as part of the surrounding content. Never let the intent color be the only signal; the text itself carries the meaning.\n\nThe dismiss button is a real `<button>` named by `dismissLabel`, with a decorative icon. When rendering several chips, give each a per-item label like `Remove Destroyer`."},{"name":"Banner","group":"Feedback & Status","route":"/docs/components/banner","description":"A full-width, solid-intent announcement bar with an optional dismiss button and top or bottom pinning. Made for page-level messages: maintenance notices, promos, and outage warnings.","importLine":"import { Banner } from \"@hyzer-labs/ui\"","props":[{"name":"children","type":"Snippet","default":"—","note":"Required. The banner body."},{"name":"intent","type":"Intent","default":"'neutral'","note":"See Foundation → Colors & Intent.","noteHref":"/docs/foundation/colors#intent"},{"name":"pin","type":"'top' | 'bottom'","default":"—","note":"Sticks the bar to that edge, in flow (position: sticky). Static by default."},{"name":"icon","type":"Snippet","default":"—","note":"Decorative; rendered aria-hidden."},{"name":"actions","type":"Snippet","default":"—","note":"Trailing slot — a Link or Button, e.g. \"Learn more\"."},{"name":"onDismiss","type":"() => void","default":"—","note":"Renders the dismiss button. Visibility is your state — the Banner never hides itself."},{"name":"dismissLabel","type":"string","default":"'Dismiss'"},{"name":"as","type":"string","default":"'div'","note":"Polymorphic root — e.g. section or aside for landmark semantics."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-banner class."}],"types":[],"hooks":{"root":"hz-banner","attrs":[{"name":"data-intent","values":"'neutral' | any registered intent","note":"Drives the solid fill via --hz-banner-bg; spans the intent registry."},{"name":"data-pin","values":"'top' | 'bottom'","note":"Present only when pinned; sticks the bar to that edge via position: sticky."},{"name":"data-dismissible","values":"present when dismissible","note":"Present only when onDismiss is set — target it to style the dismissible form."}],"props":[{"name":"--hz-banner-bg","values":"<color> — default var(--hz-intent-neutral)","note":"The solid fill, switched per intent — override to restyle every intent's bar."},{"name":"--hz-banner-fg","values":"<color> — default var(--hz-color-surface)","note":"On-fill text colour, the surface role so it flips with the mode and keeps text ≥ 4.5:1 in both."},{"name":"--hz-banner-padding-block","values":"<length> — default 1.5rem","note":"Vertical padding of the bar."},{"name":"--hz-banner-padding-inline","values":"<length> — default 2.5rem","note":"Horizontal padding of the bar."}],"parts":[{"name":".hz-banner-icon","values":"child element","note":"The decorative leading icon."},{"name":".hz-banner-content","values":"child element","note":"The message body."},{"name":".hz-banner-actions","values":"child element","note":"The trailing actions slot."},{"name":".hz-banner-dismiss","values":"child element","note":"The dismiss button."},{"name":".hz-banner-title","values":"opt-in class","note":"Banner never emits this — put it on your own lead element (strong, a heading) and it goes block-level semibold, stacking over the body copy. The .hz-card-title convention."}]},"a11yNote":"A statically rendered Banner is plain content: no role, no live region. For a Banner inserted after load, pass `role=\"status\"` (polite) or `role=\"alert\"` (assertive, use sparingly) via the rest props.\n\nThe icon slot is decorative (`aria-hidden`). Links inside are underlined, and color is never the only signal. The dismiss button is a real labeled `<button>`, and dismissal is your state change.\n\nA pinned Banner can cover a focused element that scrolls under it. Keep pinned banners short, one line where possible. Give in-page anchor targets `scroll-margin-block-start`/`-end` equal to the banner height so they clear it. The Banner itself sets no `scroll-margin`.\n\nThere is deliberately no Toast component. A timed self-dismissing overlay is hard to make accessible. Under WCAG 2.2.1 the timing has to be adjustable, extendable, or pausable. A message that vanishes on its own often goes unannounced as well. The library prefers dismissal the reader chooses, so content never disappears out from under them. Banner is the supported pattern for a pinned or site-wide message instead, with opt-in `role=\"status\"` and dismissal you own."},{"name":"Loading","group":"Feedback & Status","route":"/docs/components/loading","description":"An accessible loading indicator in four variants: spinner, ring, dots, and bar. All four are indeterminate on their own; pass a value and bar becomes a linear progress bar while ring becomes a circular arc with a centered readout, and spinner and dots stay indeterminate.","importLine":"import { Loading } from \"@hyzer-labs/ui\"","props":[{"name":"value","type":"number","default":"— (indeterminate)","note":"Omitted, undefined, or NaN ⇒ indeterminate. There is no separate `indeterminate` boolean: the mode follows `value`, the same way a native `<progress>` does."},{"name":"max","type":"number","default":"100"},{"name":"intent","type":"Intent","default":"'primary'","note":"Defaults to primary, unlike Badge and Banner, since a fill reads as an accent. See Foundation → Colors & Intent.","noteHref":"/docs/foundation/colors#intent"},{"name":"size","type":"'sm' | 'md' | 'lg'","default":"'md'"},{"name":"variant","type":"'bar' | 'spinner' | 'ring' | 'dots'","default":"'bar'","note":"A value on `bar` renders a determinate linear progress bar; a value on `ring` renders a determinate circular arc with a centered readout. `spinner` and `dots` are indeterminate only, so a value passed to either is ignored (with a warning in development). With no value, `ring` rotates continuously while its arc grows and shrinks."},{"name":"label","type":"string","default":"—","note":"The accessible name. Required: a bare progressbar has a role but no name, and a missing name logs a warning in development. You can pass aria-label or aria-labelledby through rest instead."},{"name":"showValue","type":"boolean","default":"false","note":"Renders a formatted readout beside the bar, or centered inside a determinate ring. Determinate only: ignored (with a warning in development) on any indeterminate presentation (including the indeterminate ring) and on spinner/dots. At sm/md the centered ring readout gets tight, so prefer lg or a larger --hz-loading-size."},{"name":"format","type":"(value: number, max: number) => string","default":"(v, max) => `${Math.round((v / max) * 100)}%`","note":"Feeds both the visible readout and aria-valuetext, so what you see and what is announced never differ. Applies to both the bar and the determinate ring."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-loading class."}],"types":[],"hooks":{"root":"hz-loading","attrs":[{"name":"data-intent","values":"'primary' | any registered intent","note":"Drives the fill via --hz-loading-fill for every variant/mode (bar, ring, spinner, dots). Spans the intent registry. Default primary — a fill is an accent by nature, unlike Badge/Banner’s neutral default."},{"name":"data-size","values":"'sm' | 'md' | 'lg'","note":"Bar thickness, spinner/ring diameter, and dot size. Default md."},{"name":"data-variant","values":"'bar' | 'spinner' | 'ring' | 'dots'","note":"Default bar. spinner and dots are indeterminate-only — a value passed to either is ignored (with a warning in development). ring is the sole home of circular progress: determinate with a value (a static arc), indeterminate without one (a rotating, arc-length-pulsing loader)."},{"name":"data-indeterminate","values":"present when there is no value","note":"Present whenever value is absent/NaN on bar or ring, and always for spinner and dots (both indeterminate-only by construction). :not([data-indeterminate]) selects a determinate form — the linear bar or the (value-bearing) circular ring."}],"props":[{"name":"--hz-loading-fill","values":"<color> — default var(--hz-intent-primary)","note":"The filled portion — the bar fill, the ring arc, the spinner stroke, and the dot color — switched per intent."},{"name":"--hz-loading-track","values":"<color>","note":"The unfilled track / unfilled ring."},{"name":"--hz-loading-size","values":"<length> — defaulted per data-size","note":"Bar thickness for the bar; spinner-glyph / ring diameter for the spinner and ring; dot diameter for dots — each defaulted per data-size."},{"name":"--hz-loading-ring-width","values":"<length> — default calc(var(--hz-loading-size) / 8)","note":"The ring’s stroke width (both the determinate static arc and the indeterminate rotating/pulsing arc), scaled off --hz-loading-size so it stays proportional to the ring diameter. Applied with vector-effect: non-scaling-stroke so it stays a true length under the SVG viewBox scale."},{"name":"--hz-loading-speed","values":"<time> — default calc(var(--hz-duration-base) * 6) ≈ 2.4s","note":"Base duration of the indeterminate animations: the spinner rotation, the dots cycle, and the indeterminate ring’s rotation + arc-length pulse all run at exactly this; the bar sweep runs at 1.6x it. Anchored to the --hz-duration-* scale rather than a bare token, since that scale is tuned for one-shot transitions, not continuous loops. Under reduced motion the effective duration is slowed (≈2x, ≈4.8s), never zeroed — indeterminate Loading keeps animating (the deliberate exception; contrast Skeleton). The determinate bar/ring are static, so this hook is moot for them."},{"name":"--hz-loading-ease","values":"<timing-function> — default var(--hz-ease-standard)","note":"Easing of the dots cycle and the indeterminate ring’s arc-length pulse — both reverse in place rather than travel, so an ease reads cleanly. The spinner spin, the ring’s rotation, and the bar sweep are always linear, independent of this hook — an eased rotation or looping sweep reads as stuttering (it slows to a stop and restarts each cycle)."},{"name":"--hz-loading-pulse-width","values":"<percentage> — default 150%","note":"Width of the indeterminate bar's moving highlight (its “pulse”) as a percentage of the bar's own width. Wider reads softer and more ambient — the peak stays a point at the band center, so a wide value fades gently over a long distance. Values up to ~200% keep the loop seamless. Affects the indeterminate bar only (unrelated to the ring's own arc-length pulse)."}],"parts":[{"name":".hz-loading-bar","values":"the native <progress>","note":"The bar variant’s real progressbar."},{"name":".hz-loading-value","values":"child element","note":"The formatted readout, present only with showValue on a determinate form (the bar, inline; the ring, centered). Never present on the indeterminate ring."},{"name":".hz-loading-spinner","values":"child element","note":"The spinner-variant wrapper — role=\"progressbar\" aria-busy=\"true\". Wraps the indeterminate IconLoader glyph. spinner is indeterminate-only; see ring for determinate circular progress."},{"name":".hz-loading-ring-wrapper","values":"child element","note":"The ring-variant wrapper — role=\"progressbar\". Carries aria-busy=\"true\" around the indeterminate rotating/pulsing arc, or aria-valuemin/max/now (no aria-busy) around the determinate static arc."},{"name":".hz-loading-ring","values":"child element","note":"The ring’s SVG — a static arc when determinate, a continuously rotating arc-length-pulsing loop when indeterminate. role=\"img\" aria-hidden=\"true\" (the wrapper carries the ARIA)."},{"name":".hz-loading-ring-track","values":"child element","note":"The ring’s unfilled circle."},{"name":".hz-loading-ring-fill","values":"child element","note":"The ring’s arc circle. stroke-dasharray is a theme hook; for a determinate ring the live stroke-dashoffset fraction is written inline by the component (the Slider-fill precedent), not a documented hook. An indeterminate ring instead animates this circle’s rotation and dash length entirely in the theme."},{"name":".hz-loading-dots","values":"child element","note":"The ellipsis-loader wrapper — role=\"progressbar\" aria-busy=\"true\". Adds no new custom-property hooks: reuses --hz-loading-fill, --hz-loading-size, --hz-loading-speed, and --hz-loading-ease."},{"name":".hz-loading-dot","values":"child element","note":"One of the dots’ three spans."}]},"a11yNote":"A determinate bar (what a `value` gives you) is a native `<progress>`. It carries `role=\"progressbar\"` plus `aria-valuenow`, `aria-valuemin`, and `aria-valuemax` for free, so the component stamps none of them by hand. A determinate ring (`variant=\"ring\"` with a `value`) renders a static circular arc instead. That arc is a decorative SVG, so the same ARIA (`role=\"progressbar\"`, `aria-valuemin`/`aria-valuemax`/`aria-valuenow`) is stamped on its wrapping span. Neither determinate form sets `aria-busy`, because the progress is known.\n\nThe indeterminate presentations are a bare bar with no `value`, the spinning glyph, an indeterminate ring, and the dots. None expose `aria-valuenow`, since the progress is unknown; each wraps in `role=\"progressbar\" aria-busy=\"true\"` instead. The glyph, the ring SVG, and the dots are all `aria-hidden` decoration, so the accessible name carries the meaning.\n\nA name is required: pass `label` (applied as `aria-label` on the progressbar element), or an `aria-label`/`aria-labelledby` via rest. A missing name logs a warning in development, citing WCAG 4.1.2.\n\nFor non-percentage units, pass a custom `format`. Its output becomes `aria-valuetext` as well as the visible readout (bar or determinate ring), so what a sighted user reads and what a screen reader announces always match.\n\nReduced motion is a deliberate exception here. The rest of the library reduces animation to zero under `prefers-reduced-motion: reduce`. An indeterminate Loading keeps animating, because a frozen loader reads as stalled or broken, which is worse for every user. It runs a calmer version at roughly half speed instead (about 4.8s per cycle at the default speed): the spinner keeps spinning, the dots keep cycling, and the indeterminate ring keeps rotating and pulsing, just more slowly. The bar softens from a traveling sweep to a gentle low-amplitude pulse. The determinate forms, the linear bar and the circular ring whenever a `value` is present, are static either way, so reduced motion changes nothing for them. Skeleton is the opposite of the indeterminate case: its shimmer/pulse goes fully still under reduced motion, because the placeholder shape is itself the \"loading\" cue and needs no motion."},{"name":"Skeleton","group":"Feedback & Status","route":"/docs/components/skeleton","description":"A decorative placeholder for content that has not loaded yet. Its shape variants (text lines, circle, rectangle, and fill-the-box) combine into any card-like placeholder, with free width, height, and radius overrides and a shimmer animation that goes still under reduced motion.","importLine":"import { Skeleton } from \"@hyzer-labs/ui\"","props":[{"name":"variant","type":"'text' | 'circle' | 'rect' | 'block'","default":"'text'","note":"Picks a shape and its default dimensions. For a card-like placeholder, compose several Skeletons in your own Stack/Cluster/Card layout; there is no preset prop for it."},{"name":"width","type":"string | number","default":"variant default","note":"A number is emitted as px; a string is used as written (any CSS length or percentage). This is a per-instance style rather than a theme hook, like Grid's --hz-grid-cols*."},{"name":"height","type":"string | number","default":"variant default","note":"See width."},{"name":"rounded","type":"'none' | 'sm' | 'md' | 'lg' | 'full'","default":"variant default","note":"The shared Rounded scale. circle always forces full, whatever you pass here."},{"name":"lines","type":"number","default":"1","note":"text only. > 1 renders that many stacked bars; a value ≤ 0 clamps to 1 (with a warning in development). Ignored on other variants."},{"name":"lastLineWidth","type":"string | number","default":"'60%'","note":"text only, lines > 1. The final line is shortened so the block reads like a real paragraph."},{"name":"animation","type":"'shimmer' | 'pulse' | 'none'","default":"'shimmer'","note":"Every mode collapses to a static muted block under prefers-reduced-motion: reduce."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-skeleton class."}],"types":[],"hooks":{"root":"hz-skeleton","attrs":[{"name":"data-variant","values":"'text' | 'circle' | 'rect' | 'block'","note":"Default text."},{"name":"data-animation","values":"'shimmer' | 'pulse' | 'none'","note":"Default shimmer. Collapses to a static block (same as none) under prefers-reduced-motion: reduce."},{"name":"data-rounded","values":"'none' | 'sm' | 'md' | 'lg' | 'full'","note":"1:1 with the --hz-radius-* scale. circle forces full regardless of the rounded prop."}],"props":[{"name":"--hz-skeleton-color","values":"<color>","note":"The base block color. Strengthened in dark (the Badge --hz-badge-tint precedent)."},{"name":"--hz-skeleton-highlight","values":"<color>","note":"The shimmer sweep color."},{"name":"--hz-skeleton-speed","values":"<time> — default 1.4s","note":"The shimmer/pulse animation cycle."}],"parts":[{"name":".hz-skeleton-line","values":"child element","note":"One bar in a multi-line text skeleton (variant=\"text\", lines > 1)."}]},"a11yNote":"Skeleton stamps `aria-hidden=\"true\"` by default. It is decorative scaffolding with no role, no live region, and no label, so it cannot announce loading on its own. In the rare case you want it exposed, you can override `aria-hidden` through the rest props.\n\nThat is the deliberate split: Loading announces, Skeleton decorates. Wrap the loading region in `aria-busy=\"true\"` and pair it with either a labeled indeterminate `Loading` or a polite `aria-live` \"Loading…\" message, so screen-reader users are told content is coming, then told when it arrives. See the paired loading pattern demo below.\n\nReduced motion works the other way round here from Loading, on purpose. Skeleton's shimmer and pulse go fully still under `prefers-reduced-motion: reduce`, because the placeholder shape already carries the \"content loading here\" cue, so no motion is needed. `Loading` (and Button's loading spinner) keep animating instead, much more slowly, because for them the motion is the cue."},{"name":"Dropdown","group":"Overlays","route":"/docs/components/dropdown","description":"A generic action menu built on the WAI-ARIA APG menu button pattern, with real roving-tabindex keyboard focus.","importLine":"import { Dropdown } from \"@hyzer-labs/ui\"","props":[{"name":"items","type":"DropdownEntry[]","default":"—","note":"Required. A DropdownEntry is either a DropdownItem or a DropdownSeparator, both below."},{"name":"label","type":"string","default":"—","note":"Visible trigger text (and the accessible name, unless triggerLabel overrides it)."},{"name":"triggerLabel","type":"string","default":"—","note":"Accessible-name override; required for an icon-only trigger (no label)."},{"name":"triggerProps","type":"DropdownTriggerProps","default":"{}","note":"Trigger appearance pass-through. Resolves to variant: 'outline', intent: 'neutral'."},{"name":"triggerIcon","type":"Snippet","default":"— (⇒ IconChevronDown)","note":"Decorative; the labeled trigger renders it trailing, the icon-only trigger renders it alone."},{"name":"align","type":"'start' | 'center' | 'end'","default":"'start'"},{"name":"onselect","type":"(id: string, item: DropdownItem) => void","default":"—","note":"Fires on every activation, after the item's own onselect."},{"name":"disabled","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-dropdown class."}],"types":[{"name":"DropdownItem","props":[{"name":"id","type":"string","default":"—","note":"Required. Stable identity — keys the item, its DOM id, and the onselect callback."},{"name":"label","type":"string","default":"—","note":"Required."},{"name":"disabled","type":"boolean","default":"—","note":"Stays focusable — reachable by Arrow / Home / End / typeahead — but inert on activation."},{"name":"danger","type":"boolean","default":"—","note":"Destructive action — surfaces the data-danger styling hook."},{"name":"icon","type":"Snippet","default":"—","note":"Decorative leading glyph; the label owns the accessible name."},{"name":"onselect","type":"() => void","default":"—","note":"Per-item action, fired before the component's onselect."}]},{"name":"DropdownSeparator","props":[{"name":"separator","type":"true","default":"—","note":"A non-interactive divider — discriminates it from an actionable item."}]}],"hooks":{"root":"hz-dropdown","attrs":[{"name":"data-open","values":"present while open","note":"Hides the closed menu and rotates the chevron."},{"name":"data-side","values":"'bottom' | 'top'","note":"On .hz-dropdown-menu — the resolved side, bottom unless flipped for lack of room below. See Positioning for the caret recipe."},{"name":"data-align","values":"'start' | 'center' | 'end'","note":"On .hz-dropdown-menu — which edge the menu hangs from (or centered), RTL-aware."},{"name":"data-danger","values":"present on danger items","note":"Per-item. The label text carries the meaning too — colour alone never does."},{"name":"data-disabled","values":"present on disabled items","note":"Per-item. Items stay focusable (aria-disabled, not the native attribute), so the disabled state is still discoverable."}],"parts":[{"name":".hz-dropdown-trigger","values":"on a Button","note":"Merged onto the composed Button, so the trigger carries .hz-button and its full data-attr set as well."},{"name":".hz-dropdown-menu","values":"child element","note":"The menu surface."},{"name":".hz-dropdown-item","values":"child element","note":"One item. There is no data-active — the active item is real DOM focus, so style :focus / :focus-visible."},{"name":".hz-dropdown-item-icon","values":"child element","note":"An item’s icon slot."},{"name":".hz-dropdown-separator","values":"child element","note":"A separator row."}]},"a11yNote":"The trigger is a real button (the library's own Button) with `aria-haspopup=menu`, `aria-expanded`, and `aria-controls`. It opens a `role=menu` popup of `role=menuitem` buttons, named by the trigger via `aria-labelledby`.\n\nUnlike `Combobox`, which keeps DOM focus on its text input and tracks a virtually-focused option with `aria-activedescendant`, Dropdown moves real DOM focus into the menu. Opening it lands focus on the first menuitem (the last, on `ArrowUp`). A roving `tabindex` (`0` on the focused item, `-1` on the rest) keeps the menu a single tab stop, so the active item is styled with native `:focus` rather than a `data-active` hook.\n\nKeyboard: `ArrowDown`/`ArrowUp` move focus and wrap, including over disabled items. `Home`/`End` jump to the first and last item. Typing a character cycles focus to the next item whose label starts with it. `Enter`/`Space` activate the focused item through its own native button click, with no separate handling. `Escape` closes the menu and returns focus to the trigger. `Tab`/`Shift+Tab` close the menu and let focus move on, since there is no focus trap.\n\nA disabled item keeps `aria-disabled=true` rather than the native `disabled` attribute, so it stays focusable and screen-reader users can discover it. Activating it does nothing."},{"name":"Modal","group":"Overlays","route":"/docs/components/modal","description":"An accessible dialog built on the native <dialog> element with focus trap, Esc-to-close, scroll lock, and configurable sizes.","importLine":"import { Modal } from \"@hyzer-labs/ui\"","props":[{"name":"title","type":"string","default":"—","note":"Required for accessibility."},{"name":"open","type":"boolean","default":"false","note":"$bindable."},{"name":"description","type":"string","default":"—"},{"name":"size","type":"'sm' | 'md' | 'lg' | 'full'","default":"'md'"},{"name":"closeOnOverlay","type":"boolean","default":"true","note":"Esc always closes regardless — the dialog pattern requires it."},{"name":"showClose","type":"boolean","default":"true"},{"name":"preventScroll","type":"boolean","default":"true"},{"name":"closeLabel","type":"string","default":"'Close dialog'"},{"name":"onclose","type":"() => void","default":"—"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-modal class."},{"name":"children","type":"Snippet","default":"—"},{"name":"actions","type":"Snippet","default":"—"}],"types":[],"hooks":{"root":"hz-modal","attrs":[{"name":"data-size","values":"'sm' | 'md' | 'lg' | 'full'","note":"full goes edge-to-edge and drops the radius. Default md."},{"name":"data-state","values":"'open' | 'closed'","note":"Mirrors the open prop. Visibility itself runs off the native [open] attribute that showModal() sets — this is the hook to animate against."},{"name":"data-modal-close","values":"present on the close button","note":"Load-bearing beyond styling: focus management queries it to keep the close button out of the first-focus candidates. Renaming it breaks focus order, not just looks."}],"props":[{"name":"--hz-modal-width","values":"<length> — 30/40/52rem by size","note":"One name across all three sizes: it overrides whichever size is active. data-size=\"full\" ignores it."}],"parts":[{"name":".hz-modal-header","values":"child element","note":"Always rendered."},{"name":".hz-modal-title","values":"child element","note":"Always an h2 — Modal has no headingLevel prop, because the dialog is its own document section."},{"name":".hz-modal-description","values":"child element","note":"The optional description."},{"name":".hz-modal-body","values":"child element","note":"Always rendered, so the scroll region stays stable even when empty."},{"name":".hz-modal-footer","values":"child element","note":"Present only with actions."},{"name":"::backdrop","values":"pseudo-element","note":"The native dialog backdrop."}]},"a11yNote":"Modal uses `showModal()` for native top-layer focus trapping and backdrop. `aria-modal`, `aria-labelledby`, and optional `aria-describedby` are set automatically. Focus returns to the trigger on close. The built-in close button's accessible name comes from `closeLabel`."},{"name":"Popover","group":"Overlays","route":"/docs/components/popover","description":"A click-triggered disclosure panel for rich or interactive content, such as a filter form, a settings menu, or extra detail. It is non-modal, with no focus trap and no backdrop: reach for Modal when you need a focus-trapped dialog, or Dropdown when you need a menu of actions.","importLine":"import { Popover } from \"@hyzer-labs/ui\"","props":[{"name":"open","type":"boolean","default":"false","note":"$bindable."},{"name":"placement","type":"'top' | 'bottom' | 'left' | 'right' | '<side>-start' | '<side>-end'","default":"'bottom-start'","note":"A bare side centers on the trigger; -start/-end add alignment. left/right follow the trigger's writing direction, so RTL flips the physical side. See Positioning for how a placement resolves."},{"name":"offset","type":"number","default":"8","note":"Gap from the trigger, in px."},{"name":"autoFocus","type":"boolean","default":"false","note":"true moves focus to the panel's first focusable element when it opens. Off by default, because a disclosure should not steal focus."},{"name":"dismissible","type":"boolean","default":"true","note":"false turns off closing on an outside click. Escape still closes the panel either way; there is no opt-out."},{"name":"label","type":"string","default":"—","note":"The panel's accessible name, used when it has no visible heading of its own."},{"name":"onopen","type":"() => void","default":"—"},{"name":"onclose","type":"() => void","default":"—"},{"name":"triggerLabel","type":"string","default":"—","note":"Visible label for the default composed-Button trigger. Ignored when `trigger` is provided."},{"name":"triggerProps","type":"PopoverTriggerProps","default":"{}","note":"Trigger appearance pass-through. Resolves to variant: 'outline', intent: 'neutral'."},{"name":"triggerIcon","type":"Snippet","default":"—","note":"Icon for the default Button trigger."},{"name":"trigger","type":"Snippet<[TriggerAttrs]>","default":"—","note":"Escape hatch. Wins over triggerLabel/triggerProps/triggerIcon. Receives an attrs bag (id, aria-expanded, aria-controls, popovertarget, onclick) to spread onto your own element: a link, an avatar, a custom control."},{"name":"children","type":"Snippet","default":"—","note":"Required. The panel content. Interactive controls are supported."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-popover class."}],"types":[],"hooks":{"root":"hz-popover","attrs":[{"name":"data-open","values":"present while open","note":"On the root — the Dropdown parity hook."},{"name":"data-state (panel)","values":"'open' | 'closed'","note":"On .hz-popover-panel — mirrors the open prop; the hook to animate the entrance against."},{"name":"data-placement","values":"Placement","note":"On .hz-popover-panel — the requested placement string, e.g. \"bottom-start\"."},{"name":"data-side","values":"'top' | 'bottom' | 'left' | 'right'","note":"On .hz-popover-panel — the resolved, physical side. See Positioning for the caret recipe."},{"name":"data-align","values":"'start' | 'center' | 'end'","note":"On .hz-popover-panel — the resolved cross-axis alignment (RTL-aware for a top/bottom placement)."}],"props":[{"name":"--hz-z-popover","values":"<number> — default 200","note":"Matters mainly on the non-top-layer fallback path (older browsers) — a real top-layer panel stacks above everything regardless of z-index."}],"parts":[{"name":".hz-popover-trigger","values":"on a Button","note":"Merged onto the default composed Button trigger — absent when the `trigger` snippet escape hatch is used instead, since that renders your own element."},{"name":".hz-popover-panel","values":"child element","note":"The disclosure panel surface."},{"name":".hz-popover-content","values":"child element","note":"The scroll container inside the panel — the panel itself stays overflow-visible so a caret you draw can protrude. See Positioning for the recipe."}]},"a11yNote":"The trigger carries `aria-expanded` and `aria-controls` pointing at the panel. That is the APG Disclosure pattern, not a dialog. The panel itself is a plain region by default, or a labeled `role=\"group\"` when you pass `label` (for a panel with no visible heading). It is never `aria-modal`, never traps focus, and never renders a backdrop. Interactive content inside flows through the normal tab order and back out.\n\nEscape always closes the panel and returns focus to the trigger, even with `dismissible: false`. There is no opt-out, the same rule Modal follows for its own Escape handling. Clicking outside the panel closes it too, unless `dismissible: false`. Unlike Escape, an outside click leaves focus wherever the click landed rather than pulling it back.\n\nBy default (`autoFocus: false`) opening the panel does not move focus at all, because a disclosure should not take focus uninvited. Set `autoFocus` when the panel's first control really is the next thing someone wants, such as a search field."},{"name":"Tooltip","group":"Overlays","route":"/docs/components/tooltip","description":"An accessible hover and focus description for an element you already have, such as an icon button, a link, or an abbreviation. It holds non-interactive text only; for a click-triggered panel with rich content, use Popover.","importLine":"import { tooltip } from \"@hyzer-labs/ui\"","props":[{"name":"text","type":"string","default":"—","note":"Required. The tooltip text. `tooltip('Save changes')` is shorthand for `tooltip({ text: 'Save changes' })`."},{"name":"placement","type":"'top' | 'bottom' | 'left' | 'right' | '<side>-start' | '<side>-end'","default":"'top'","note":"'top' == 'top-center'; -start/-end add alignment. left/right follow the trigger's writing direction, so RTL flips the physical side."},{"name":"offset","type":"number","default":"8","note":"Gap from the trigger, in px."},{"name":"openDelay","type":"number","default":"400","note":"A hover-intent filter, in ms. A pointer that only passes over the trigger never opens it. Keyboard focus shows the tooltip right away, with no delay."},{"name":"closeDelay","type":"number","default":"150","note":"The hoverable bridge, in ms. It gives the pointer time to travel from the trigger onto the tooltip itself without the tooltip disappearing."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-tooltip class."}],"types":[],"hooks":{"root":"hz-tooltip","attrs":[{"name":"data-state","values":"'open' | 'closed'","note":"Drives visibility (inline display, not the theme) — the hook to animate the entrance/exit against."},{"name":"data-placement","values":"Placement","note":"The requested placement string, e.g. \"top\" or \"bottom-start\" — mirrors the placement option verbatim."},{"name":"data-side","values":"'top' | 'bottom' | 'left' | 'right'","note":"The resolved, physical side — after any overflow flip and RTL resolution. See Positioning for the caret recipe."},{"name":"data-align","values":"'start' | 'center' | 'end'","note":"The resolved cross-axis alignment (RTL-aware for a top/bottom placement)."}],"props":[{"name":"--hz-z-tooltip","values":"<number> — default 150","note":"Matters mainly on the non-top-layer fallback path (older browsers) — a real top-layer tooltip stacks above everything regardless of z-index."}]},"a11yNote":"The trigger gains `aria-describedby` pointing at the tooltip's `role=\"tooltip\"` node. Any existing `aria-describedby` is kept, with the tooltip appended to it, and restored on teardown. The tooltip shows on hover, after `openDelay` filters out incidental passes, and on keyboard focus, immediately and with no delay. The APG Tooltip pattern requires both, not just one.\n\nWCAG 2.2 SC 1.4.13 (Content on Hover or Focus) is satisfied. Dismissible: Escape hides the tooltip without moving focus or the pointer, and it will not re-open until you leave and re-enter or re-focus the trigger. Hoverable: the pointer can travel from the trigger onto the tooltip itself, across the `offset` gap, without it disappearing, because `closeDelay` bridges the gap. Persistent: it stays visible until you dismiss it, blur the trigger, or leave both the trigger and the tooltip. It never times out on its own.\n\n`tooltip` never adds `tabindex`. It enhances a control that is already focusable. Attach it to a non-focusable element (a plain `<span>`, say) and it still shows on hover, but keyboard users cannot reach it. That case logs a warning in development.\n\nThere is no hover on touch. With a touch or coarse pointer, a tap activates the control underneath rather than revealing the tooltip, though `aria-describedby` still exposes the text to assistive tech. For content revealed by a tap, use Popover."},{"name":"Container","group":"Layout","route":"/docs/components/container","description":"Centers content horizontally with a configurable max-width and padding. Container decides how wide a region is, so pair it with Stack, Cluster, Grid, or Split to arrange the content inside.","importLine":"import { Container } from \"@hyzer-labs/ui\"","props":[{"name":"max","type":"'sm' | 'md' | 'lg' | 'xl' | 'full'","default":"'lg'"},{"name":"padding","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"'md'","note":"Both axes. Shared LayoutPadding scale — near/away tighten inside data-density-shift regions."},{"name":"paddingInline","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"—","note":"Per-axis override — wins over padding on the inline axis. Same LayoutPadding scale."},{"name":"paddingBlock","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"—","note":"Per-axis override — wins over padding on the block axis. Same LayoutPadding scale."},{"name":"center","type":"boolean","default":"true"},{"name":"breakout","type":"boolean","default":"false","note":"Escapes the parent column to span the nearest inline-size container (viewport when none). Overrides max."},{"name":"as","type":"string","default":"'div'"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-container class."},{"name":"children","type":"Snippet","default":"—"}],"types":[],"hooks":{"root":"hz-container","attrs":[{"name":"data-max","values":"'sm' | 'md' | 'lg' | 'xl' | 'full'","note":"Selects which --hz-width-* token caps the column. Default lg."},{"name":"data-padding","values":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","note":"Padding on both axes. Default md."},{"name":"data-center","values":"present when centered","note":"Applies margin-inline: auto. Present by default."},{"name":"data-breakout","values":"present when breaking out","note":"Escapes the column to span the nearest inline-size container."}],"props":[{"name":"--hz-breakout-shift","values":"<length> — default calc(50% - 50cqw)","note":"Breakout alignment offset. The default assumes a centered column; set it to 0 in a start-aligned layout so the bleed only grows rightward."}]},"a11yNote":"Container is a layout primitive with no ARIA semantics. Use the `as` prop to render a meaningful landmark element (e.g. as='main') where that fits."},{"name":"Stack","group":"Layout","route":"/docs/components/stack","description":"Lays children out in a vertical column with consistent spacing between items. Stack arranges content; pair it with a Container when the column also needs a max-width or page gutters.","importLine":"import { Stack } from \"@hyzer-labs/ui\"","props":[{"name":"gap","type":"'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'near' | 'away'","default":"'md'","note":"near/away are the density distances — they tighten inside data-density-shift regions."},{"name":"align","type":"'start' | 'center' | 'end' | 'stretch' | 'baseline'","default":"'stretch'","note":"Shared LayoutAlign scale (Stack/Cluster/Grid)."},{"name":"padding","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"'none'","note":"Both axes. Shared LayoutPadding scale — near/away tighten inside data-density-shift regions."},{"name":"paddingInline","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"—","note":"Per-axis override — wins over padding on the inline axis. Same LayoutPadding scale."},{"name":"paddingBlock","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"—","note":"Per-axis override — wins over padding on the block axis. Same LayoutPadding scale."},{"name":"as","type":"string","default":"'div'"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-stack class."},{"name":"children","type":"Snippet","default":"—"}],"types":[],"hooks":{"root":"hz-stack","attrs":[{"name":"data-gap","values":"'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'near' | 'away'","note":"Row gap. Default md. The only layout gap scale with an xl rung."},{"name":"data-align","values":"'start' | 'center' | 'end' | 'stretch' | 'baseline'","note":"align-items. Default stretch."},{"name":"data-padding","values":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","note":"Padding on both axes. Default none."}]},"a11yNote":"Stack is a layout primitive with no ARIA semantics. The reading and focus order follow the DOM order of children."},{"name":"Cluster","group":"Layout","route":"/docs/components/cluster","description":"Lays children out in a horizontal row that wraps to new lines as needed.","importLine":"import { Cluster } from \"@hyzer-labs/ui\"","props":[{"name":"gap","type":"'none' | 'xs' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"'sm'","note":"near/away are the density distances — they tighten inside data-density-shift regions."},{"name":"justify","type":"'start' | 'center' | 'end' | 'between' | 'around'","default":"'start'"},{"name":"align","type":"'start' | 'center' | 'end' | 'stretch' | 'baseline'","default":"'center'","note":"Shared LayoutAlign scale (Stack/Cluster/Grid)."},{"name":"wrap","type":"boolean","default":"true"},{"name":"padding","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"'none'","note":"Both axes. Shared LayoutPadding scale — near/away tighten inside data-density-shift regions."},{"name":"paddingInline","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"—","note":"Per-axis override — wins over padding on the inline axis. Same LayoutPadding scale."},{"name":"paddingBlock","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"—","note":"Per-axis override — wins over padding on the block axis. Same LayoutPadding scale."},{"name":"as","type":"string","default":"'div'"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-cluster class."},{"name":"children","type":"Snippet","default":"—"}],"types":[],"hooks":{"root":"hz-cluster","attrs":[{"name":"data-gap","values":"'none' | 'xs' | 'sm' | 'md' | 'lg' | 'near' | 'away'","note":"Gap between items. Default sm."},{"name":"data-justify","values":"'start' | 'center' | 'end' | 'between' | 'around'","note":"justify-content. Default start."},{"name":"data-align","values":"'start' | 'center' | 'end' | 'stretch' | 'baseline'","note":"align-items. Default center."},{"name":"data-wrap","values":"present when wrapping","note":"Present by default; its absence is styled too (:not([data-wrap]) sets nowrap)."},{"name":"data-padding","values":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","note":"Padding on both axes. Default none."}]},"a11yNote":"Cluster is a layout primitive with no ARIA semantics. Reading and focus order follow DOM order."},{"name":"Grid","group":"Layout","route":"/docs/components/grid","description":"Responsive CSS grid that adapts its column count to its own width via container queries.","importLine":"import { Grid } from \"@hyzer-labs/ui\"","props":[{"name":"columns","type":"number | { sm?: number; md?: number; lg?: number; xl?: number } | { min: string }","default":"{ sm: 1, md: 2, lg: 3 }","note":"Band keys name the band ending at their width token: sm below 640px, md to 968px, lg to 1200px, xl beyond — fixed system constants, because queries cannot read tokens. { min } is the fluid, fully var-driven mode."},{"name":"gap","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"'md'","note":"near/away are the density distances — they tighten inside data-density-shift regions."},{"name":"align","type":"'start' | 'center' | 'end' | 'stretch' | 'baseline'","default":"'stretch'","note":"Shared LayoutAlign scale (Stack/Cluster/Grid)."},{"name":"padding","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"'none'","note":"Both axes, on the grid root — the column breakpoints measure the padded-down width. Shared LayoutPadding scale."},{"name":"paddingInline","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"—","note":"Per-axis override — wins over padding on the inline axis. Same LayoutPadding scale."},{"name":"paddingBlock","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"—","note":"Per-axis override — wins over padding on the block axis. Same LayoutPadding scale."},{"name":"as","type":"string","default":"'div'"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-grid class."},{"name":"children","type":"Snippet","default":"—"}],"types":[],"hooks":{"root":"hz-grid","attrs":[{"name":"data-gap","values":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","note":"Track gap. Default md."},{"name":"data-align","values":"'start' | 'center' | 'end' | 'stretch' | 'baseline'","note":"align-items. Default stretch."},{"name":"data-padding","values":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","note":"Padding, applied to the root so container queries measure the space the tracks actually get. Default none."},{"name":"data-fluid","values":"present in fluid mode","note":"Read-only: present when columns is {min}. Target it to style fluid grids apart from banded ones — the mode is set by the prop, not the attribute."}],"parts":[{"name":".hz-grid-layout","values":"child element","note":"The real grid. The root is a size container only (an element cannot container-query itself), so track and gap rules live here."}]},"a11yNote":"Grid is a layout primitive with no ARIA semantics. Reading and focus order follow DOM order."},{"name":"Split","group":"Layout","route":"/docs/components/split","description":"Two-column layout with configurable proportions that stacks to a single column when its own width gets narrow.","importLine":"import { Split } from \"@hyzer-labs/ui\"","props":[{"name":"fraction","type":"'1/4' | '1/3' | '1/2' | '2/3' | '3/4' | 'auto'","default":"'1/2'"},{"name":"gap","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"'md'","note":"near/away are the density distances — they tighten inside data-density-shift regions."},{"name":"reverse","type":"boolean","default":"false"},{"name":"stackBelow","type":"'sm' | 'md' | 'lg'","default":"'sm'","note":"Stacks under the --hz-width-sm/md/lg token (640/968/1200px) of the Split's own width. Resolves via var() — overriding the token retunes the threshold."},{"name":"padding","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"'none'","note":"Both axes, on the split root — stackBelow measures the padded-down width. Shared LayoutPadding scale."},{"name":"paddingInline","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"—","note":"Per-axis override — wins over padding on the inline axis. Same LayoutPadding scale."},{"name":"paddingBlock","type":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","default":"—","note":"Per-axis override — wins over padding on the block axis. Same LayoutPadding scale."},{"name":"as","type":"string","default":"'div'"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-split class."},{"name":"children","type":"Snippet","default":"—","note":"Two direct children become the columns."}],"types":[],"hooks":{"root":"hz-split","attrs":[{"name":"data-fraction","values":"'1/4' | '1/3' | '1/2' | '2/3' | '3/4' | 'auto'","note":"Ratio of the two columns above the stack threshold. Default 1/2."},{"name":"data-gap","values":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","note":"Gap between the columns. Default md."},{"name":"data-reverse","values":"present when reversed","note":"Visual-only swap via order — DOM and focus order are preserved."},{"name":"data-stack-below","values":"'sm' | 'md' | 'lg'","note":"Picks which --hz-width-* token the stack threshold resolves through, so overriding that token retunes when the split stacks. Default sm."},{"name":"data-padding","values":"'none' | 'sm' | 'md' | 'lg' | 'near' | 'away'","note":"Padding, applied to the root. Default none."}],"parts":[{"name":".hz-split-layout","values":"child element","note":"The flex row. Carries the gap; the fraction and order rules reach your two children through it."}]},"a11yNote":"Split is a layout primitive with no ARIA semantics. DOM order determines reading and focus order regardless of visual arrangement."},{"name":"Virtualizer","group":"Layout","route":"/docs/components/virtualizer","description":"A headless windowing primitive that renders only the visible slice of a huge items array. Row heights can be uniform, known-variable, or measured at runtime.","importLine":"import { Virtualizer } from \"@hyzer-labs/ui\"","props":[{"name":"items","type":"T[]","default":"—","note":"Required."},{"name":"itemHeight","type":"number | ((item: T, index: number) => number)","default":"—","note":"Required. A fixed px height (uniform), or a per-item height function (known-variable). The estimate/seed when measure is true."},{"name":"height","type":"number","default":"—","note":"Optional — omit for fluid. Viewport extent in px for fixed, SSR-exact windowing. Omitted, the viewport is fluid: CSS-size it and the component measures its own box at runtime."},{"name":"measure","type":"boolean","default":"false","note":"Runtime-measures each rendered row via ResizeObserver; itemHeight becomes the seed estimate for unmeasured rows."},{"name":"overscan","type":"number","default":"3","note":"Extra rows rendered above/below the visible span."},{"name":"row","type":"Snippet<[T, number]>","default":"—","note":"Required. Renders one row — see the row snippet signature below."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-virtualizer class."}],"types":[{"name":"itemHeight (union)","props":[{"name":"number","type":"number","default":"—","note":"Uniform row height in px — the O(1) fast path (no measure)."},{"name":"(item, index) => number","type":"(item: T, index: number) => number","default":"—","note":"Known-variable — a per-row height resolved from the item/index."}]},{"name":"row snippet — Snippet<[item, index]>","props":[{"name":"item","type":"T","default":"—","note":"The array element rendered by this row."},{"name":"index","type":"number","default":"—","note":"The item's absolute index in items — not the window-local render position — so keys, striping, and aria-posinset stay correct despite windowing."}]}],"hooks":{"root":"hz-virtualizer","parts":[{"name":".hz-virtualizer-sizer","values":"child element","note":"Full-height spacer that gives the scroller its scroll range."},{"name":".hz-virtualizer-window","values":"child element","note":"The translated window holding the rendered rows."},{"name":".hz-virtualizer-row","values":"child element","note":"Per-row wrapper. Also load-bearing at runtime — measured mode queries it — so it is stable by contract, not just by convention."}]},"a11yNote":"Virtualizer is role-neutral by design. It applies no `role`, `aria-*`, or `tabindex` of its own. It is a rendering optimization rather than a widget, so all semantics come from the `row` snippet and `...rest` on the viewport.\n\nWindowing removes off-screen rows from the DOM, so any count-dependent semantics must be supplied explicitly. Set `aria-setsize` to the total item count and `aria-posinset` to the row's absolute index plus one, using the absolute index the snippet receives. That way assistive tech announces 'item N of total' correctly despite the elided DOM. See the List semantics demo below.\n\nA keyboard-scrollable viewport is opt-in: add a `tabindex` of 0 (plus a `role`/label) through `...rest`. The component adds no key handling of its own.\n\nVirtualization can scroll a focused row out of the DOM, a known windowing hazard. Patterns that need a persistently focused off-screen row (for example, `aria-activedescendant` listboxes) should keep the active row rendered rather than reaching for the raw Virtualizer."},{"name":"Nav","group":"Navigation","route":"/docs/components/nav","description":"A horizontal row of links with dropdown menus, or a vertical sidebar column with nested, multi-open disclosure sections. It stays a semantically correct navigation landmark in any context: standalone, in a sidebar, or composed by Header into a full top bar.","importLine":"import { Nav } from \"@hyzer-labs/ui\"","props":[{"name":"items","type":"NavItem[]","default":"—","note":"Required. See NavItem below."},{"name":"orientation","type":"'horizontal' | 'vertical'","default":"'horizontal'","note":"horizontal: a row of links with dropdown menus. vertical: a sidebar column with inline, nested, multi-open disclosure sections."},{"name":"ariaLabel","type":"string","default":"'Main navigation'"},{"name":"chevronIcon","type":"Snippet","default":"—","note":"Replaces the dropdown chevron."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-nav class."}],"types":[{"name":"NavItem","props":[{"name":"label","type":"string","default":"—","note":"Required."},{"name":"href","type":"string","default":"—","note":"Omit (with children present) for a trigger-only dropdown / section parent."},{"name":"children","type":"NavChild[]","default":"—","note":"Makes the item a dropdown (horizontal) or a nested disclosure section (vertical). May contain items or { heading } group labels."},{"name":"external","type":"boolean","default":"—","note":"Adds the external-link treatment."},{"name":"ariaCurrent","type":"'page' | 'step' | 'true'","default":"—","note":"Set on the active item."},{"name":"defaultOpen","type":"boolean","default":"—","note":"Vertical only: the section starts open, and re-opens when items is rebuilt (e.g. on navigation)."}]}],"hooks":{"root":"hz-nav","attrs":[{"name":"data-orientation","values":"'horizontal' | 'vertical'","note":"Selects the layout mode — a horizontal row with popover dropdown panels, or a sidebar column with inline nested disclosure sections. Default horizontal."},{"name":"data-state","values":"'open' | 'closed'","note":"On .hz-nav-panel — the dropdown/disclosure open/closed vocabulary to animate against."}],"parts":[{"name":".hz-nav-links","values":"child element","note":"The link list — a row (horizontal) or a column (vertical)."},{"name":".hz-nav-dropdown","values":"child element","note":"The list item wrapping a submenu."},{"name":".hz-nav-trigger","values":"child element","note":"The label-only section toggle (no href)."},{"name":".hz-nav-chevron","values":"child element","note":"The separate chevron button when the item is both a link and a toggle."},{"name":".hz-nav-panel","values":"child element","note":"The submenu list — a popover when horizontal, an inline section when vertical."},{"name":".hz-nav-heading","values":"child element","note":"A group label inside a panel: static text, no focus stop."}]},"a11yNote":"Horizontal dropdowns follow the APG menu-button pattern. The trigger carries `aria-haspopup`/`aria-expanded`, the panel is a `role=menu` with roving arrow-key focus, and `Escape` closes it and returns focus to the trigger.\n\nVertical sections are plain disclosure (`aria-expanded` on each `<button>`), not menus, with native keyboard traversal.\n\nA `heading` entry is static, non-focusable text read in sequence before the links it labels."},{"name":"Header","group":"Navigation","route":"/docs/components/header","description":"A site header bar with branding, navigation, actions, and a responsive hamburger and drawer built in. It composes Nav horizontally in the bar and vertically in the drawer, so one item set drives both.","importLine":"import { Header } from \"@hyzer-labs/ui\"","props":[{"name":"items","type":"NavItem[]","default":"—","note":"Navigation — rendered horizontally in the bar and vertically in the drawer. See Nav."},{"name":"logo","type":"Snippet","default":"—","note":"Logo region at the start."},{"name":"actions","type":"Snippet","default":"—","note":"End of the bar; repeated inside the drawer."},{"name":"sticky","type":"boolean","default":"false","note":"position: sticky at the top of the nearest scroll container."},{"name":"variant","type":"'default' | 'transparent'","default":"'default'"},{"name":"bordered","type":"boolean","default":"false","note":"Bottom hairline — composes with any variant."},{"name":"mobileBreakpoint","type":"'sm' | 'md' | 'lg' | 'none'","default":"'md'","note":"Collapse threshold (640/968/1200px), a container query against the header width. none never collapses."},{"name":"ariaLabel","type":"string","default":"'Main navigation'","note":"Names the navigation."},{"name":"menuIcon","type":"Snippet","default":"—","note":"Replaces the hamburger icon."},{"name":"chevronIcon","type":"Snippet","default":"—","note":"Forwarded to the Nav."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-header class."}],"types":[],"hooks":{"root":"hz-header","attrs":[{"name":"data-variant","values":"'default' | 'transparent'","note":"Bar surface treatment. Default default."},{"name":"data-bordered","values":"present when bordered","note":"Bottom hairline. Composes with any variant."},{"name":"data-sticky","values":"present when sticky","note":"Sticks the bar to the top."},{"name":"data-mobile-breakpoint","values":"'sm' | 'md' | 'lg' | 'none'","note":"Which width collapses the bar into the drawer. none never collapses. Default md."},{"name":"data-state","values":"'open' | 'closed'","note":"On .hz-header-drawer — the drawer open/closed vocabulary to animate against."}],"parts":[{"name":".hz-header-inner","values":"child element","note":"The flex bar; owns its padding."},{"name":".hz-header-logo","values":"child element","note":"Wrapper for the logo snippet."},{"name":".hz-header-actions","values":"child element","note":"Wrapper for the actions snippet (in the bar). Below the mobile breakpoint, an auto inline-start margin pins it to the end, next to the hamburger — override margin-inline-start on this class (e.g. via a descendant selector off your own class prop) for centering or any other placement."},{"name":".hz-header-toggle","values":"child element","note":"The hamburger button."},{"name":".hz-header-drawer","values":"child element","note":"The mobile drawer."},{"name":".hz-header-drawer-actions","values":"child element","note":"The actions repeated inside the drawer."}]},"a11yNote":"The header is a `banner` landmark. Its bar and drawer each render a `Nav` landmark with distinct accessible names (`ariaLabel` and `ariaLabel (menu)`), so they do not collide.\n\nThe hamburger carries `aria-expanded` and `aria-controls`. The open drawer traps focus. `Escape` closes it and returns focus to the toggle.\n\nAt narrow widths, the drawer keeps every link reachable."},{"name":"Footer","group":"Navigation","route":"/docs/components/footer","description":"Site footer with auto-fitting multi-column link groups, optional logo, social links, and a bottom bar.","importLine":"import { Footer } from \"@hyzer-labs/ui\"","props":[{"name":"columns","type":"FooterColumn[]","default":"—","note":"Required. See FooterColumn below."},{"name":"variant","type":"'default' | 'minimal'","default":"'default'"},{"name":"bordered","type":"boolean","default":"false","note":"Top hairline — composes with any variant."},{"name":"linkVariant","type":"'default' | 'subtle' | 'nav'","default":"'subtle'","note":"Passed through to every column Link."},{"name":"headingLevel","type":"2 | 3 | 4 | 5 | 6","default":"2","note":"Heading element for column titles — match your page hierarchy."},{"name":"logo","type":"Snippet","default":"—","note":"Rendered above the columns."},{"name":"social","type":"Snippet","default":"—","note":"Icon-link row rendered under the columns."},{"name":"bottom","type":"Snippet","default":"—","note":"Bottom bar with a top hairline — copyright, legal links."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-footer class."}],"types":[{"name":"FooterColumn","props":[{"name":"title","type":"string","default":"—","note":"Required. Labels the column’s nav landmark."},{"name":"links","type":"NavItem[]","default":"—","note":"Required. label/href/external/ariaCurrent apply — see NavItem on the Nav page."}]}],"hooks":{"root":"hz-footer","attrs":[{"name":"data-variant","values":"'default' | 'minimal'","note":"minimal drops the padding and goes transparent. Default default."},{"name":"data-bordered","values":"present when bordered","note":"Top hairline. A prop, not a variant — it composes with either surface."}],"props":[{"name":"--hz-footer-col-min","values":"<length> — default 12rem","note":"Minimum column width for the auto-fit link grid: how narrow a column may get before the row reflows. No media queries involved — this is the whole knob."}],"parts":[{"name":".hz-footer-logo","values":"child element","note":"Wrapper for the logo snippet."},{"name":".hz-footer-columns","values":"on a Grid","note":"Rides through Grid’s class prop. The auto-fit override hangs off it — and lands on .hz-grid-layout, since that is where Grid does its layout."},{"name":".hz-footer-column","values":"child element","note":"One column’s nav landmark."},{"name":".hz-footer-heading","values":"child element","note":"A column heading."},{"name":".hz-footer-social","values":"child element","note":"Wrapper for the social snippet."},{"name":".hz-footer-bottom","values":"child element","note":"The bottom bar."}]},"a11yNote":"Each column is a `<nav>` landmark whose `aria-label` is its title. The visible heading repeats that text, but the heading is not what names the landmark. Set `headingLevel` to match your page hierarchy. Links use the Link component, and icon-only social links need an `ariaLabel`."},{"name":"Breadcrumbs","group":"Navigation","route":"/docs/components/breadcrumbs","description":"A wrapping breadcrumb trail of navigation links with chevron separators and automatic current-page semantics.","importLine":"import { Breadcrumbs } from \"@hyzer-labs/ui\"","props":[{"name":"items","type":"BreadcrumbItem[]","default":"—","note":"Required. See BreadcrumbItem below."},{"name":"ariaLabel","type":"string","default":"'Breadcrumb'"},{"name":"separator","type":"Snippet","default":"—","note":"Replaces the chevron between items; rendered aria-hidden."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-breadcrumbs class."}],"types":[{"name":"BreadcrumbItem","props":[{"name":"label","type":"string","default":"—","note":"Required."},{"name":"href","type":"string","default":"—","note":"Omit on the last item to render the current page as plain text."},{"name":"external","type":"boolean","default":"—","note":"Adds the external-link treatment."},{"name":"ariaCurrent","type":"'page' | 'step' | 'true'","default":"—","note":"The last item gets aria-current=\"page\" automatically; set this to override."}]}],"hooks":{"root":"hz-breadcrumbs","attrs":[{"name":"[aria-current]","values":"'page'","note":"Not a data hook — Breadcrumbs stamps no data-*. The last crumb carries aria-current, and that is the only state selector on offer."}],"parts":[{"name":".hz-breadcrumbs-current","values":"child element","note":"The final crumb when it has no href (plain text rather than a link)."},{"name":".hz-breadcrumbs-separator","values":"child element","note":"The aria-hidden separator wrapper; sizes its icon to 1em."}]},"a11yNote":"Breadcrumbs renders a `<nav aria-label=\"Breadcrumb\">` landmark wrapping an ordered list. The last item is the current page: it gets `aria-current=\"page\"` automatically, and renders as plain text when it has no `href`. Separators are decorative and `aria-hidden`."},{"name":"Pagination","group":"Navigation","route":"/docs/components/pagination","description":"A navigation landmark of page controls: previous and next, boundary and sibling windows with ellipsis truncation, and button or real-link modes.","importLine":"import { Pagination } from \"@hyzer-labs/ui\"","props":[{"name":"count","type":"number","default":"—","note":"Required. Total pages."},{"name":"page","type":"number","default":"1","note":"Bindable. 1-based."},{"name":"siblings","type":"number","default":"1","note":"Pages shown on each side of the current page."},{"name":"boundaries","type":"number","default":"1","note":"Pages pinned at each end."},{"name":"href","type":"(page: number) => string","default":"—","note":"Link mode: items render as real anchors; omit for button mode."},{"name":"onchange","type":"(page: number) => void","default":"—"},{"name":"ariaLabel","type":"string","default":"'Pagination'","note":"Names the landmark."},{"name":"prevLabel","type":"string","default":"'Previous page'"},{"name":"nextLabel","type":"string","default":"'Next page'"},{"name":"pageLabel","type":"(page: number) => string","default":"`Page ${page}`","note":"Accessible name per page item."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-pagination class."}],"types":[],"hooks":{"root":"hz-pagination","parts":[{"name":".hz-pagination-list","values":"child element","note":"The row of page controls."},{"name":".hz-pagination-page","values":"on a Button","note":"Rides through Button’s class prop, so it lands on a .hz-button — target it accordingly. Tabular numerals and a min-width keep the row from jittering."},{"name":".hz-pagination-prev","values":"on a Button","note":"Previous control. Also a .hz-button."},{"name":".hz-pagination-next","values":"on a Button","note":"Next control. Also a .hz-button."},{"name":".hz-pagination-ellipsis","values":"child element","note":"The aria-hidden gap marker."}]},"a11yNote":"Pagination renders a `<nav>` landmark named by `ariaLabel`. Give it a distinct name when a page hosts several.\n\nEvery control is a `Button`. The current item carries `aria-current=\"page\"`, and page items get full accessible names via `pageLabel` (\"Page 7\", not a bare number). Ellipses are decorative.\n\nIn link mode, everything is a real `<a>`. At the ends, the previous and next controls render as disabled Buttons with no `href`, because a link cannot be disabled and a dead link that announces itself helps no one.\n\nTab order is native. There are no roving-focus tricks."},{"name":"Toc","group":"Navigation","route":"/docs/components/toc","description":"A table-of-contents rail with automatic heading collection, nested levels, scroll-spy, smooth scroll, and an optional mobile collapse. It is the docs site's own 'On this page' rail, generalized behind props.","importLine":"import { Toc } from \"@hyzer-labs/ui\"","props":[{"name":"container","type":"string | HTMLElement","default":"'main'","note":"Selector or element to collect headings from."},{"name":"levels","type":"number[]","default":"[2]","note":"Heading levels to collect — 2 for h2, 3 for h3, and so on."},{"name":"exclude","type":"string | string[]","default":"—","note":"Headings inside a match are skipped (e.g. demo/example regions). Pass an array to combine several selectors."},{"name":"minEntries","type":"number","default":"2","note":"Renders nothing below this many entries — a single link is not navigation."},{"name":"title","type":"string","default":"'On this page'","note":"'' hides it."},{"name":"ariaLabel","type":"string","default":"— (defaults to title)","note":"Names the nav landmark."},{"name":"autoId","type":"boolean","default":"true","note":"Slugify id-less headings (kebab, deduped with -2, -3 …). false skips them instead."},{"name":"watch","type":"boolean","default":"true","note":"Re-collects on DOM mutation (a MutationObserver on container, debounced). No framework coupling — no $app/* import, so it works the same in any SvelteKit or plain-Svelte app."},{"name":"smoothScroll","type":"boolean","default":"true","note":"Instant under prefers-reduced-motion regardless."},{"name":"breakpoint","type":"'sm' | 'md' | 'lg' | 'none'","default":"'none'","note":"Collapses into a disclosure below this width (640/968/1200px). none never collapses."},{"name":"active","type":"string","default":"''","note":"Bindable id of the current heading (scroll-spy — or the last clicked link)."},{"name":"onActive","type":"(id: string) => void","default":"—","note":"Fires when the active heading changes — not on every scroll frame."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-toc class."}],"types":[{"name":"TocEntry","props":[{"name":"id","type":"string","default":"—","note":"The heading's id — its own, or an autoId slug."},{"name":"label","type":"string","default":"—","note":"The heading's trimmed text content."},{"name":"level","type":"number","default":"—","note":"The collected heading level (2, 3, …)."}]}],"hooks":{"root":"hz-toc","attrs":[{"name":"data-breakpoint","values":"'sm' | 'md' | 'lg' | 'none'","note":"Which width the rail collapses below into a disclosure. none (default) never collapses. A real @media breakpoint, not a container query — the rail’s own box is typically narrow regardless of page width."},{"name":"data-collapsed","values":"present while the mobile disclosure is closed","note":"Only stamped when data-breakpoint is not none. Absent once the panel opens, and inert above the breakpoint, where the panel is always shown."},{"name":"[aria-current]","values":"'location'","note":"On .hz-toc-link — the scroll-spy’s active entry. Not a data-* hook."},{"name":"data-level (on .hz-toc-link)","values":"<number> — the collected heading level (2, 3, …)","note":"Per-entry, not root-level — drives the indent."}],"parts":[{"name":".hz-toc-title","values":"child element","note":"The plain (non-heading) title."},{"name":".hz-toc-trigger","values":"child element","note":"The mobile disclosure button (title + chevron). Always rendered, shown only below the breakpoint — the theme swaps it in for .hz-toc-title by data-breakpoint and the viewport, the Header toggle precedent."},{"name":".hz-toc-panel","values":"child element","note":"Wraps the list; what the trigger shows/hides below the breakpoint."},{"name":".hz-toc-list","values":"child element","note":"A nested <ul>, one per level below the top."},{"name":".hz-toc-link","values":"child element","note":"One entry."}]},"a11yNote":"The root is a `nav` landmark named by `ariaLabel` (which defaults to `title`). Give every Toc on a page a distinct name if there's more than one.\n\nThe active entry carries `aria-current=\"location\"`, updated by the scroll-spy and by clicking a link.\n\nIn collapse mode, the trigger is a real disclosure button (`aria-expanded`/`aria-controls`). `Escape` closes it and returns focus to the trigger, and an outside click closes it without stealing focus from wherever the click landed.\n\nMotion respects `prefers-reduced-motion`: `smoothScroll` degrades to an instant jump automatically."},{"name":"Image","group":"Media","route":"/docs/components/image","description":"Responsive image with aspect-ratio, object-fit, rounded corners, color and blur placeholder states, and a picture mode for art direction.","importLine":"import { Image } from \"@hyzer-labs/ui\"","props":[{"name":"src","type":"string","default":"—","note":"Required. Fallback in picture mode."},{"name":"alt","type":"string","default":"—","note":"Required. Empty string for decorative."},{"name":"sources","type":"ImageSource[]","default":"—","note":"Renders a <picture>: candidates in priority order — see ImageSource below. src remains the fallback and drives the load state."},{"name":"width","type":"number","default":"—"},{"name":"height","type":"number","default":"—"},{"name":"loading","type":"'lazy' | 'eager'","default":"'lazy'"},{"name":"aspectRatio","type":"'auto' | '1/1' | '4/3' | '16/9' | '21/9' | string","default":"'auto'"},{"name":"fit","type":"'cover' | 'contain' | 'fill' | 'none'","default":"'cover'"},{"name":"rounded","type":"boolean | 'sm' | 'md' | 'lg' | 'full'","default":"false","note":"true is shorthand for md."},{"name":"placeholder","type":"'blur' | 'color' | 'none'","default":"'none'"},{"name":"placeholderSrc","type":"string","default":"—","note":"Low-res image for placeholder=\"blur\"; required in that mode."},{"name":"placeholderColor","type":"string","default":"'var(--hz-color-border)'"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-image class."}],"types":[{"name":"ImageSource","props":[{"name":"srcset","type":"string","default":"—","note":"Required. Candidate URL(s) for this source."},{"name":"type","type":"string","default":"—","note":"MIME type for format negotiation, e.g. 'image/avif'."},{"name":"media","type":"string","default":"—","note":"Viewport media query for art direction, e.g. (min-width: 968px)."},{"name":"sizes","type":"string","default":"—"}]}],"hooks":{"root":"hz-image","attrs":[{"name":"data-state","values":"'loading' | 'loaded' | 'error'","note":"The load lifecycle. Read-only — the component owns it. Drives the placeholder crossfade and the error surface."},{"name":"data-fit","values":"'cover' | 'contain' | 'fill' | 'none'","note":"object-fit on the inner img. Default cover."},{"name":"data-rounded","values":"present | 'sm' | 'md' | 'lg' | 'full'","note":"Corner radius. Bare presence (from rounded={true}) means md."},{"name":"data-aspect-ratio","values":"'auto' | '1/1' | '4/3' | '16/9' | '21/9' | any ratio","note":"Mirrors the prop; the ratio itself lands as an inline style. The union is open — the listed values are documentation, not a limit."},{"name":"data-placeholder","values":"'blur' | 'color'","note":"Absent when there is no placeholder — you cannot select [data-placeholder='none']. blur without placeholderSrc degrades to none."},{"name":"data-loading","values":"'lazy' | 'eager'","note":"Mirrors the loading prop."},{"name":"data-picture","values":"present in picture mode","note":"Present when sources are supplied."}],"props":[{"name":"--hz-image-placeholder-blur","values":"<length> — default 20px","note":"Blur radius on the low-res placeholder."},{"name":"--hz-image-fade-duration","values":"<time> — default 0.3s","note":"Placeholder crossfade duration. Read at both ends of the fade, so the two stay locked together."}],"parts":[{"name":".hz-image__img","values":"child element","note":"The real img. Note the BEM double underscore — Image and Video use it; the rest of the library uses a single dash."},{"name":".hz-image__placeholder","values":"child element","note":"The decorative low-res image that fades out."},{"name":".hz-image__picture","values":"child element","note":"The <picture> element. display: contents, so it adds no box."}]},"a11yNote":"Pass descriptive alt text for informative images. Pass `alt=''` for decorative images: the component then sets `role='presentation'` for you."},{"name":"Lightbox","group":"Media","route":"/docs/components/lightbox","description":"A click-to-enlarge media viewer: a thumbnail strip whose items open in an accessible, focus-trapped dialog. Multiple images and videos page through an embedded Carousel.","importLine":"import { Lightbox } from \"@hyzer-labs/ui\"","props":[{"name":"items","type":"LightboxItem[]","default":"—","note":"Media entries (images and videos) — see LightboxItem below. Renders one thumbnail trigger per item; the viewer pages through them with a Carousel."},{"name":"src / alt / thumbSrc / caption","type":"string","default":"—","note":"Single-image sugar — equivalent to a one-item items array."},{"name":"open","type":"boolean (bindable)","default":"false"},{"name":"dialogLabel","type":"string","default":"'Media viewer'","note":"Dialog name in multi-item mode (single items are labeled by their own name)."},{"name":"triggerLabel","type":"string","default":"'View larger: {name}'","note":"Single trigger only."},{"name":"closeLabel","type":"string","default":"'Close media viewer'"},{"name":"prevLabel","type":"string","default":"'Previous item'"},{"name":"nextLabel","type":"string","default":"'Next item'"},{"name":"onclose","type":"() => void","default":"—","note":"Fires once per dismissal."},{"name":"children","type":"Snippet","default":"—","note":"Custom trigger content — replaces the thumbnail strip; opens the first item."},{"name":"trigger","type":"Snippet<[LightboxItem, number]>","default":"—","note":"Per-item trigger face, e.g. an Image — replaces the default thumb/badge for every item. Ignored (with a DEV warning) when children is also passed."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-lightbox-triggers class (the inline strip)."}],"types":[{"name":"LightboxItem (image)","props":[{"name":"type","type":"'image'","default":"'image'","note":"Optional — image is the default."},{"name":"src","type":"string","default":"—","note":"Required. Full-size image."},{"name":"alt","type":"string","default":"—","note":"Required."},{"name":"thumbSrc","type":"string","default":"—","note":"Strip thumbnail; defaults to src."},{"name":"caption","type":"string","default":"—"}]},{"name":"LightboxItem (video)","props":[{"name":"type","type":"'video'","default":"—","note":"Required for videos."},{"name":"src","type":"string","default":"—","note":"Required. Native file or YouTube/Vimeo URL — plays via the Video component."},{"name":"label","type":"string","default":"—","note":"Required. Accessible name (the Video title)."},{"name":"poster","type":"string","default":"—"},{"name":"thumbSrc","type":"string","default":"—","note":"Strip thumbnail; defaults to poster."},{"name":"caption","type":"string","default":"—"}]}],"hooks":{"root":"hz-lightbox-triggers","attrs":[{"name":"data-state","values":"'open' | 'closed'","note":"On the viewer dialog, mirroring the bindable open prop. Visibility itself runs off the native [open] attribute — this is the hook to animate against."},{"name":"data-gallery","values":"present with more than one item","note":"On the viewer dialog. Drives the fixed gallery stage sizing that a single item skips (it hugs its media instead)."},{"name":"data-lightbox-trigger","values":"present on a grouped trigger","note":"Set by the lightboxGroup attachment rather than the component. Its only styling is a zoom-in cursor."}],"parts":[{"name":".hz-lightbox-trigger","values":"child element","note":"A thumbnail button. Also the root class when you supply your own children."},{"name":".hz-lightbox-thumb","values":"child element","note":"The thumbnail image."},{"name":".hz-lightbox-thumb-label","values":"child element","note":"Fallback chip when no thumbnail resolves."},{"name":".hz-lightbox-badge","values":"child element","note":"The play marker on video items."},{"name":".hz-lightbox","values":"the viewer dialog","note":"The overlay itself. It takes no class prop, so this name is unconditionally stable."},{"name":".hz-lightbox-close","values":"child element","note":"The close control."},{"name":".hz-lightbox-carousel","values":"on the embedded Carousel","note":"Multi-item viewers only — see Carousel for its own parts underneath."},{"name":".hz-lightbox-figure","values":"child element","note":"The figure wrapping each item."},{"name":".hz-lightbox-caption","values":"child element","note":"The caption."},{"name":".hz-lightbox-img","values":"on an Image's wrapper","note":"Rides through Image’s class prop — so it names a div, not an img. Reach the picture itself through .hz-lightbox-img .hz-image__img."},{"name":".hz-lightbox-video","values":"child element","note":"The wrapper around a video item’s Video."}]},"a11yNote":"Each thumbnail is a real `<button>` with `aria-haspopup=\"dialog\"`, named by its item.\n\nThe viewer is a native `<dialog>` opened with `showModal()`. Focus is trapped. Escape and the backdrop both close it, and focus returns to the thumbnail that opened it.\n\nMulti-item viewers embed the Carousel (labeled slides, live announcements), and ArrowLeft/ArrowRight page through it from anywhere in the dialog. Body scroll is locked while open."},{"name":"Logo","group":"Media","route":"/docs/components/logo","description":"Renders a raw inline SVG mark at a size normalized against its own aspect ratio, so a wide wordmark, a square badge, and a tall crest read as consistent side by side. With no SVG it falls back to the brand name as text.","importLine":"import { Logo } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Names the mark for assistive technology, and is the visible text when no SVG is given."},{"name":"svg","type":"string","default":"—","note":"Raw SVG markup, rendered as-is and never sanitized. Pass only a static asset you control — a `?raw` import or a build-time constant, never user input or a fetched string. Missing (or whitespace-only) renders `name` as text instead."},{"name":"children","type":"Snippet","default":"—","note":"Another way to supply the mark: an `<svg>` written directly as markup. Logo can only measure it once it mounts in the browser, while `svg` is measured during server rendering. If both are given, `svg` wins."},{"name":"scale","type":"number","default":"1","note":"A multiplier on top of the normalized width — the optical-weight knob. A positive number, typically 0.7-1.4."},{"name":"brightness","type":"number","default":"1","note":"A filter: brightness() nudge for a mark that reads too dark or too light. Written inline only when it differs from 1."},{"name":"decorative","type":"boolean","default":"false","note":"Set when adjacent text already names the brand — hides the mark from assistive technology entirely."},{"name":"monochrome","type":"boolean","default":"true","note":"Recolors the mark to one color that follows the surrounding text. Set to false to keep the SVG's own colors."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-logo class."}],"types":[],"hooks":{"root":"hz-logo","attrs":[{"name":"data-fallback","values":"present with no svg","note":"The text form — no injected SVG, .hz-logo-text renders the name instead."},{"name":"data-monochrome","values":"present by default","note":"Absent when monochrome={false}. Gates the fill rule below, so an unset SVG keeps its own colours."}],"props":[{"name":"--hz-logo-size","values":"<length> — default clamp(4rem, 8vw, 6rem)","note":"The normalization base every logo scales against. Override it on a wall container (Cluster/Grid) to resize every logo inside at once."},{"name":"--hz-logo-brightness","values":"<number> — default 1","note":"Applied as filter: brightness(). Usually set through the brightness prop, which stamps it inline only when != 1 — the hook stays directly settable in CSS for container-wide muting."},{"name":"--hz-logo-color","values":"<color> — default currentColor","note":"Read, not declared, in the monochrome fill rule — a wall mutes itself with one override, the Container --hz-breakout-shift precedent for a read-only hook."}],"parts":[{"name":".hz-logo-text","values":"child element","note":"The text fallback."}]},"a11yNote":"An informative logo (the default) gets `role=\"img\"` and an `aria-label` taken from `name`. Assistive technology reads one graphic with one name, however many paths or groups the SVG holds inside. Set `decorative` when nearby text already says the brand name; the mark is then hidden from assistive technology. With no `svg`, `name` renders as real, selectable text and needs no `role` at all."},{"name":"Video","group":"Media","route":"/docs/components/video","description":"Video player supporting YouTube, Vimeo embeds, and native HTML5 video. Detects provider from URL and builds the correct embed.","importLine":"import { Video } from \"@hyzer-labs/ui\"","props":[{"name":"src","type":"string","default":"—","note":"Required. Native file URL, or a YouTube/Vimeo URL — the provider is detected and the right embed is built."},{"name":"title","type":"string","default":"—","note":"Required for accessibility."},{"name":"aspectRatio","type":"'16/9' | '4/3' | '1/1' | '9/16'","default":"'16/9'"},{"name":"autoplay","type":"boolean","default":"false","note":"Requires muted; suppressed under prefers-reduced-motion."},{"name":"muted","type":"boolean","default":"false"},{"name":"controls","type":"boolean","default":"true"},{"name":"loop","type":"boolean","default":"false"},{"name":"poster","type":"string","default":"—","note":"Native provider only."},{"name":"loading","type":"'lazy' | 'eager'","default":"'lazy'"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-video class."}],"types":[],"hooks":{"root":"hz-video","attrs":[{"name":"data-provider","values":"'youtube' | 'vimeo' | 'native'","note":"Detected from src. Styling hook for provider-specific chrome."},{"name":"data-aspect-ratio","values":"'16/9' | '4/3' | '1/1' | '9/16'","note":"Mirrors the prop; the ratio lands as an inline style. Closed union — unlike Image, there is no auto. Default 16/9."},{"name":"data-state","values":"'idle' | 'playing' | 'paused' | 'ended'","note":"Read-only playback state. Native embeds only — a YouTube or Vimeo iframe stays idle, because the provider owns those controls."}],"parts":[{"name":".hz-video__el","values":"child element","note":"The player — the iframe or the native video, whichever the src resolved to. One class across all three branches, so it fills the ratio box either way."}]},"a11yNote":"The `title` prop is required and maps to the iframe title or video aria-label. `autoplay` requires `muted`, both because browsers refuse to autoplay sound and because motion a reader did not ask for is disorienting."},{"name":"Form","group":"Forms","route":"/docs/components/form","description":"A form wrapper that renders an accessible error summary and moves focus on failed submits, while staying out of the way of native submission and SvelteKit's use:enhance.","importLine":"import { Form, toFormErrors } from \"@hyzer-labs/ui\"","props":[{"name":"errors","type":"FormError[]","default":"[]","note":"The summary renders when non-empty. See FormError below."},{"name":"onSubmit","type":"(e: SubmitEvent) => void","default":"—","note":"Omit for native submission / use:enhance (the default path); provide it for client-side validation — preventDefault runs first."},{"name":"summaryTitle","type":"string | ((count: number) => string)","default":"'There is a problem'","note":"A string overrides the default outright; a function receives the error count. The default is count-aware and plural-safe: \"There is a problem\" for 1, \"There are 3 problems\" for 3."},{"name":"summaryHeadingLevel","type":"2 | 3 | 4 | 5 | 6","default":"2","note":"Match the page's heading structure."},{"name":"focusTarget","type":"'summary' | 'firstField'","default":"'summary'","note":"What receives focus after a submit with errors."},{"name":"novalidate","type":"boolean","default":"false"},{"name":"ariaLabel","type":"string","default":"—","note":"Names the form landmark."},{"name":"children","type":"Snippet","default":"—","note":"Required. The form content."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-form class."}],"types":[{"name":"FormError","props":[{"name":"name","type":"string","default":"—","note":"Field name to link to. Empty or unresolved names become non-interactive form-level errors, listed last."},{"name":"message","type":"string","default":"—","note":"Required."}]}],"hooks":{"root":"hz-form","attrs":[{"name":"data-state","values":"'error' | absent","note":"Present once the summary has errors. Two-state, unlike the field family’s three — a form is never disabled."}],"parts":[{"name":".hz-form-error-summary","values":"on an Alert","note":"The summary box — an Alert underneath. Load-bearing beyond styling: the focus-on-submit path queries this class."},{"name":".hz-form-error-list","values":"child element","note":"The list of errors."},{"name":".hz-form-error-summary-item","values":"child element","note":"One error row, including its jump link."}]},"a11yNote":"The error summary is the first child of the form and has `role=\"alert\"`, so it is announced when it appears. It takes focus when errors arrive after a submit. With `focusTarget=\"firstField\"`, the first invalid field takes focus instead.\n\nEach summary item links to its field and focuses it on activation, scrolling smoothly unless `prefers-reduced-motion` is set.\n\nSet `summaryHeadingLevel` so the summary title fits your page's heading outline."},{"name":"TextInput","group":"Forms","route":"/docs/components/text-input","description":"A labeled single-line input covering the common HTML input types, with description, inline error, and decorative prefix and suffix slots.","importLine":"import { TextInput } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Form field name."},{"name":"label","type":"string","default":"—","note":"Required. Always in the DOM; sr-only with hideLabel."},{"name":"type","type":"'text' | 'email' | 'password' | 'tel' | 'url' | 'search' | 'number' | 'date' | 'time' | 'datetime-local'","default":"'text'","note":"Date/time types render the native platform picker."},{"name":"value","type":"string","default":"''","note":"Bindable."},{"name":"placeholder","type":"string","default":"—"},{"name":"autocomplete","type":"HTMLInputAttributes['autocomplete']","default":"—","note":"Native autofill hint, e.g. \"email\", \"name\"."},{"name":"maxlength","type":"number","default":"—"},{"name":"pattern","type":"string","default":"—","note":"Native validation regex."},{"name":"inputmode","type":"HTMLInputAttributes['inputmode']","default":"—","note":"Virtual-keyboard hint, e.g. \"numeric\", \"tel\"."},{"name":"description","type":"string","default":"—","note":"Help text below the label."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state."},{"name":"required","type":"boolean","default":"false"},{"name":"disabled","type":"boolean","default":"false"},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"prefix","type":"Snippet","default":"—","note":"Decorative leading content (icon, symbol); rendered aria-hidden."},{"name":"suffix","type":"Snippet","default":"—","note":"Decorative trailing content (unit, icon); rendered aria-hidden."},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field class."}],"types":[],"hooks":{"root":"hz-field","attrs":[{"name":"data-state","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, on the root. Precedence is fixed: error beats disabled."},{"name":"data-has-prefix","values":"present when a prefix is set","note":"On .hz-input-wrapper. The reference theme ships no rule for it — it exists so you can, e.g., retune padding when an affix is present."},{"name":"data-has-suffix","values":"present when a suffix is set","note":"On .hz-input-wrapper. Same: yours to use."}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."},{"name":".hz-input-wrapper","values":"child element","note":"The bordered box. It carries the border, so prefix and suffix sit inside it."},{"name":".hz-input-prefix","values":"child element","note":"The leading affix."},{"name":".hz-input-suffix","values":"child element","note":"The trailing affix."}]},"a11yNote":"The input is associated with its label via `id`/`for`. With `hideLabel`, the label stays in the DOM as screen-reader-only text.\n\n`description` and `error` chain into `aria-describedby`, description first. `required` sets `aria-required`, and an `error` sets `aria-invalid`.\n\n`prefix` and `suffix` are rendered `aria-hidden`. Keep them decorative, and put any meaning in the label or description instead."},{"name":"Textarea","group":"Forms","route":"/docs/components/textarea","description":"A labeled multi-line text area with configurable resize behavior, including an auto-grow mode, plus description and inline error.","importLine":"import { Textarea } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Form field name."},{"name":"label","type":"string","default":"—","note":"Required. Always in the DOM; sr-only with hideLabel."},{"name":"value","type":"string","default":"''","note":"Bindable."},{"name":"rows","type":"number","default":"3"},{"name":"resize","type":"'none' | 'vertical' | 'both' | 'auto'","default":"'vertical'","note":"'vertical' and 'auto' grow with content; 'vertical' keeps the drag handle, 'auto' hides it. rows is the minimum height."},{"name":"maxlength","type":"number","default":"—"},{"name":"description","type":"string","default":"—","note":"Help text below the label."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state."},{"name":"required","type":"boolean","default":"false"},{"name":"disabled","type":"boolean","default":"false"},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field class."}],"types":[],"hooks":{"root":"hz-field","attrs":[{"name":"data-state","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, on the root. Precedence is fixed: error beats disabled."},{"name":"data-resize","values":"'none' | 'vertical' | 'both' | 'auto'","note":"On the textarea. Default vertical; auto uses field-sizing: content."}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."}]},"a11yNote":"The textarea is associated with its label via `id`/`for`. With `hideLabel`, the label stays in the DOM as screen-reader-only text.\n\n`description` and `error` chain into `aria-describedby`, description first. `required` sets `aria-required`, and an `error` sets `aria-invalid`."},{"name":"Checkbox","group":"Forms","route":"/docs/components/checkbox","description":"A labeled checkbox with description, inline error, and an indeterminate state for select-all patterns.","importLine":"import { Checkbox } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Form field name."},{"name":"label","type":"string","default":"—","note":"Required. Always in the DOM; sr-only with hideLabel."},{"name":"checked","type":"boolean","default":"false","note":"Bindable."},{"name":"indeterminate","type":"boolean","default":"false","note":"Set via the DOM .indeterminate property; visual only, checked is unaffected."},{"name":"value","type":"string","default":"—","note":"Submitted value when checked."},{"name":"description","type":"string","default":"—","note":"Help text below the label."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state."},{"name":"required","type":"boolean","default":"false"},{"name":"disabled","type":"boolean","default":"false"},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field hz-field--checkbox classes."}],"types":[],"hooks":{"root":"hz-field hz-field--checkbox","attrs":[{"name":"data-state","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, on the root. Precedence is fixed: error beats disabled."},{"name":":checked / :indeterminate","values":"native pseudo-classes","note":"Checkbox stamps no attribute for either — style the native pseudo-classes. (indeterminate is set as a DOM property, so only :indeterminate sees it.)"}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."}]},"a11yNote":"The input is associated with its label via `id`/`for` (the label renders after the box). `description` and `error` chain into `aria-describedby`, description first. `required` sets `aria-required`, and an `error` sets `aria-invalid`.\n\n`indeterminate` is applied through the DOM `.indeterminate` property, so screen readers announce the mixed state natively."},{"name":"RadioGroup","group":"Forms","route":"/docs/components/radio-group","description":"A group of radio buttons in a fieldset with a legend, vertical or horizontal layout, and standard field accessibility.","importLine":"import { RadioGroup } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Shared by every radio in the group."},{"name":"label","type":"string","default":"—","note":"Required. Rendered as the fieldset legend; sr-only with hideLabel."},{"name":"options","type":"FormOption[]","default":"—","note":"Required. See FormOption below."},{"name":"value","type":"string","default":"''","note":"Bindable. The selected option value."},{"name":"orientation","type":"'horizontal' | 'vertical'","default":"'vertical'"},{"name":"description","type":"string","default":"—","note":"Help text below the legend."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state."},{"name":"required","type":"boolean","default":"false"},{"name":"disabled","type":"boolean","default":"false","note":"Disables the whole group."},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field hz-field--radio-group classes."}],"types":[{"name":"FormOption","props":[{"name":"value","type":"string","default":"—","note":"Required."},{"name":"label","type":"string","default":"—","note":"Required."},{"name":"disabled","type":"boolean","default":"—","note":"Disables only this option."}]}],"hooks":{"root":"hz-field hz-field--radio-group","attrs":[{"name":"data-state","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, on the root. Precedence is fixed: error beats disabled."},{"name":"data-orientation","values":"'horizontal' | 'vertical'","note":"Default vertical. The root is a fieldset and the label is its legend."}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."},{"name":".hz-radio-options","values":"child element","note":"The radiogroup container."},{"name":".hz-radio-option","values":"child element","note":"One option row."}]},"a11yNote":"The group is a `<fieldset>` whose `label` renders as the `<legend>`. With `hideLabel`, the legend stays in the DOM as screen-reader-only text. Each radio has its own label.\n\nThe options sit in an inner `role=\"radiogroup\"` container that carries `aria-describedby` (description first, then error), `aria-invalid` on error, and `aria-required` when required.\n\nArrow-key movement between radios is native browser behavior."},{"name":"Toggle","group":"Forms","route":"/docs/components/toggle","description":"A switch for binary on and off settings. It is a native checkbox exposed with the switch role, so it submits a form value like any other field.","importLine":"import { Toggle } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Form field name."},{"name":"label","type":"string","default":"—","note":"Required. Always in the DOM; sr-only with hideLabel."},{"name":"checked","type":"boolean","default":"false","note":"Bindable."},{"name":"value","type":"string","default":"—","note":"Submitted value when on."},{"name":"description","type":"string","default":"—","note":"Help text on its own row."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state."},{"name":"required","type":"boolean","default":"false"},{"name":"disabled","type":"boolean","default":"false"},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field hz-field--toggle classes."}],"types":[],"hooks":{"root":"hz-field hz-field--toggle","warning":"`.hz-field--toggle input.hz-toggle` — the track and thumb — is the one rule in the reference theme shipped outside `@layer hz-theme`, on purpose: headless, Toggle is a bare native checkbox, so this rule needs to out-rank the component's own scoped structural reset unconditionally, and a layer can only lose to an unlayered rule regardless of specificity. It sits at a raw `(0,2,1)` specificity. A normal override written inside a layer — or even an unlayered rule that ties on specificity and loses on source order — silently does nothing; the toggle keeps its default look with no error. To win reliably, give your override enough specificity to clear `(0,2,1)` outright, e.g. by scoping it under your own theme's root class: `.your-theme .hz-field--toggle input.hz-toggle { … }`. See the Terminal example theme for a worked example (`theme/examples/terminal/components/toggle.css`), which does exactly this. The same applies to the `--hz-toggle-width` / `--hz-toggle-height` custom properties below — they're declared directly on that selector, so setting them from a lower-specificity or losing rule never reaches the element.","attrs":[{"name":"data-state (on the root)","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, as on every field. Precedence is fixed: error beats disabled."},{"name":"data-state (on input.hz-toggle)","values":"'on' | 'off'","note":"A second data-state nested inside the root’s, with a disjoint vocabulary — this is the stable hook for the track and thumb. It exists rather than relying on :checked because the toggle rules must win the specificity fight described above, and an attribute selector composes into them cleanly."}],"props":[{"name":"--hz-toggle-width","values":"<length> — default 2.5rem","note":"Track width, and the distance the thumb travels."},{"name":"--hz-toggle-height","values":"<length> — default 1.5rem","note":"Track height. The thumb derives from it (height minus a 2px inset all round), so there is no separate thumb knob — and the travel stays correct under any override."}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."},{"name":".hz-toggle","values":"the checkbox input","note":"The track. Its ::before is the thumb, so the thumb has no class of its own."}]},"a11yNote":"Toggle renders an `<input type=\"checkbox\" role=\"switch\">` associated with its label via `id`/`for`. Screen readers announce it as a switch, while the native checked state, keyboard behavior (Space toggles, Enter submits the form), and form participation all come from the platform.\n\n`description` and `error` chain into `aria-describedby`, description first. `required` sets `aria-required`, and an `error` sets `aria-invalid`."},{"name":"Select","group":"Forms","route":"/docs/components/select","description":"A labeled native select with flat options, option groups, a placeholder option, and standard field accessibility.","importLine":"import { Select } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Form field name."},{"name":"label","type":"string","default":"—","note":"Required. Always in the DOM; sr-only with hideLabel."},{"name":"options","type":"SelectOption[]","default":"—","note":"Required. Flat options and optgroups mix freely — see SelectOption below."},{"name":"multiple","type":"boolean","default":"false","note":"Renders the native multiple attribute; switches value to string[]."},{"name":"value","type":"string | string[]","default":"'' (single) / [] (multiple)","note":"Bindable. A discriminated union on multiple: string when omitted/false, string[] when true."},{"name":"placeholder","type":"string","default":"'Select...'","note":"Rendered as a disabled leading option."},{"name":"description","type":"string","default":"—","note":"Help text below the label."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state."},{"name":"required","type":"boolean","default":"false"},{"name":"disabled","type":"boolean","default":"false"},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field class."}],"types":[{"name":"SelectOption (flat arm: FormOption)","props":[{"name":"value","type":"string","default":"—","note":"Required."},{"name":"label","type":"string","default":"—","note":"Required."},{"name":"disabled","type":"boolean","default":"—","note":"—"}]},{"name":"SelectOption (group arm)","props":[{"name":"group","type":"string","default":"—","note":"Group form: renders an <optgroup> with this label."},{"name":"options","type":"FormOption[]","default":"—","note":"Group form: the flat options inside the group."}]}],"hooks":{"root":"hz-field","attrs":[{"name":"data-state","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, on the root. Precedence is fixed: error beats disabled."}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."}]},"a11yNote":"The select is associated with its label via `id`/`for`. With `hideLabel`, the label stays in the DOM as screen-reader-only text.\n\n`description` and `error` chain into `aria-describedby`, description first. `required` sets `aria-required`, and an `error` sets `aria-invalid`.\n\nThe control is the native `<select>`, so keyboard and assistive-tech behavior come from the platform."},{"name":"Combobox","group":"Forms","route":"/docs/components/combobox","description":"A multi-select, filterable text input that follows the WAI-ARIA APG combobox (list-autocomplete) pattern, with each pick rendered as a dismissible chip.","importLine":"import { Combobox } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Repeated on every hidden input."},{"name":"label","type":"string","default":"—","note":"Required. Always in the DOM; sr-only with hideLabel."},{"name":"options","type":"FormOption[]","default":"—","note":"Required. Flat, multi-select — see FormOption below."},{"name":"value","type":"string[]","default":"[]","note":"Bindable. Selected option values, in selection order."},{"name":"placeholder","type":"string","default":"'Search...'","note":"Shown only while nothing is selected — once chips exist, they say it better."},{"name":"filter","type":"(query: string, option: FormOption) => boolean","default":"—","note":"Overrides the default case-insensitive substring match."},{"name":"emptyText","type":"string","default":"'No results'"},{"name":"toggleLabel","type":"string","default":"'Show options'"},{"name":"chipProps","type":"ComboboxChipProps","default":"{}","note":"Styling applied to every chip — see ComboboxChipProps below. Resolves to size: 'sm'."},{"name":"onchange","type":"(value: string[]) => void","default":"—","note":"Fires on every selection change: toggle, chip dismiss, or Backspace removal."},{"name":"description","type":"string","default":"—","note":"Help text below the label."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state."},{"name":"required","type":"boolean","default":"false"},{"name":"disabled","type":"boolean","default":"false"},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field hz-combobox classes."}],"types":[{"name":"FormOption","props":[{"name":"value","type":"string","default":"—","note":"Required."},{"name":"label","type":"string","default":"—","note":"Required. Shown on the chip and in the option row."},{"name":"disabled","type":"boolean","default":"—","note":"Inert — visible but never toggleable; an existing selection stays removable."}]},{"name":"ComboboxChipProps","props":[{"name":"intent","type":"Intent","default":"'neutral'","note":"See Foundation → Colors & Intent.","noteHref":"/docs/foundation/colors#intent"},{"name":"variant","type":"'soft' | 'solid' | 'outline'","default":"'soft'"},{"name":"size","type":"'sm' | 'md'","default":"'sm'","note":"Combobox's own default overrides Badge's md default."},{"name":"rounded","type":"'none' | 'sm' | 'md' | 'lg' | 'full'","default":"'full'"},{"name":"class","type":"string","default":"—"}]}],"hooks":{"root":"hz-field hz-combobox","attrs":[{"name":"data-state","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, on the root. Precedence is fixed: error beats disabled."},{"name":"data-open","values":"present while the popup is open","note":"On the root. Rotates the toggle chevron and gates the popup."},{"name":"data-active","values":"present on the active option","note":"Virtual focus — the highlighted option. Real focus stays in the input."},{"name":"data-selected","values":"present on selected options","note":"Selected options; the theme adds a checkmark."},{"name":"data-disabled","values":"present on disabled options","note":"Per-option. Disabled options stay focusable (aria-disabled, not the native attribute)."}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."},{"name":".hz-combobox-control","values":"child element","note":"The bordered row holding the input, chips, and toggle."},{"name":".hz-combobox-input","values":"child element","note":"The text input."},{"name":".hz-combobox-toggle","values":"child element","note":"The chevron button."},{"name":".hz-combobox-popup","values":"child element","note":"The floating panel."},{"name":".hz-combobox-listbox","values":"child element","note":"The option list."},{"name":".hz-combobox-option","values":"child element","note":"One option."},{"name":".hz-combobox-empty","values":"child element","note":"The no-results message."}]},"a11yNote":"The visible input carries `role=combobox` with `aria-autocomplete=list`, `aria-expanded`, and `aria-controls` pointing at the `role=listbox` popup, which is `aria-multiselectable`. DOM focus never moves into the list: the highlighted option is tracked as virtual focus with `aria-activedescendant` on the input, so screen readers announce the active option while the field stays editable.\n\n`description`/`error` chain into `aria-describedby` on the input (description first), still computed even while the popup visually overlays that region. `required` sets `aria-required`, and an `error` sets `aria-invalid`.\n\nEach selected value renders as a `Badge` chip inside the control. Every chip has a labeled dismiss button, announced as Remove followed by the option's label. Each of those buttons is a real tab stop of its own, so tabbing into the field reaches the chips before the input.\n\nKeyboard: `ArrowDown`/`ArrowUp` open the popup and move the active option, wrapping and skipping disabled entries. `Alt+ArrowDown` opens without moving, and `Alt+ArrowUp` closes without changing anything. `Home`/`End` jump to the first and last enabled option while open (they're native text-cursor moves while closed). `Enter` toggles the active option's membership and keeps the popup open so you can keep picking. `Backspace` on an empty query removes the last chip. `Escape` closes the popup and clears the filter query. It never clears a selection, since every chip has its own dismiss button. `Tab` closes the popup and lets focus move on.\n\nThe input is the only tab stop that opens the popup. The trailing toggle button is `tabindex=-1`, reachable by pointer only."},{"name":"FileUpload","group":"Forms","route":"/docs/components/file-upload","description":"A file selection field backed by a real native input, single or multiple, with accept, maxSize, and maxFiles validation, a removable file list, and an optional drag-and-drop dropzone. It selects files rather than uploading them: the named input carries the chosen files into a plain form submission or into your own upload code.","importLine":"import { FileUpload } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Carried by the native input."},{"name":"label","type":"string","default":"—","note":"Required. Always in the DOM; sr-only with hideLabel."},{"name":"files","type":"File[]","default":"[]","note":"Bindable. Selected files in selection order — not a FileList."},{"name":"multiple","type":"boolean","default":"false","note":"Single mode replaces on each pick; multiple mode appends + de-dupes."},{"name":"accept","type":"string","default":"—","note":"Native accept syntax: extensions, exact MIME types, or wildcard MIME types."},{"name":"maxSize","type":"number","default":"—","note":"Bytes, per file. No total-size cap."},{"name":"maxFiles","type":"number","default":"—","note":"Multiple mode only — ignored in single mode (the cap is always 1)."},{"name":"dropzone","type":"boolean","default":"false","note":"Swaps the visible input for a drag-and-drop surface + activation button."},{"name":"buttonText","type":"string","default":"'Browse files'","note":"Dropzone mode only."},{"name":"dropzoneText","type":"string","default":"'Drag and drop files here, or'","note":"Dropzone mode only."},{"name":"onchange","type":"(files: File[]) => void","default":"—","note":"Fires on every accepted change: add, remove, or single-mode replace."},{"name":"onreject","type":"(rejections: FileRejection[]) => void","default":"—","note":"Fires whenever a pick or drop yields rejected files — see FileRejection below."},{"name":"description","type":"string","default":"—","note":"Help text below the label."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state. The component never sets this itself."},{"name":"required","type":"boolean","default":"false"},{"name":"disabled","type":"boolean","default":"false"},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field hz-file-upload classes."}],"types":[{"name":"FileRejection","props":[{"name":"file","type":"File","default":"—","note":"The rejected file."},{"name":"reason","type":"'type' | 'size' | 'too-many'","default":"—","note":"Accept mismatch, over maxSize, or beyond the count cap."},{"name":"message","type":"string","default":"—","note":"A ready-to-display English string."}]}],"hooks":{"root":"hz-field hz-file-upload","attrs":[{"name":"data-state","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, on the root. Precedence is fixed: error beats disabled."},{"name":"data-dropzone","values":"present in dropzone mode","note":"On the root. The reference theme discriminates the two modes by targeting the parts instead, so this one is yours."},{"name":"data-dragover","values":"present while dragging over","note":"On .hz-file-dropzone. Backed by a depth counter, so it survives dragging across child elements."}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."},{"name":".hz-file-control","values":"child element","note":"The box in basic mode."},{"name":".hz-file-dropzone","values":"child element","note":"The box in dropzone mode."},{"name":".hz-file-dropzone-text","values":"child element","note":"The dropzone prompt."},{"name":".hz-file-button","values":"child element","note":"The browse button."},{"name":".hz-file-list","values":"child element","note":"The selected-file list."},{"name":".hz-file-item","values":"child element","note":"One file row."},{"name":".hz-file-name","values":"child element","note":"The file name."},{"name":".hz-file-size","values":"child element","note":"The formatted size."},{"name":".hz-file-remove","values":"child element","note":"The per-file remove button. Also load-bearing: focus placement after a removal queries it."}]},"a11yNote":"Drag-and-drop is a progressive enhancement, so the non-drag alternative is always present. In basic mode, the visible native input opens the platform picker directly. In dropzone mode, a real `<button>` (`buttonText`) opens the same picker. No function ever depends on a dragging movement (WCAG 2.5.7).\n\nThe native input is labeled by the field's `<label for>`. In dropzone mode the input is `aria-hidden` and out of the tab order, because the button is the control you operate. `description` and `error` chain into `aria-describedby` on the input, description first. `required` sets `aria-required`, and an `error` sets `aria-invalid`.\n\nEach selected file's remove button carries its own `Remove` label naming that file. Additions, removals, and rejection counts are announced through a polite `aria-live` status region. That region sits outside the file list, so screen reader users hear changes the re-rendered list alone would not surface.\n\nThe theme's dragover state changes the background and the border, not the color alone."},{"name":"Slider","group":"Forms","route":"/docs/components/slider","description":"A labeled range slider paired with a synced number field for fine-tuned keyboard entry. Typed values commit on change, snapped to the step and clamped to the range.","importLine":"import { Slider } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Form field name (on the range input)."},{"name":"label","type":"string","default":"—","note":"Required. Labels the range; sr-only with hideLabel."},{"name":"min","type":"number","default":"0"},{"name":"max","type":"number","default":"100"},{"name":"step","type":"number","default":"1"},{"name":"value","type":"number","default":"min","note":"Bindable."},{"name":"showInput","type":"boolean","default":"true","note":"Renders the exact-entry number field next to the slider."},{"name":"ticks","type":"SliderTick[]","default":"—","note":"Decorative marks under the track. See SliderTick below."},{"name":"unit","type":"string","default":"—","note":"Visual suffix after the number field; rendered aria-hidden."},{"name":"inputLabel","type":"string","default":"`${label} (exact value)`","note":"Accessible name for the number field."},{"name":"orientation","type":"'horizontal' | 'vertical'","default":"'horizontal'","note":"Vertical uses writing-mode: vertical-lr (bottom-up growth); horizontal is unchanged."},{"name":"inputPosition","type":"'start' | 'end'","default":"'end'","note":"Logical on both axes: horizontal trailing/leading, vertical below/above the track."},{"name":"description","type":"string","default":"—","note":"Help text below the label."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state."},{"name":"required","type":"boolean","default":"false","note":"Label indicator only."},{"name":"disabled","type":"boolean","default":"false","note":"Disables both inputs."},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field hz-field--slider classes."}],"types":[{"name":"SliderTick","props":[{"name":"value","type":"number","default":"—","note":"Required. A bare number is shorthand for an unlabeled tick. Out-of-range ticks are skipped."},{"name":"label","type":"string","default":"—","note":"Small text under the mark."}]}],"hooks":{"root":"hz-field hz-field--slider","attrs":[{"name":"data-state","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, on the root. Precedence is fixed: error beats disabled."},{"name":"data-has-ticks","values":"present when ticks render","note":"On .hz-slider-row; reserves room for the tick strip. Reflects the normalized list — out-of-range ticks are dropped — so it is not derivable from the prop."},{"name":"data-has-input","values":"present when the number field shows","note":"On .hz-slider-row. The reference theme targets the parts instead, so this one is yours."},{"name":"data-orientation","values":"'horizontal' | 'vertical'","note":"On .hz-slider-row; always present. Drives the writing-mode-based vertical mechanism and the theme rotation (fill, ticks). Default horizontal."},{"name":"data-input-position","values":"'start' | 'end'","note":"On .hz-slider-row; always present. Logical on both axes — reorders the track vs. the number field/readout. Default end."}],"props":[{"name":"--hz-slider-track-height","values":"<length> — default 0.375rem","note":"Track thickness."},{"name":"--hz-slider-thumb-size","values":"<length> — default 1.125rem","note":"Thumb width and height. The fill edge tracks the thumb centre, so the fill and ticks stay aligned at any size."},{"name":"--hz-slider-length","values":"<length> — default 12rem","note":"The track's block length in vertical orientation. Ignored in horizontal, where the track flexes to fill the row instead."}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."},{"name":".hz-slider-row","values":"child element","note":"The track-and-readout row."},{"name":".hz-slider-track","values":"child element","note":"The painted track."},{"name":".hz-slider","values":"the range input","note":"The native input carrying the thumb."},{"name":".hz-slider-ticks","values":"child element","note":"The tick strip."},{"name":".hz-slider-tick","values":"child element","note":"One tick."},{"name":".hz-slider-tick-label","values":"child element","note":"A tick’s label."},{"name":".hz-slider-number","values":"child element","note":"The editable number field."},{"name":".hz-slider-value","values":"child element","note":"The read-only readout."},{"name":".hz-slider-unit","values":"child element","note":"The unit suffix."}]},"a11yNote":"The range input is the labeled (`id`/`for`), named, form-participating control. Its slider semantics (value, min, max) are native, and arrow keys step it.\n\nThe number field is an exact-entry affordance with its own accessible name (`inputLabel`) and no `name`, so it never submits.\n\n`description` and `error` chain into `aria-describedby` on the range (description first), and an `error` sets `aria-invalid`. `required` renders the label indicator only. `aria-required` is not part of the slider role's supported ARIA attributes, so it is never applied.\n\n`unit` is decorative and `aria-hidden`. Put meaning-bearing units in the label or description instead. In vertical orientation the component stamps `aria-orientation=\"vertical\"` on the range explicitly, since writing-mode does not reliably flip a native range's implicit `horizontal` orientation in the accessibility tree across engines and assistive tech."},{"name":"RangeSlider","group":"Forms","route":"/docs/components/range-slider","description":"A dual-thumb slider that selects a min to max interval on one track, with paired number fields for exact entry. The thumbs can meet but never cross.","importLine":"import { RangeSlider } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Base name — the thumbs submit as {name}-min and {name}-max."},{"name":"label","type":"string","default":"—","note":"Required. Rendered as the fieldset legend; sr-only with hideLabel."},{"name":"min","type":"number","default":"0"},{"name":"max","type":"number","default":"100"},{"name":"step","type":"number","default":"1"},{"name":"valueMin","type":"number","default":"min","note":"Bindable. The lower thumb."},{"name":"valueMax","type":"number","default":"max","note":"Bindable. The upper thumb."},{"name":"showInput","type":"boolean","default":"true","note":"Renders the pair of exact-entry number fields."},{"name":"ticks","type":"SliderTick[]","default":"—","note":"Decorative marks under the track. See SliderTick below."},{"name":"unit","type":"string","default":"—","note":"One visual suffix after the pair; rendered aria-hidden."},{"name":"minThumbLabel","type":"string","default":"`${label} (minimum)`","note":"Accessible name for the lower thumb (and its number field)."},{"name":"maxThumbLabel","type":"string","default":"`${label} (maximum)`","note":"Accessible name for the upper thumb (and its number field)."},{"name":"orientation","type":"'horizontal' | 'vertical'","default":"'horizontal'","note":"Vertical uses writing-mode: vertical-lr (bottom-up growth) on both ranges; horizontal is unchanged."},{"name":"inputPosition","type":"'start' | 'end'","default":"'end'","note":"Logical on both axes. The two exact-entry fields stay one inline cluster, placed above or below the track in vertical rather than stacked to mirror the thumbs."},{"name":"description","type":"string","default":"—","note":"Help text below the legend."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state."},{"name":"required","type":"boolean","default":"false","note":"Legend indicator only."},{"name":"disabled","type":"boolean","default":"false","note":"Disables all four inputs."},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field hz-field--slider hz-field--slider-range classes."}],"types":[{"name":"SliderTick","props":[{"name":"value","type":"number","default":"—","note":"Required. A bare number is shorthand for an unlabeled tick. Out-of-range ticks are skipped."},{"name":"label","type":"string","default":"—","note":"Small text under the mark."}]}],"hooks":{"root":"hz-field hz-field--slider hz-field--slider-range","attrs":[{"name":"data-state","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, on the root. Precedence is fixed: error beats disabled."},{"name":"data-has-ticks","values":"present when ticks render","note":"On .hz-slider-row, as on Slider."},{"name":"data-has-input","values":"present when the number fields show","note":"On .hz-slider-row. Unstyled by the reference theme."},{"name":"data-orientation","values":"'horizontal' | 'vertical'","note":"On .hz-slider-row; always present, as on Slider. Both ranges pick up writing-mode: vertical-lr; direction: rtl."},{"name":"data-input-position","values":"'start' | 'end'","note":"On .hz-slider-row; always present, as on Slider. The min–max pair stays one inline cluster, reordered as a unit."}],"props":[{"name":"--hz-slider-track-height","values":"<length> — default 0.375rem","note":"Track thickness."},{"name":"--hz-slider-thumb-size","values":"<length> — default 1.125rem","note":"Thumb width and height. The fill edge tracks the thumb centre, so the fill and ticks stay aligned at any size."},{"name":"--hz-slider-length","values":"<length> — default 12rem","note":"The track's block length in vertical orientation. Ignored in horizontal, where the track flexes to fill the row instead."}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."},{"name":".hz-slider-row","values":"child element","note":"The track-and-readout row."},{"name":".hz-slider-track","values":"child element","note":"Paints the track and the between-thumbs fill."},{"name":".hz-slider-min","values":"the lower range input","note":"Carries .hz-slider too."},{"name":".hz-slider-max","values":"the upper range input","note":"Carries .hz-slider too."},{"name":".hz-slider-inputs","values":"child element","note":"Wraps the number-field pair (or the readout) so it stays one inline cluster regardless of orientation."},{"name":".hz-slider-number-min","values":"child element","note":"The lower number field."},{"name":".hz-slider-number-max","values":"child element","note":"The upper number field."},{"name":".hz-slider-sep","values":"child element","note":"The dash between the readouts."},{"name":".hz-slider-ticks","values":"child element","note":"The tick strip."}]},"a11yNote":"The group is a `<fieldset>` whose `label` renders as the `<legend>`. Each thumb is a real range input with its own accessible name (`minThumbLabel`/`maxThumbLabel`) and native slider semantics. Tab reaches both thumbs, arrow keys step them, and a thumb dragged past its partner clamps rather than crossing.\n\nThe number fields carry derived accessible names and never submit. The two thumbs submit as the base `name` plus `-min`/`-max` suffixes.\n\n`description` and `error` chain into `aria-describedby` on both ranges, and an `error` also sets `aria-invalid` on both. `required` renders the legend indicator only. `aria-required` is not part of the slider role's supported ARIA attributes, so it is never applied.\n\nTicks, the separator, and the readout are decorative and `aria-hidden`. In vertical orientation the component stamps `aria-orientation=\"vertical\"` on both ranges explicitly, since writing-mode does not reliably flip a native range's implicit `horizontal` orientation in the accessibility tree across engines and assistive tech."},{"name":"ColorInput","group":"Forms","route":"/docs/components/color-input","description":"A labeled native color picker paired with a synced hex field for exact keyboard entry. Typed values commit on change, validated and normalized.","importLine":"import { ColorInput } from \"@hyzer-labs/ui\"","props":[{"name":"name","type":"string","default":"—","note":"Required. Form field name."},{"name":"label","type":"string","default":"—","note":"Required. Always in the DOM; sr-only with hideLabel."},{"name":"value","type":"string","default":"'#000000'","note":"Bindable. The platform normalizes to 7-char lowercase hex."},{"name":"showInput","type":"boolean","default":"true","note":"Renders the exact-entry hex field beside the swatch."},{"name":"inputLabel","type":"string","default":"`${label} (hex value)`","note":"Accessible name for the hex field."},{"name":"description","type":"string","default":"—","note":"Help text below the label."},{"name":"error","type":"string","default":"—","note":"Inline error message; sets the error state."},{"name":"required","type":"boolean","default":"false","note":"Label indicator only."},{"name":"disabled","type":"boolean","default":"false"},{"name":"hideLabel","type":"boolean","default":"false"},{"name":"class","type":"string","default":"—","note":"Merged after the hz-field hz-field--color classes."}],"types":[],"hooks":{"root":"hz-field hz-field--color","attrs":[{"name":"data-state","values":"'default' | 'error' | 'disabled'","note":"The universal field state hook, on the root. Precedence is fixed: error beats disabled."}],"props":[{"name":"--hz-color-swatch-size","values":"<length> — default 2rem","note":"The swatch height; its width follows at 1.5×, so the 3:2 shape is fixed. Declared on input.hz-color — set it there or deeper, not on the .hz-field root, or the local declaration wins over your inherited value."}],"parts":[{"name":".hz-field-label","values":"child element","note":"The label."},{"name":".hz-field-required","values":"child element","note":"The required marker."},{"name":".hz-field-description","values":"child element","note":"The hint text."},{"name":".hz-field-error","values":"child element","note":"The error message."},{"name":".hz-color-row","values":"child element","note":"The swatch-and-readout row."},{"name":".hz-color","values":"the color input","note":"The native swatch."},{"name":".hz-color-hex","values":"child element","note":"The editable hex field."},{"name":".hz-color-value","values":"child element","note":"The read-only hex readout."}]},"a11yNote":"The color input is the labeled (`id`/`for`), named, form-participating control. Activating it opens the platform picker, which owns the picking experience.\n\nThe hex field is there for typing an exact value. It has its own accessible name (`inputLabel`) and no `name`, so it never submits.\n\n`description` and `error` chain into `aria-describedby`, description first, and an `error` sets `aria-invalid`. `required` renders the label indicator only, since a color input always holds a value.\n\nDo not let color alone carry meaning; the visible hex value helps."}]}