# Component reference

> Every component with its import path and prop types, generated from the norns-ui type shims.

> [!NOTE] Generated from `src/types/*.d.ts` in `@human-synthesis/norns-ui` 0.0.13 by `scripts/gen-reference.mjs`. Do not edit this page by hand; fix the shim in norns-ui and re-run `bun run docs:components` there.

Every component under **components/** is auto-imported by name in `.n` files when the consumer wires `presetUI()` into `nornsAutoImport` (`Btn(variant="primary") Save`). Components under **motion/** are opt-in: `import { Reveal } from '@human-synthesis/norns-ui/motion'`. Snippet props (`children`, `header`, `trigger`, …) are filled with `+snippet('name')` blocks in Pug. Every component accepts `class` and merges it with `cn()`.

Live usage of every component: `norns-demo/src/routes/examples/ui/+page.n`.

## Components

[Accordion](#accordion) · [Audio](#audio) · [Autocomplete](#autocomplete) · [Avatar](#avatar) · [AvatarGroup](#avatargroup) · [Badge](#badge) · [Banner](#banner) · [Breadcrumbs](#breadcrumbs) · [Btn](#btn) · [ButtonGroup](#buttongroup) · [Calendar](#calendar) · [Card](#card) · [Carousel](#carousel) · [Checkbox](#checkbox) · [Chip](#chip) · [Collapsible](#collapsible) · [ColorPicker](#colorpicker) · [ContextMenu](#contextmenu) · [CopyButton](#copybutton) · [DataTable](#datatable) · [DatePicker](#datepicker) · [DateRangePicker](#daterangepicker) · [Dialog](#dialog) · [Dropdown](#dropdown) · [Field](#field) · [FieldGroup](#fieldgroup) · [Form](#form) · [GradientText](#gradienttext) · [Header](#header) · [HeroBanner](#herobanner) · [HierarchicalMenu](#hierarchicalmenu) · [Icon](#icon) · [Image](#image) · [Input](#input) · [MegaMenu](#megamenu) · [MultiSelect](#multiselect) · [NumberInput](#numberinput) · [OtpField](#otpfield) · [Pagination](#pagination) · [Popover](#popover) · [Progress](#progress) · [ProgressCircular](#progresscircular) · [Radio](#radio) · [RippleButton](#ripplebutton) · [ScrollArea](#scrollarea) · [Select](#select) · [Separator](#separator) · [Sheet](#sheet) · [ShinyButton](#shinybutton) · [Skeleton](#skeleton) · [Stepper](#stepper) · [Surface](#surface) · [Switch](#switch) · [Tabs](#tabs) · [TagsInput](#tagsinput) · [Textarea](#textarea) · [ThemeToggler](#themetoggler) · [Timeline](#timeline) · [TimePicker](#timepicker) · [ToastProvider](#toastprovider) · [ToggleButton](#togglebutton) · [ToggleButtonGroup](#togglebuttongroup) · [Toolbar](#toolbar) · [Tooltip](#tooltip) · [Tree](#tree) · [Uploader](#uploader) · [Video](#video) · [Window](#window)

### Accordion

`@human-synthesis/norns-ui/components/Accordion.n`

```ts
export type AccordionItem = {
	value: string;
	title: string;
	content?: Snippet;
};

export type AccordionProps = {
	items?: AccordionItem[];
	multiple?: boolean;
	value?: string | string[];
	class?: string;
};
```

### Audio

`@human-synthesis/norns-ui/components/Audio.n`

```ts
export type AudioSource = { src: string; type?: string };

export type AudioProps = {
	src?: string;
	sources?: AudioSource[];
	controls?: boolean;
	autoplay?: boolean;
	loop?: boolean;
	muted?: boolean;
	preload?: 'none' | 'metadata' | 'auto';
	fallback?: string;
	class?: string;
};
```

### Autocomplete

`@human-synthesis/norns-ui/components/Autocomplete.n`

```ts
export type ComboboxItem = { value: string; label: string };

export type AutocompleteProps = {
	items?: ComboboxItem[];
	value?: string;
	open?: boolean;
	placeholder?: string;
	disabled?: boolean;
	name?: string;
	id?: string;
	error?: boolean;
};
```

### Avatar

`@human-synthesis/norns-ui/components/Avatar.n`

```ts
export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';

export type AvatarProps = {
	src?: string;
	/** Display name; first + last initial used as fallback when `src` is missing. */
	name?: string;
	size?: AvatarSize;
	class?: string;
};
```

### AvatarGroup

`@human-synthesis/norns-ui/components/AvatarGroup.n`

```ts
export type AvatarGroupItem = { src?: string; name?: string };

export type AvatarGroupProps = {
	items?: AvatarGroupItem[];
	max?: number;
	size?: AvatarSize;
	label?: string;
	class?: string;
};
```

### Badge

`@human-synthesis/norns-ui/components/Badge.n`

```ts
export type BadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
export type BadgeSize = 'sm' | 'md';

export type BadgeProps = {
	variant?: BadgeVariant;
	size?: BadgeSize;
	children?: Snippet;
	class?: string;
};
```

### Banner

`@human-synthesis/norns-ui/components/Banner.n`

```ts
export type BannerVariant = 'info' | 'success' | 'warning' | 'danger';

export type BannerProps = {
	variant?: BannerVariant;
	/** Iconify icon name (e.g. `"lucide:alert-triangle"`). */
	icon?: string;
	actions?: Snippet;
	children?: Snippet;
	class?: string;
};
```

### Breadcrumbs

`@human-synthesis/norns-ui/components/Breadcrumbs.n`

```ts
export type BreadcrumbItem = {
	label: string;
	href?: string;
};

export type BreadcrumbsProps = {
	items?: BreadcrumbItem[];
	separator?: string;
	class?: string;
};
```

### Btn

`@human-synthesis/norns-ui/components/Btn.n`

```ts
/**
 * Type shim for `Btn.n`. Hand-rolled until we have a proper
 * `svelte-package` build over the Civet+Pug source.
 */


export type BtnVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
export type BtnSize = 'sm' | 'md' | 'lg';

export type BtnProps = Omit<HTMLButtonAttributes, 'class' | 'children'> & {
	variant?: BtnVariant;
	size?: BtnSize;
	loading?: boolean;
	disabled?: boolean;
	type?: 'button' | 'submit' | 'reset';
	/**
	 * Convenience: Iconify icon name (e.g. `"lucide:save"`). Renders an
	 * `<Icon>` in the leading slot. If you also pass a `leading` snippet,
	 * the snippet wins.
	 */
	icon?: string;
	/** When set, renders as an `<a>` styled like the button. */
	href?: string;
	target?: string;
	rel?: string;
	class?: string;
	children?: Snippet;
	leading?: Snippet;
	trailing?: Snippet;
};
```

### ButtonGroup

`@human-synthesis/norns-ui/components/ButtonGroup.n`

```ts
export type ButtonGroupProps = {
	orientation?: 'horizontal' | 'vertical';
	children?: Snippet;
	class?: string;
};
```

### Calendar

`@human-synthesis/norns-ui/components/Calendar.n`

```ts
export type CalendarDateRange = { start: string; end: string };

export type CalendarProps = {
	/** ISO YYYY-MM-DD. Bindable. */
	value?: string;
	/** Range mode. Bindable. Use instead of `value` when `range` is true. */
	rangeValue?: CalendarDateRange;
	range?: boolean;
	/** ISO YYYY-MM-DD inclusive. */
	min?: string;
	max?: string;
	label?: string;
	onchange?: (next: string | CalendarDateRange) => void;
	class?: string;
};
```

### Card

`@human-synthesis/norns-ui/components/Card.n`

```ts
export type CardProps = {
	/** Wrap body in `.card-body` (default true). Set false for full-bleed content. */
	padded?: boolean;
	/** Apply `.card-interactive` hover/focus treatment. Implicit if `href` is set. */
	interactive?: boolean;
	/** Renders as `<a>` instead of `<div>` when set. */
	href?: string;
	target?: string;
	rel?: string;
	header?: Snippet;
	footer?: Snippet;
	children?: Snippet;
	class?: string;
};
```

### Carousel

`@human-synthesis/norns-ui/components/Carousel.n`

```ts
export type CarouselSlide = {
	src?: string;
	alt?: string;
	[key: string]: unknown;
};

export type CarouselProps = {
	slides?: CarouselSlide[];
	index?: number;
	autoplay?: boolean;
	/** Autoplay interval in milliseconds. Default 5000. */
	interval?: number;
	/** Default aspect ratio applied if no explicit `class="aspect-..."` is passed. */
	aspect?: 'video' | 'square' | 'wide' | 'none';
	/** Custom slide renderer; receives `(slide, i)` and runs only for slides without `src`. */
	children?: Snippet<[CarouselSlide, number]>;
	class?: string;
};
```

### Checkbox

`@human-synthesis/norns-ui/components/Checkbox.n`

```ts
export type CheckboxProps = Omit<HTMLInputAttributes, 'class' | 'type' | 'checked'> & {
	checked?: boolean;
	error?: boolean;
	class?: string;
};
```

### Chip

`@human-synthesis/norns-ui/components/Chip.n`

```ts
export type ChipProps = {
	icon?: string;
	removable?: boolean;
	onremove?: (event: MouseEvent) => void;
	children?: Snippet;
	class?: string;
};
```

### Collapsible

`@human-synthesis/norns-ui/components/Collapsible.n`

```ts
export type CollapsibleProps = {
	open?: boolean;
	title?: string;
	trigger?: Snippet;
	children?: Snippet;
	onopenchange?: (open: boolean) => void;
	class?: string;
};
```

### ColorPicker

`@human-synthesis/norns-ui/components/ColorPicker.n`

```ts
export type ColorPickerProps = {
	/** Hex value with leading `#`. Bindable. */
	value?: string;
	disabled?: boolean;
	/** Hide the clipboard-copy button. */
	hideCopy?: boolean;
	name?: string;
	id?: string;
	class?: string;
};
```

### ContextMenu

`@human-synthesis/norns-ui/components/ContextMenu.n`

```ts
export type ContextMenuItem = {
	label?: string;
	icon?: string;
	separator?: boolean;
	disabled?: boolean;
	onSelect?: () => void;
	/**
	 * NOTE: nested submenus are not supported in 0.0.6+ (the property is
	 * accepted but children are ignored). Re-introduce when the upstream
	 * design needs them.
	 */
	children?: ContextMenuItem[];
};

export type ContextMenuProps = {
	items?: ContextMenuItem[];
	trigger?: Snippet;
	triggerClass?: string;
};
```

### CopyButton

`@human-synthesis/norns-ui/components/CopyButton.n`

```ts
export type CopyButtonProps = {
	value?: string | number;
	variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
	size?: 'sm' | 'md' | 'lg';
	icon?: string;
	disabled?: boolean;
	label?: string;
	oncopy?: (value: string | number) => void;
	children?: Snippet;
	class?: string;
};
```

### DataTable

`@human-synthesis/norns-ui/components/DataTable.n`

```ts
export type DataTableColumn = {
	key: string;
	label?: string;
	width?: string;
	class?: string;
	cellClass?: string;
	sortable?: boolean;
};

export type DataTableProps = {
	columns?: DataTableColumn[];
	rows?: Record<string, unknown>[];
	striped?: boolean;
	dense?: boolean;
	stickyHeader?: boolean;
	emptyMessage?: string;
	/** Currently-sorted column key. Bindable. */
	sortKey?: string;
	sortDir?: 'asc' | 'desc';
	onrowclick?: (row: Record<string, unknown>, index: number) => void;
	class?: string;
};
```

### DatePicker

`@human-synthesis/norns-ui/components/DatePicker.n`

```ts
export type DatePickerProps = {
	/** ISO date string (YYYY-MM-DD). Bindable. */
	value?: string;
	/** Whether the popover is open. Bindable. */
	open?: boolean;
	min?: string;
	max?: string;
	placeholder?: string;
	disabled?: boolean;
	/** Close the popover automatically once a date is picked (default true). */
	closeOnSelect?: boolean;
	/** aria-label override; defaults to "Pick a date". */
	label?: string;
	name?: string;
	id?: string;
	error?: boolean;
	class?: string;
};
```

### DateRangePicker

`@human-synthesis/norns-ui/components/DateRangePicker.n`

```ts
export type DateRange = { start: string; end: string };

export type DateRangePickerProps = {
	value?: DateRange;
	/** Whether the popover is open. Bindable. */
	open?: boolean;
	min?: string;
	max?: string;
	placeholder?: string;
	disabled?: boolean;
	class?: string;
};
```

### Dialog

`@human-synthesis/norns-ui/components/Dialog.n`

```ts
export type DialogProps = {
	open?: boolean;
	title?: string;
	description?: string;
	hideClose?: boolean;
	/** Close on overlay click and Escape. Default true. */
	dismissable?: boolean;
	/** Used as `aria-label` on the dialog when no `title` is provided. */
	ariaLabel?: string;
	trigger?: Snippet;
	actions?: Snippet;
	children?: Snippet;
	triggerClass?: string;
	overlayClass?: string;
	class?: string;
};
```

### Dropdown

`@human-synthesis/norns-ui/components/Dropdown.n`

```ts
export type DropdownItem = {
	label?: string;
	icon?: string;
	separator?: boolean;
	disabled?: boolean;
	onSelect?: () => void;
};

export type DropdownProps = {
	open?: boolean;
	side?: 'top' | 'right' | 'bottom' | 'left';
	align?: 'start' | 'center' | 'end';
	sideOffset?: number;
	items?: DropdownItem[];
	trigger?: Snippet;
	children?: Snippet;
	triggerClass?: string;
	class?: string;
};
```

### Field

`@human-synthesis/norns-ui/components/Field.n`

```ts
export type FieldProps = {
	/** Label text rendered above the control. Used as id-derivation fallback if no `id`/`name` is given. */
	label?: string;
	/** Helper text shown below the control when there's no error. */
	help?: string;
	/**
	 * Explicit error message. Overrides any auto-resolved error from the
	 * parent `<Form form={...}>` context. Switches the inner control to
	 * error styling.
	 */
	error?: string;
	/**
	 * Field name — used to look up an auto-error from the parent `<Form>`
	 * context (matching `form.errors[*].path[0].key`) and as the default
	 * id for the inner control. When set, an inner `<Input>` doesn't need
	 * an explicit `name=` either if it inherits from this Field.
	 */
	name?: string;
	/** Adds a red asterisk after the label. Cosmetic; pair with `required` on the actual input. */
	required?: boolean;
	/** Explicit id for the control. Otherwise: `name`, then slugified `label`, then undefined. */
	id?: string;
	/** Optional class merged into the field wrapper. */
	class?: string;
	/** Snippet that renders the actual control. Children should call `<Input />`, `<Textarea />`, etc. */
	children?: Snippet;
};
```

### FieldGroup

`@human-synthesis/norns-ui/components/FieldGroup.n`

```ts
export type FieldGroupProps = {
	/** Optional legend text rendered as the fieldset's `<legend>`. */
	legend?: string;
	class?: string;
	children?: Snippet;
};
```

### Form

`@human-synthesis/norns-ui/components/Form.n`

```ts
/**
 * The shape returned by `fail(400, { errors, values })` from a SvelteKit
 * action — what `+page.server.c`'s `page.actions` produces on validation
 * failure (or a manually-constructed equivalent). `errors` is a valibot
 * issue list; `values` is the raw form data echoed back for re-rendering.
 */
export type FormActionResult = {
	errors?: Array<{ path?: Array<{ key?: string }>; message: string }>;
	values?: Record<string, string>;
} | null | undefined;

export type FormProps = Omit<HTMLFormAttributes, 'class' | 'children'> & {
	method?: 'GET' | 'POST';
	action?: string;
	enctype?: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';
	/**
	 * Pass the page's `form` prop here. Form derives a `name → message` errors
	 * map and exposes it via context so descendant `<Field name="...">` looks
	 * up its own error automatically.
	 */
	form?: FormActionResult;
	class?: string;
	children?: Snippet;
};
```

### GradientText

`@human-synthesis/norns-ui/components/GradientText.n`

```ts
export type GradientTextProps = {
	/** Start color. Default `var(--color-primary-500)`. Accepts any CSS color. */
	from?: string;
	/** End color. Default `var(--color-info-500)`. */
	to?: string;
	/** Optional middle stop. */
	via?: string;
	/** Animate the gradient via background-position keyframes. Default true. */
	animate?: boolean;
	/** Gradient angle in degrees. Default 90. */
	angle?: number;
	children?: Snippet;
	class?: string;
};
```

### Header

`@human-synthesis/norns-ui/components/Header.n`

```ts
export type HeaderProps = {
	sticky?: boolean;
	brand?: Snippet;
	nav?: Snippet;
	actions?: Snippet;
	class?: string;
};
```

### HeroBanner

`@human-synthesis/norns-ui/components/HeroBanner.n`

```ts
export type HeroBannerProps = {
	title: string;
	description?: string;
	/** Background/decorative image src; rendered with reduced opacity behind the content. */
	image?: string;
	align?: 'left' | 'center';
	/** Apply animated `GradientText` to the title. Default false. */
	gradient?: boolean;
	actions?: Snippet;
	children?: Snippet;
	class?: string;
};
```

### HierarchicalMenu

`@human-synthesis/norns-ui/components/HierarchicalMenu.n`

```ts
export type HierarchicalMenuItem = {
	label: string;
	href?: string;
	icon?: string;
	description?: string;
	children?: HierarchicalMenuItem[];
};

export type HierarchicalMenuProps = {
	items?: HierarchicalMenuItem[];
	/** aria-label for the <nav> wrapper. */
	label?: string;
	onSelect?: (item: HierarchicalMenuItem) => void;
	class?: string;
};
```

### Icon

`@human-synthesis/norns-ui/components/Icon.n`

```ts
export type IconProps = {
	/**
	 * Iconify icon name in `<set>:<name>` form, e.g. `"lucide:check"` or
	 * `"mdi:home"`. The `@iconify-json/lucide` package ships with the library;
	 * other sets can be installed alongside (`@iconify-json/heroicons`, etc.).
	 */
	name: string;
	/**
	 * Tailwind size utility for the icon, e.g. `"size-4"`, `"size-5"`. Default
	 * `"size-4"`. Pass any class string accepted by Tailwind.
	 */
	size?: string;
	flip?: 'horizontal' | 'vertical' | 'horizontal,vertical';
	rotate?: 90 | 180 | 270 | string;
	class?: string;
};
```

### Image

`@human-synthesis/norns-ui/components/Image.n`

```ts
export type ImageProps = {
	src: string;
	alt?: string;
	loading?: 'lazy' | 'eager';
	fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
	rounded?: boolean | 'sm' | 'md' | 'lg' | 'full';
	width?: number | string;
	height?: number | string;
	class?: string;
};
```

### Input

`@human-synthesis/norns-ui/components/Input.n`

```ts
export type InputSize = 'sm' | 'md' | 'lg';
export type InputType =
	| 'text'
	| 'email'
	| 'password'
	| 'number'
	| 'tel'
	| 'url'
	| 'search'
	| 'date'
	| 'time'
	| 'datetime-local'
	| 'month'
	| 'week';

export type InputProps = Omit<HTMLInputAttributes, 'class' | 'type' | 'size' | 'value'> & {
	value?: string | number;
	type?: InputType;
	size?: InputSize;
	/** Force the error styling regardless of the parent Field's error state. */
	error?: boolean;
	class?: string;
};
```

### MegaMenu

`@human-synthesis/norns-ui/components/MegaMenu.n`

```ts
export type MegaMenuItem = {
	label: string;
	href?: string;
	icon?: string;
	description?: string;
};

export type MegaMenuColumn = {
	title?: string;
	items: MegaMenuItem[];
};

export type MegaMenuSection = {
	label: string;
	cols?: 2 | 3 | 4;
	columns: MegaMenuColumn[];
};

export type MegaMenuProps = {
	sections?: MegaMenuSection[];
	/** aria-label for the <nav> wrapper. */
	label?: string;
	class?: string;
};
```

### MultiSelect

`@human-synthesis/norns-ui/components/MultiSelect.n`

```ts
export type MultiSelectProps = {
	items?: ComboboxItem[];
	value?: string[];
	open?: boolean;
	placeholder?: string;
	disabled?: boolean;
	name?: string;
	id?: string;
	error?: boolean;
};
```

### NumberInput

`@human-synthesis/norns-ui/components/NumberInput.n`

```ts
export type NumberInputProps = {
	value?: number;
	min?: number;
	max?: number;
	stepSize?: number;
	disabled?: boolean;
	readonly?: boolean;
	name?: string;
	id?: string;
	error?: boolean;
	class?: string;
};
```

### OtpField

`@human-synthesis/norns-ui/components/OtpField.n`

```ts
export type OtpFieldProps = {
	value?: string;
	length?: number;
	mask?: boolean;
	label?: string;
	oncomplete?: (value: string) => void;
	class?: string;
};
```

### Pagination

`@human-synthesis/norns-ui/components/Pagination.n`

```ts
export type PaginationProps = {
	page?: number;
	total?: number;
	pageSize?: number;
	/** How many pages to show on each side of the current. Default 1. */
	siblingCount?: number;
	class?: string;
};
```

### Popover

`@human-synthesis/norns-ui/components/Popover.n`

```ts
export type PopoverSide = 'top' | 'right' | 'bottom' | 'left';
export type PopoverAlign = 'start' | 'center' | 'end';

export type PopoverProps = {
	open?: boolean;
	side?: PopoverSide;
	align?: PopoverAlign;
	sideOffset?: number;
	trigger?: Snippet;
	children?: Snippet;
	triggerClass?: string;
	class?: string;
};
```

### Progress

`@human-synthesis/norns-ui/components/Progress.n`

```ts
export type ProgressVariant = 'primary' | 'success' | 'warning' | 'danger';

export type ProgressProps = {
	value?: number;
	max?: number;
	variant?: ProgressVariant;
	indeterminate?: boolean;
	class?: string;
};
```

### ProgressCircular

`@human-synthesis/norns-ui/components/ProgressCircular.n`

```ts
export type ProgressCircularSize = 'sm' | 'md' | 'lg';

export type ProgressCircularProps = {
	value?: number;
	max?: number;
	size?: ProgressCircularSize;
	indeterminate?: boolean;
	class?: string;
};
```

### Radio

`@human-synthesis/norns-ui/components/Radio.n`

```ts
export type RadioProps = Omit<HTMLInputAttributes, 'class' | 'type'> & {
	/** Two-way bound shared group value — pass `bind:group={selected}` from parent. */
	group?: string;
	value?: string;
	error?: boolean;
	class?: string;
};
```

### RippleButton

`@human-synthesis/norns-ui/components/RippleButton.n`

```ts
export type RippleButtonProps = {
	variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
	size?: 'sm' | 'md' | 'lg';
	type?: 'button' | 'submit' | 'reset';
	disabled?: boolean;
	onclick?: (event: MouseEvent) => void;
	icon?: string;
	children?: Snippet;
	class?: string;
};
```

### ScrollArea

`@human-synthesis/norns-ui/components/ScrollArea.n`

```ts
export type ScrollAreaProps = {
	/** Allow horizontal scrolling instead of just vertical. */
	horizontal?: boolean;
	children?: Snippet;
	class?: string;
};
```

### Select

`@human-synthesis/norns-ui/components/Select.n`

```ts
export type SelectProps = Omit<HTMLSelectAttributes, 'class' | 'value' | 'children'> & {
	value?: string | number | string[];
	error?: boolean;
	class?: string;
	/** `<option>` children. Pass via Pug body. */
	children?: Snippet;
};
```

### Separator

`@human-synthesis/norns-ui/components/Separator.n`

```ts
export type SeparatorProps = {
	orientation?: 'horizontal' | 'vertical';
	class?: string;
};
```

### Sheet

`@human-synthesis/norns-ui/components/Sheet.n`

```ts
export type SheetSide = 'top' | 'right' | 'bottom' | 'left';

export type SheetProps = {
	open?: boolean;
	/** `'left'` was previously the default for the now-removed `<Drawer>`. */
	side?: SheetSide;
	title?: string;
	description?: string;
	hideClose?: boolean;
	dismissable?: boolean;
	ariaLabel?: string;
	trigger?: Snippet;
	actions?: Snippet;
	children?: Snippet;
	triggerClass?: string;
	overlayClass?: string;
	class?: string;
};
```

### ShinyButton

`@human-synthesis/norns-ui/components/ShinyButton.n`

```ts
export type ShinyButtonProps = {
	variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
	size?: 'sm' | 'md' | 'lg';
	type?: 'button' | 'submit' | 'reset';
	disabled?: boolean;
	onclick?: (event: MouseEvent) => void;
	icon?: string;
	children?: Snippet;
	class?: string;
};
```

### Skeleton

`@human-synthesis/norns-ui/components/Skeleton.n`

```ts
export type SkeletonVariant = 'rect' | 'text' | 'circle';

export type SkeletonProps = {
	variant?: SkeletonVariant;
	width?: number | string;
	height?: number | string;
	class?: string;
};
```

### Stepper

`@human-synthesis/norns-ui/components/Stepper.n`

```ts
export type StepperStep = {
	id?: string;
	label: string;
	complete?: boolean;
	current?: boolean;
};

export type StepperProps = {
	steps?: StepperStep[];
	/** Index of the active step. Steps before are auto-marked complete unless overridden. */
	current?: number;
	orientation?: 'horizontal' | 'vertical';
	class?: string;
};
```

### Surface

`@human-synthesis/norns-ui/components/Surface.n`

```ts
export type SurfaceProps = {
	tone?: 'glass';
	children?: Snippet;
	class?: string;
};
```

### Switch

`@human-synthesis/norns-ui/components/Switch.n`

```ts
export type SwitchProps = Omit<HTMLInputAttributes, 'class' | 'type' | 'checked' | 'role'> & {
	checked?: boolean;
	error?: boolean;
	class?: string;
};
```

### Tabs

`@human-synthesis/norns-ui/components/Tabs.n`

```ts
export type TabsItem = {
	value: string;
	label: string;
	panel?: Snippet;
	disabled?: boolean;
};

export type TabsProps = {
	value?: string;
	items?: TabsItem[];
	/** ARIA label for the tablist. Default 'Tabs'. */
	label?: string;
	class?: string;
};
```

### TagsInput

`@human-synthesis/norns-ui/components/TagsInput.n`

```ts
export type TagsInputProps = {
	value?: string[];
	max?: number;
	placeholder?: string;
	commitOnBlur?: boolean;
	separators?: string[];
	disabled?: boolean;
	name?: string;
	id?: string;
	error?: boolean;
	class?: string;
};
```

### Textarea

`@human-synthesis/norns-ui/components/Textarea.n`

```ts
export type TextareaProps = Omit<HTMLTextareaAttributes, 'class' | 'value' | 'rows'> & {
	value?: string;
	rows?: number;
	error?: boolean;
	class?: string;
};
```

### ThemeToggler

`@human-synthesis/norns-ui/components/ThemeToggler.n`

```ts
export type ThemeTogglerProps = {
	variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
	size?: 'sm' | 'md' | 'lg';
	showLabel?: boolean;
	labelLight?: string;
	labelDark?: string;
	lightIcon?: string;
	darkIcon?: string;
	/** localStorage key. Default `'norns-theme'`. */
	storageKey?: string;
	class?: string;
};
```

### Timeline

`@human-synthesis/norns-ui/components/Timeline.n`

```ts
export type TimelineItem = {
	time?: string;
	title?: string;
	description?: string;
	icon?: string;
	variant?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
};

export type TimelineProps = {
	items?: TimelineItem[];
	class?: string;
};
```

### TimePicker

`@human-synthesis/norns-ui/components/TimePicker.n`

```ts
export type TimePickerProps = {
	/** Time string `HH:MM` (24h regardless of display `hourCycle`). Bindable. */
	value?: string;
	open?: boolean;
	/** Minute increment in the picker (5, 10, 15, 30). Default 5. */
	minuteStep?: number;
	hourCycle?: 12 | 24;
	placeholder?: string;
	disabled?: boolean;
	label?: string;
	name?: string;
	id?: string;
	error?: boolean;
	class?: string;
};
```

### ToastProvider

`@human-synthesis/norns-ui/components/ToastProvider.n`

```ts
export type ToastVariant = 'info' | 'success' | 'warning' | 'error';

export type ToastProviderProps = {
	class?: string;
};

export type ToastOpts = {
	variant?: ToastVariant;
	duration?: number;
};
```

### ToggleButton

`@human-synthesis/norns-ui/components/ToggleButton.n`

```ts
export type ToggleButtonProps = {
	pressed?: boolean;
	disabled?: boolean;
	icon?: string;
	/** Any `.btn-*` variant. The toggle visual state is controlled by `data-pressed`. */
	variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
	size?: 'sm' | 'md' | 'lg';
	onpressedchange?: (pressed: boolean) => void;
	children?: Snippet;
	class?: string;
};
```

### ToggleButtonGroup

`@human-synthesis/norns-ui/components/ToggleButtonGroup.n`

```ts
export type ToggleGroupItem = {
	value: string;
	label?: string;
	icon?: string;
	iconOnly?: boolean;
	disabled?: boolean;
};

export type ToggleButtonGroupProps = {
	items?: ToggleGroupItem[];
	value?: string | string[] | undefined;
	multiple?: boolean;
	disabled?: boolean;
	label?: string;
	class?: string;
};
```

### Toolbar

`@human-synthesis/norns-ui/components/Toolbar.n`

```ts
export type ToolbarProps = {
	orientation?: 'horizontal' | 'vertical';
	/** Accessible label, used as aria-label. */
	label?: string;
	children?: Snippet;
	class?: string;
};
```

### Tooltip

`@human-synthesis/norns-ui/components/Tooltip.n`

```ts
export type TooltipProps = {
	content?: string;
	side?: 'top' | 'right' | 'bottom' | 'left';
	align?: 'start' | 'center' | 'end';
	sideOffset?: number;
	delay?: number;
	/** Wider, surface-elevated styling for tooltips with markup/multiline content. */
	rich?: boolean;
	trigger?: Snippet;
	children?: Snippet;
	triggerClass?: string;
	class?: string;
};
```

### Tree

`@human-synthesis/norns-ui/components/Tree.n`

```ts
export type TreeNode = {
	id: string | number;
	label: string;
	icon?: string;
	children?: TreeNode[];
};

export type TreeProps = {
	items?: TreeNode[];
	/** Array of node ids that are expanded. Bindable. */
	expanded?: (string | number)[];
	/** Currently-selected node id. Bindable. */
	selected?: string | number;
	class?: string;
};
```

### Uploader

`@human-synthesis/norns-ui/components/Uploader.n`

```ts
export type UploaderProps = {
	files?: File[];
	multiple?: boolean;
	accept?: string;
	disabled?: boolean;
	placeholder?: string;
	helper?: string;
	onchange?: (files: File[]) => void;
	class?: string;
};
```

### Video

`@human-synthesis/norns-ui/components/Video.n`

```ts
export type VideoSource = { src: string; type?: string };

export type VideoProps = {
	src?: string;
	sources?: VideoSource[];
	controls?: boolean;
	autoplay?: boolean;
	loop?: boolean;
	muted?: boolean;
	playsinline?: boolean;
	preload?: 'none' | 'metadata' | 'auto';
	poster?: string;
	fallback?: string;
	class?: string;
};
```

### Window

`@human-synthesis/norns-ui/components/Window.n`

```ts
export type WindowProps = {
	/** Header title (truncated). */
	title?: string;
	/** Remove the whole header bar; keeps the rounded shell. */
	hideHeader?: boolean;
	/** When set, renders a `lucide:x` close button (marked `.nodrag`). */
	onClose?: () => void;
	/** Fires on double-click over the header bar (e.g. maximise). */
	onHeaderDoubleClick?: (event: MouseEvent) => void;
	/** Extra header controls, rendered before the close button. */
	actions?: Snippet;
	children?: Snippet;
	/** Classes for the outer shell — pass dimensions here (`w-[520px] h-[380px]`). */
	class?: string;
	/** Classes for the body section. */
	bodyClass?: string;
};
```

## Motion (opt-in)

[AnimatedNumber](#animatednumber) · [GradientBackground](#gradientbackground) · [LiquidButton](#liquidbutton) · [Reveal](#reveal) · [Sparkles](#sparkles)

### AnimatedNumber

`import { AnimatedNumber } from '@human-synthesis/norns-ui/motion'`

```ts
export type AnimatedNumberProps = {
	value: number;
	from?: number;
	/** Duration in seconds. */
	duration?: number;
	decimals?: number;
	/** Custom number-to-string formatter. */
	format?: (n: number) => string;
	class?: string;
};
```

### GradientBackground

`import { GradientBackground } from '@human-synthesis/norns-ui/motion'`

```ts
export type GradientBackgroundProps = {
	colors?: string[];
	/** Loop duration in seconds. Default 18. */
	duration?: number;
	/** Gradient angle. Default 130. */
	angle?: number;
	children?: Snippet;
	class?: string;
};
```

### LiquidButton

`import { LiquidButton } from '@human-synthesis/norns-ui/motion'`

```ts
export type LiquidButtonProps = {
	variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
	size?: 'sm' | 'md' | 'lg';
	type?: 'button' | 'submit' | 'reset';
	disabled?: boolean;
	onclick?: (event: MouseEvent) => void;
	icon?: string;
	children?: Snippet;
	class?: string;
};
```

### Reveal

`import { Reveal } from '@human-synthesis/norns-ui/motion'`

```ts
export type RevealProps = {
	direction?: 'up' | 'down' | 'left' | 'right';
	/** Travel distance in pixels. Default 12. */
	distance?: number;
	/** Animation duration in seconds. Default 0.5. */
	duration?: number;
	/** Delay before animating, seconds. Default 0. */
	delay?: number;
	/** Re-trigger on every entry (false) or just the first time (true). Default true. */
	once?: boolean;
	/** IntersectionObserver threshold (0–1). Default 0.15. */
	threshold?: number;
	children?: Snippet;
	class?: string;
};
```

### Sparkles

`import { Sparkles } from '@human-synthesis/norns-ui/motion'`

```ts
export type SparklesProps = {
	density?: number;
	colors?: string[];
	minSize?: number;
	maxSize?: number;
	children?: Snippet;
	class?: string;
};
```
