# vfunc-ui components — reference for LLMs

> Generated by build/components.mjs from layer2/catalog.json and layer2/types/*.d.ts. Do not edit.

Layer 2 of vfunc.js: components without classes on top of the engine (`vf.vfunc`). Use them where they save work; plain HTML is still fine.

- **Load:** `vfunc.js` → `vfunc-ui.js` (→ `vfunc-ui-data.js` for the grid and charts) (→ `vfunc-ui.locale.ko.js`), CSS `vfunc.tokens.css` + `vfunc-ui.css`. ES modules: `import 'vfunc/ui'` (and `'vfunc/ui/data'`) add the members to `vf`.
- **`vf.vs*` returns SafeHtml:** put it in `vf.html` templates or `render` as is (not escaped twice). It never holds state.
- **`vf.vf*` returns an instance:** `.mount(el)` or a parent's `childs`. Callbacks get `{ sender, event, data }`: read `e.data`. `getValue()` / `setValue(v)`; `setValue` never calls `onChange`.
- **Hooks:** `id`, `ref` (`data-ref`) and `action` (`data-action`) props; never select by the `vf-*` classes. Every string prop is escaped; pass `vf.html` for markup.
- **Text:** built-in messages are the keys listed per component; override them with props or `vf.i18n.add(locale, { key: text })`.
- Types below are the d.ts declarations without comments. Types used by several components come first.

## Shared types

```ts
interface VfUiEvent<D = Record<string, unknown>, I = VfuncInstance> {
  sender: I;
  event: Event | null;
  data: D;
}
type VfValueInstance<V, M = {}> = VfuncInstance & { getValue(): V; setValue(value: V): void; } & M;
type VsContent = string | number | SafeHtml;
interface VsCommonProps {
  id?: string;
  ref?: string;
  action?: string;
  describedBy?: string;
  className?: string;
}
interface VsFieldTextProps {
  label?: VsContent;
  hint?: VsContent;
  error?: VsContent;
  required?: boolean;
}
interface VsControlProps extends VsCommonProps, VsFieldTextProps {
  name?: string;
  disabled?: boolean;
  readonly?: boolean;
}
type VsOption = string | number | { value: string | number; label?: VsContent; disabled?: boolean };
type VsTone = 'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
type VsSlot = VsContent | VsContent[];
type VfSlot = VsSlot | VfuncInstance;
interface VfMenuItem {
  label?: VsContent;
  action?: string;
  disabled?: boolean;
  danger?: boolean;
  separator?: boolean;
}
type VfPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
interface VfModalMethods {
  open(): void;
  close(reason?: string): void;
  isOpen(): boolean;
}
interface VfToastOptions {
  message: VsContent;
  title?: VsContent;
  variant?: 'info' | 'success' | 'warning' | 'danger';
  duration?: number;
  action?: { label: string; onClick?: (e: VfUiEvent<{ id: string }>) => void };
}
interface VsTableColumn<T = any> {
  key: string;
  label: VsContent;
  align?: 'start' | 'center' | 'end';
  sortable?: boolean;
  render?: (row: T, index: number) => VsSlot;
}
interface VsFieldControlArgs {
  id: string;
  describedBy: string | null;
  invalid: boolean;
  required: boolean;
}
interface VfGridMethods<T = any> {
  setData(data: T[]): void;
  getData(): T[];
  setColumns(columns: Array<VsTableColumn<T>>): void;
  getSelection(): T[];
  clearSelection(): void;
  setPage(page: number): void;
  setQuery(query: string): void;
  setLoading(loading: boolean): void;
  setTotal(total: number): void;
  getValue(): string[];
  setValue(keys: string[]): void;
  readonly instance: unknown;
}
type VfChartType = 'bar' | 'line' | 'area' | 'pie' | 'donut' | 'sparkline';
interface VfChartData {
  labels: Array<string | number>;
  series: Array<{ name: string; data: number[] }>;
}
interface VfChartMethods {
  setData(data: VfChartData): void;
  setType(type: VfChartType): void;
  resize(): void;
  getValue(): VfChartData;
  setValue(data: VfChartData): void;
  readonly instance: unknown;
}
```

## Input

### `vsField` — S

Label, hint and error around a control you build yourself; gives it the id and aria-describedby.

```js
vf.vsField({ label: 'Color', control: (a) => vf.html`<input type="color" id="${a.id}" aria-describedby="${a.describedBy}">` })
```

```ts
interface VsFieldProps extends VsFieldTextProps {
  id?: string;
  describedBy?: string;
  className?: string;
  control: (args: VsFieldControlArgs) => SafeHtml;
}
// Label, hint and error around a control you build yourself.
function vsField(props: VsFieldProps): SafeHtml;
```

### `vsInput` — S

A text-like <input>; label, hint and error make it a linked field.

```js
vf.vsInput({ name: 'email', type: 'email', label: 'Email', error: errors.email })
```

```ts
interface VsInputProps extends VsControlProps {
  type?: 'text' | 'email' | 'tel' | 'url' | 'number' | 'search' | 'password' | 'date' | 'time' | 'datetime-local' | 'month' | 'week';
  value?: string | number;
  placeholder?: string;
  autocomplete?: string;
  inputmode?: string;
  pattern?: string;
  min?: string | number;
  max?: string | number;
  step?: string | number;
  minlength?: number;
  maxlength?: number;
  size?: 'md' | 'sm' | 'lg';
}
// An `<input class="vf-input">`, in a `.vf-field` when label, hint or error is given.
function vsInput(props: VsInputProps): SafeHtml;
```

### `vsTextarea` — S

A <textarea> with the same field props as vsInput.

```js
vf.vsTextarea({ name: 'memo', label: 'Memo', rows: 4 })
```

```ts
interface VsTextareaProps extends VsControlProps {
  value?: string;
  rows?: number;
  placeholder?: string;
  minlength?: number;
  maxlength?: number;
  size?: 'md' | 'sm' | 'lg';
}
// A `<textarea class="vf-textarea">`.
function vsTextarea(props: VsTextareaProps): SafeHtml;
```

### `vsSelect` — S

A native <select> from an options list, with groups, placeholder and multiple.

```js
vf.vsSelect({ name: 'role', label: 'Role', options: ['admin', 'user'], value: 'user' })
```

```ts
interface VsSelectProps extends VsControlProps {
  options: Array<VsOption | { label: string; options: VsOption[]; disabled?: boolean }>;
  value?: string | number | Array<string | number>;
  placeholder?: string;
  multiple?: boolean;
  size?: 'md' | 'sm' | 'lg';
}
// A `<select class="vf-select">`.
function vsSelect(props: VsSelectProps): SafeHtml;
```

### `vsCheckbox` — S

A native checkbox with its label.

```js
vf.vsCheckbox({ name: 'agree', label: 'I agree', checked: true })
```

```ts
interface VsCheckboxProps extends VsControlProps {
  label: VsContent;
  checked?: boolean;
  value?: string;
}
// A native checkbox inside its `<label class="vf-check">`.
function vsCheckbox(props: VsCheckboxProps): SafeHtml;
```

### `vsRadioGroup` — S

Radio buttons in a fieldset with a legend.

```js
vf.vsRadioGroup({ name: 'plan', label: 'Plan', options: ['free', 'pro'], value: 'free' })
```

```ts
interface VsRadioGroupProps extends VsControlProps {
  options: VsOption[];
  value?: string | number;
  direction?: 'vertical' | 'horizontal';
}
// Radio buttons in a `<fieldset class="vf-radio-group">`; `label` is the legend, `id` the fieldset's.
function vsRadioGroup(props: VsRadioGroupProps): SafeHtml;
```

### `vsSwitch` — S

An on/off switch: a native checkbox with role="switch".

```js
vf.vsSwitch({ name: 'notify', label: 'Notifications', checked: true })
```

```ts
// An on/off switch: a native checkbox with `role="switch"`.
function vsSwitch(props: VsCheckboxProps): SafeHtml;
```

### `vsSlider` — S

A native range slider.

```js
vf.vsSlider({ name: 'volume', label: 'Volume', value: 30 })
```

```ts
interface VsSliderProps extends VsControlProps {
  value?: number;
  min?: number;
  max?: number;
  step?: number;
}
// A native range `<input class="vf-slider">`.
function vsSlider(props: VsSliderProps): SafeHtml;
```

### `vsProgress` — S

A native progress bar; no value shows the indeterminate state.

```js
vf.vsProgress({ label: 'Upload', value: 42, showValue: true })
```

```ts
interface VsProgressProps extends VsCommonProps {
  value?: number | null;
  max?: number;
  label?: VsContent;
  hint?: VsContent;
  showValue?: boolean;
}
// A native `<progress class="vf-progress">`.
function vsProgress(props: VsProgressProps): SafeHtml;
```

### `vsNumberInput` · `vfNumberInput` — P

A number input with decrease/increase buttons; vf* steps, clamps to min/max and rounds to the step.

Messages: `numberInput.decrement`, `numberInput.increment`

```js
vf.vfNumberInput({ name: 'qty', label: 'Quantity', value: 1, min: 1, max: 99, onChange: (e) => setQty(e.data.value) })
```

```ts
interface VsNumberInputProps extends VsControlProps {
  value?: number | null;
  min?: number;
  max?: number;
  step?: number;
  placeholder?: string;
  size?: 'md' | 'sm' | 'lg';
  decrementLabel?: string;
  incrementLabel?: string;
}
// An `<input type="number">` between buttons with data-action "decrement" / "increment".
function vsNumberInput(props: VsNumberInputProps): SafeHtml;
interface VfNumberInputProps extends VsNumberInputProps {
  onChange?: (e: VfUiEvent<{ value: number | null }>) => void;
}
// Steps with the buttons, clamps and rounds on commit.
function vfNumberInput(props: VfNumberInputProps): VfValueInstance<number | null>;
```

### `vsSearchInput` · `vfSearchInput` — P

A search box with a clear button; vf* debounces onSearch (IME-aware), searches at once on Enter.

Messages: `searchInput.clear`, `searchInput.label`, `searchInput.placeholder`

```js
vf.vfSearchInput({ debounce: 300, onSearch: (e) => load(e.data.value) })
```

```ts
interface VsSearchInputProps extends VsControlProps {
  value?: string;
  placeholder?: string;
  ariaLabel?: string;
  clearLabel?: string;
  size?: 'md' | 'sm' | 'lg';
}
// An `<input type="search">` with a clear button (data-action "clear").
function vsSearchInput(props: VsSearchInputProps): SafeHtml;
interface VfSearchInputProps extends VsSearchInputProps {
  debounce?: number;
  onSearch?: (e: VfUiEvent<{ value: string }>) => void;
  onClear?: (e: VfUiEvent<{}>) => void;
}
function vfSearchInput(props: VfSearchInputProps): VfValueInstance<string, { clear(): void; focus(): void }>;
```

### `vsPasswordInput` · `vfPasswordInput` — P

A password input with a show/hide button (aria-pressed).

Messages: `passwordInput.hide`, `passwordInput.show`

```js
vf.vfPasswordInput({ name: 'password', label: 'Password', autocomplete: 'new-password' })
```

```ts
interface VsPasswordInputProps extends VsControlProps {
  value?: string;
  visible?: boolean;
  autocomplete?: string;
  placeholder?: string;
  minlength?: number;
  maxlength?: number;
  size?: 'md' | 'sm' | 'lg';
  showLabel?: string;
  hideLabel?: string;
}
// A password input with a toggle button (data-action "toggle-visibility", aria-pressed).
function vsPasswordInput(props: VsPasswordInputProps): SafeHtml;
interface VfPasswordInputProps extends VsPasswordInputProps {
  onChange?: (e: VfUiEvent<{ value: string }>) => void;
  onToggle?: (e: VfUiEvent<{ visible: boolean }>) => void;
}
function vfPasswordInput(props: VfPasswordInputProps): VfValueInstance<string, { toggle(visible?: boolean): void }>;
```

### `vsChipsInput` · `vfChipsInput` — P

Chips (vsTag) and a text input; Enter or comma adds, Backspace removes; hidden inputs for forms.

```js
vf.vfChipsInput({ name: 'tags', label: 'Tags', value: ['ui'], max: 5, onChange: (e) => save(e.data.value) })
```

```ts
interface VsChipsInputProps extends VsControlProps {
  value?: string[];
  placeholder?: string;
  max?: number;
}
// vsTag chips (data-action "remove") and a text input; one hidden input per chip when `name` is set.
function vsChipsInput(props: VsChipsInputProps): SafeHtml;
interface VfChipsInputProps extends VsChipsInputProps {
  onChange?: (e: VfUiEvent<{ value: string[] }>) => void;
}
// Enter or comma adds, Backspace in the empty input removes the last chip.
function vfChipsInput(props: VfChipsInputProps): VfValueInstance<string[], { add(text: string): boolean; remove(text: string): void }>;
```

### `vsRating` · `vfRating` — P

Stars on native radios (keyboard works); readonly shows an image with a text alternative.

Messages: `rating.label`, `rating.value`

```js
vf.vfRating({ name: 'score', value: 3, onChange: (e) => rate(e.data.value) })
```

```ts
interface VsRatingProps extends VsCommonProps {
  value?: number;
  max?: number;
  readonly?: boolean;
  label?: VsContent;
  name?: string;
  disabled?: boolean;
  required?: boolean;
  hint?: VsContent;
  error?: VsContent;
}
// Stars on radios (data-action "rate") in a fieldset; readonly: `role="img"` with "{value} of {max}".
function vsRating(props: VsRatingProps): SafeHtml;
interface VfRatingProps extends VsRatingProps {
  onChange?: (e: VfUiEvent<{ value: number }>) => void;
}
function vfRating(props: VfRatingProps): VfValueInstance<number>;
```

### `vsSelectButton` · `vfSelectButton` — P

Options as aria-pressed toggle buttons; one choice or several with multiple.

```js
vf.vfSelectButton({ ariaLabel: 'Period', options: ['day', 'week', 'month'], value: 'week', onChange: (e) => setPeriod(e.data.value) })
```

```ts
interface VsSelectButtonProps extends VsCommonProps {
  options: VsOption[];
  value?: string | number | Array<string | number>;
  multiple?: boolean;
  label?: VsContent;
  ariaLabel?: string;
  name?: string;
  disabled?: boolean;
  hint?: VsContent;
  error?: VsContent;
  size?: 'md' | 'sm' | 'lg';
}
// Toggle buttons (data-action "select", data-value, aria-pressed) in a `role="group"`.
function vsSelectButton(props: VsSelectButtonProps): SafeHtml;
interface VfSelectButtonProps extends VsSelectButtonProps {
  onChange?: (e: VfUiEvent<{ value: string | string[] }>) => void;
}
function vfSelectButton(props: VfSelectButtonProps): VfValueInstance<string | number | Array<string | number>>;
```

### `vsMaskedInput` · `vfMaskedInput` — P

A text input formatted by a mask (0 digit, a letter, * either); vf* keeps the caret and gives the raw value.

```js
vf.vfMaskedInput({ name: 'phone', label: 'Phone', mask: '000-0000-0000', onChange: (e) => save(e.data.raw) })
```

```ts
interface VsMaskedInputProps extends VsControlProps {
  mask: string;
  value?: string;
  placeholder?: string;
  autocomplete?: string;
  size?: 'md' | 'sm' | 'lg';
}
// A text `<input class="vf-input" data-mask>` with the value formatted by the mask.
function vsMaskedInput(props: VsMaskedInputProps): SafeHtml;
interface VfMaskedInputProps extends VsMaskedInputProps {
  onInput?: (e: VfUiEvent<{ value: string; raw: string }>) => void;
  onChange?: (e: VfUiEvent<{ value: string; raw: string }>) => void;
}
function vfMaskedInput(props: VfMaskedInputProps): VfValueInstance<string, { getRawValue(): string }>;
```

### `vsDatePicker` · `vfDatePicker` — P

A native date input with 'YYYY-MM-DD' values; vf* normalizes typed text (IE11) and gives a Date.

Messages: `datePicker.placeholder`

```js
vf.vfDatePicker({ name: 'due', label: 'Due date', min: new Date(), onChange: (e) => setDue(e.data.date) })
```

```ts
interface VsDatePickerProps extends VsControlProps {
  value?: string | Date;
  min?: string | Date;
  max?: string | Date;
  placeholder?: string;
  size?: 'md' | 'sm' | 'lg';
}
// A native `<input type="date" class="vf-input">`.
function vsDatePicker(props: VsDatePickerProps): SafeHtml;
interface VfDatePickerProps extends VsDatePickerProps {
  onChange?: (e: VfUiEvent<{ value: string; date: Date | null }>) => void;
}
// Normalizes typed dates where the input is text; `aria-invalid` for text that is not a date.
function vfDatePicker(props: VfDatePickerProps): VfValueInstance<string, { getDate(): Date | null }>;
```

### `vsTimePicker` · `vfTimePicker` — P

A native time input with 'HH:mm' values; vf* normalizes typed text (IE11).

Messages: `timePicker.placeholder`

```js
vf.vfTimePicker({ name: 'at', label: 'Time', step: 900 })
```

```ts
interface VsTimePickerProps extends VsControlProps {
  value?: string | Date;
  min?: string;
  max?: string;
  step?: number;
  placeholder?: string;
  size?: 'md' | 'sm' | 'lg';
}
// A native `<input type="time" class="vf-input">`.
function vsTimePicker(props: VsTimePickerProps): SafeHtml;
interface VfTimePickerProps extends VsTimePickerProps {
  onChange?: (e: VfUiEvent<{ value: string }>) => void;
}
function vfTimePicker(props: VfTimePickerProps): VfValueInstance<string>;
```

### `vfDateRangePicker` — F

Start and end dates on two native inputs; the end never goes before the start.

Messages: `datePicker.placeholder`, `dateRangePicker.end`, `dateRangePicker.start`

```js
vf.vfDateRangePicker({ label: 'Period', names: ['from', 'to'], onChange: (e) => search(e.data.start, e.data.end) })
```

```ts
interface VfDateRangePickerProps extends VsCommonProps {
  start?: string | Date;
  end?: string | Date;
  min?: string | Date;
  max?: string | Date;
  names?: [string, string];
  label?: VsContent;
  startLabel?: string;
  endLabel?: string;
  disabled?: boolean;
  readonly?: boolean;
  required?: boolean;
  hint?: VsContent;
  error?: VsContent;
  onChange?: (e: VfUiEvent<{ start: string; end: string; startDate: Date | null; endDate: Date | null }>) => void;
}
// Two native date inputs (data-action "start" / "end") in a fieldset; the end never precedes the start.
function vfDateRangePicker(props: VfDateRangePickerProps): VfValueInstance<{ start: string; end: string }>;
```

## Display

### `vsButton` — S

A button with variant, size, loading state and data-action / data-ref hooks.

Messages: `common.loading`

```js
vf.vsButton({ label: 'Save', variant: 'primary', action: 'save' })
```

```ts
interface VsButtonProps {
  label: string | SafeHtml;
  variant?: 'secondary' | 'primary' | 'danger' | 'ghost';
  size?: 'md' | 'sm' | 'lg';
  type?: 'button' | 'submit' | 'reset';
  action?: string;
  ref?: string;
  id?: string;
  disabled?: boolean;
  loading?: boolean;
  loadingText?: string;
  ariaLabel?: string;
  describedBy?: string;
  aria?: Record<string, string | number | boolean | null | undefined>;
  className?: string;
}
// A `<button class="vf-button">`.
function vsButton(props: VsButtonProps): SafeHtml;
```

### `vsButtonGroup` — S

Buttons side by side in a role="group".

```js
vf.vsButtonGroup({ label: 'View', attached: true, buttons: [{ label: 'List', action: 'list' }, { label: 'Grid', action: 'grid' }] })
```

```ts
interface VsButtonGroupProps {
  buttons: VsButtonProps[];
  label?: string;
  size?: 'md' | 'sm' | 'lg';
  attached?: boolean;
  id?: string;
  ref?: string;
  className?: string;
}
// Buttons in a `<div role="group" class="vf-button-group">`.
function vsButtonGroup(props: VsButtonGroupProps): SafeHtml;
```

### `vsBadge` — S

A small status label or count.

```js
vf.vsBadge({ label: 'Active', variant: 'success', dot: true })
```

```ts
interface VsBadgeProps extends VsCommonProps {
  label: VsContent;
  variant?: VsTone;
  dot?: boolean;
}
// A `<span class="vf-badge">`.
function vsBadge(props: VsBadgeProps): SafeHtml;
```

### `vsTag` — S

A label, optionally with a remove button (data-action, data-value).

Messages: `tag.remove`

```js
vf.vsTag({ label: 'urgent', value: 'urgent', removable: true, removeAction: 'remove-tag' })
```

```ts
interface VsTagProps {
  label: VsContent;
  variant?: VsTone;
  value?: string;
  removable?: boolean;
  removeAction?: string;
  removeLabel?: string;
  disabled?: boolean;
  id?: string;
  ref?: string;
  className?: string;
}
// A `<span class="vf-tag">`; the app removes it in its own delegate.
function vsTag(props: VsTagProps): SafeHtml;
```

### `vsAvatar` — S

A user picture, or the initials of the name.

```js
vf.vsAvatar({ name: 'Ada Lovelace', src: user.photo })
```

```ts
interface VsAvatarProps {
  name?: string;
  src?: string;
  alt?: string;
  size?: 'md' | 'sm' | 'lg';
  id?: string;
  ref?: string;
  className?: string;
}
// A `<span class="vf-avatar" role="img">` with an image or initials.
function vsAvatar(props: VsAvatarProps): SafeHtml;
```

### `vsAlert` — S

A message box: info, success, warning, danger; optionally dismissible.

Messages: `alert.dismiss`

```js
vf.vsAlert({ variant: 'warning', title: 'Check', message: 'Unsaved changes', dismissible: true })
```

```ts
interface VsAlertProps {
  message?: VsContent;
  title?: VsContent;
  variant?: 'info' | 'success' | 'warning' | 'danger';
  dismissible?: boolean;
  dismissAction?: string;
  dismissLabel?: string;
  id?: string;
  ref?: string;
  className?: string;
}
// A `<div class="vf-alert">`.
function vsAlert(props: VsAlertProps): SafeHtml;
```

### `vsCard` — S

A surface with header (title, subtitle, actions), body and footer.

```js
vf.vsCard({ title: 'Sales', body: vf.html`<p>${text}</p>`, footer: vf.vsButton({ label: 'More', action: 'more' }) })
```

```ts
interface VsCardProps {
  title?: VsContent;
  subtitle?: VsContent;
  actions?: VsSlot;
  body?: VsSlot;
  footer?: VsSlot;
  headingLevel?: 2 | 3 | 4 | 5 | 6;
  id?: string;
  ref?: string;
  className?: string;
}
// A `<div class="vf-card">`.
function vsCard(props: VsCardProps): SafeHtml;
```

### `vsDescriptions` — S

Label/value pairs as a <dl>, in 1–4 columns.

```js
vf.vsDescriptions({ columns: 2, items: [{ label: 'Name', value: user.name }, { label: 'Email', value: user.email }] })
```

```ts
interface VsDescriptionsProps {
  items: Array<{ label: VsContent; value: VsSlot }>;
  columns?: 1 | 2 | 3 | 4;
  title?: VsContent;
  id?: string;
  ref?: string;
  className?: string;
}
// Label/value pairs as a `<dl>`.
function vsDescriptions(props: VsDescriptionsProps): SafeHtml;
```

### `vsStatCard` — S

A key figure formatted with vf.fmt, with an optional change (+12.5%).

Messages: `statCard.down`, `statCard.up`

```js
vf.vsStatCard({ label: 'Revenue', value: 1250000, format: { style: 'currency', currency: 'KRW' }, delta: 0.125, deltaLabel: 'vs last month' })
```

```ts
interface VsStatCardProps {
  label: VsContent;
  value: VsContent;
  format?: Intl.NumberFormatOptions;
  delta?: number;
  deltaLabel?: VsContent;
  description?: VsContent;
  icon?: VsSlot;
  id?: string;
  ref?: string;
  className?: string;
}
// A key figure in a `<div class="vf-stat-card">`.
function vsStatCard(props: VsStatCardProps): SafeHtml;
```

### `vsTimeline` — S

Events in order, with times formatted by vf.fmt.date.

```js
vf.vsTimeline({ items: [{ title: 'Order placed', time: order.createdAt, variant: 'success' }] })
```

```ts
interface VsTimelineProps {
  items: Array<{ title: VsContent; time?: Date | number | string; description?: VsContent; variant?: VsTone }>;
  timeFormat?: Intl.DateTimeFormatOptions;
  id?: string;
  ref?: string;
  className?: string;
}
// Events in an `<ol class="vf-timeline">`.
function vsTimeline(props: VsTimelineProps): SafeHtml;
```

### `vsEmptyState` — S

What to show when a list or search has nothing.

Messages: `emptyState.title`

```js
vf.vsEmptyState({ description: 'Try another keyword', action: vf.vsButton({ label: 'Reset', action: 'reset' }) })
```

```ts
interface VsEmptyStateProps {
  title?: VsContent;
  description?: VsContent;
  icon?: VsSlot;
  action?: VsSlot;
  id?: string;
  ref?: string;
  className?: string;
}
// A `<div class="vf-empty-state">`.
function vsEmptyState(props: VsEmptyStateProps): SafeHtml;
```

### `vsSkeleton` — S

Loading placeholders (text lines, rectangle, circle), hidden from screen readers.

```js
vf.vsSkeleton({ lines: 3 })
```

```ts
interface VsSkeletonProps {
  variant?: 'text' | 'rect' | 'circle';
  lines?: number;
  id?: string;
  ref?: string;
  className?: string;
}
// Placeholders, `aria-hidden`; mark the loading region with aria-busy.
function vsSkeleton(props: VsSkeletonProps): SafeHtml;
```

### `vsSpinner` — S

A loading indicator with a hidden status text.

Messages: `common.loading`

```js
vf.vsSpinner({ size: 'sm' })
```

```ts
interface VsSpinnerProps {
  label?: string;
  size?: 'md' | 'sm' | 'lg';
  id?: string;
  ref?: string;
  className?: string;
}
// A `<span class="vf-spinner" role="status">`.
function vsSpinner(props: VsSpinnerProps): SafeHtml;
```

### `vsTooltip` — S

A short text on hover and focus, linked with aria-describedby; CSS only.

```js
vf.vsTooltip({ text: 'Copy link', trigger: (a) => vf.vsButton({ label: 'Copy', action: 'copy', describedBy: a.describedBy }) })
```

```ts
interface VsTooltipProps {
  text: string;
  trigger: ((args: { describedBy: string }) => SafeHtml) | SafeHtml;
  placement?: 'top' | 'bottom' | 'start' | 'end';
  id?: string;
  ref?: string;
  className?: string;
}
// A trigger with a CSS-only tooltip (hover and focus).
function vsTooltip(props: VsTooltipProps): SafeHtml;
```

### `vsListView` · `vfListView` — P

A list of items; with selectable, a listbox with roving focus (arrows, Space, Enter).

```js
vf.vfListView({ label: 'Users', items: users, selectable: 'single', onSelect: (e) => open(e.data.value) })
```

```ts
interface VsListViewProps<T = any> {
  items: T[];
  render?: (item: T, index: number) => SafeHtml;
  itemKey?: string;
  selectable?: 'none' | 'single' | 'multiple';
  selected?: string | string[];
  label?: string;
  emptyText?: VsContent;
  id?: string;
  ref?: string;
  className?: string;
}
// A `<ul class="vf-list-view">`, or an empty state when there are no items.
function vsListView<T = any>(props: VsListViewProps<T>): SafeHtml;
interface VfListViewProps<T = any> extends VsListViewProps<T> {
  onSelect?: (e: VfUiEvent<{ value: string | string[]; items: T[] }>) => void;
}
// Click, Space or Enter selects; Up, Down, Home, End move the focus. Since 1.2.0 the rows are kept by key (itemKey, else the position): setItems draws only new or changed items (give a changed item a new object), and selecting changes aria-selected and tabindex without drawing the list again. Its options have no id or data-index; find one by data-value. A render that takes the index draws a row again when its position changes.
function vfListView<T = any>(props: VfListViewProps<T>): VfValueInstance<string | string[] | null, { setItems(items: T[]): void }>;
```

### `vfCarousel` — F

Slides one at a time with previous/next, dots and optional autoplay that pauses on hover, focus and reduced motion.

Messages: `carousel.carousel`, `carousel.goTo`, `carousel.next`, `carousel.pause`, `carousel.play`, `carousel.position`, `carousel.prev`, `carousel.slide`

```js
vf.vfCarousel({ label: 'News', items: [{ src: '/a.jpg', alt: 'Launch' }, { content: vf.html`<p>${text}</p>` }], autoplay: 5000 })
```

```ts
interface VfCarouselProps {
  items: Array<{ content?: VfSlot; src?: string; alt?: string }>;
  label: string;
  index?: number;
  loop?: boolean;
  autoplay?: number;
  showDots?: boolean;
  onChange?: (e: VfUiEvent<{ index: number }>) => void;
  id?: string;
  ref?: string;
  className?: string;
}
// A `<section aria-roledescription="carousel">` (WAI-ARIA carousel pattern).
function vfCarousel(props: VfCarouselProps): VfValueInstance<number, { next(): void; prev(): void; goTo(index: number): void; play(): void; pause(): void; }>;
```

## Navigation

### `vsBreadcrumb` — S

The path to the current page; the last item has aria-current="page".

Messages: `breadcrumb.label`

```js
vf.vsBreadcrumb({ items: [{ label: 'Home', href: '#/' }, { label: 'Users', href: '#/users' }, { label: user.name }] })
```

```ts
interface VsBreadcrumbProps {
  items: Array<{ label: VsContent; href?: string }>;
  label?: string;
  id?: string;
  ref?: string;
  className?: string;
}
// A `<nav class="vf-breadcrumb">` with an ordered list.
function vsBreadcrumb(props: VsBreadcrumbProps): SafeHtml;
```

### `vsPagination` · `vfPagination` — P

Page buttons with previous/next and gaps; vf* changes the page and calls onChange.

Messages: `pagination.label`, `pagination.next`, `pagination.page`, `pagination.previous`

```js
vf.vfPagination({ total: 230, pageSize: 20, onChange: (e) => load(e.data.page) })
```

```ts
interface VsPaginationProps {
  total: number;
  page?: number;
  pageSize?: number;
  siblings?: number;
  label?: string;
  id?: string;
  ref?: string;
  className?: string;
}
// Page buttons (data-action "page", data-page) in a `<nav class="vf-pagination">`.
function vsPagination(props: VsPaginationProps): SafeHtml;
interface VfPaginationProps extends VsPaginationProps {
  onChange?: (e: VfUiEvent<{ page: number }>) => void;
}
function vfPagination(props: VfPaginationProps): VfValueInstance<number, { setTotal(total: number): void }>;
```

### `vsTabs` · `vfTabs` — P

Tabs and panels (WAI-ARIA): roving tabindex; vf* adds arrow keys (RTL aware), Home and End.

```js
vf.vfTabs({ label: 'Settings', tabs: [{ id: 'profile', label: 'Profile', content: profileHtml }, { id: 'security', label: 'Security', content: securityHtml }] })
```

```ts
interface VsTabsProps {
  tabs: Array<{ id: string; label: VsContent; content?: VsSlot; disabled?: boolean }>;
  active?: string;
  label?: string;
  id?: string;
  ref?: string;
  className?: string;
}
// Tabs (data-action "tab") and their panels; inactive panels are hidden.
function vsTabs(props: VsTabsProps): SafeHtml;
interface VfTabsProps extends Omit<VsTabsProps, 'tabs'> {
  tabs: Array<{ id: string; label: VsContent; content?: VfSlot; disabled?: boolean }>;
  onChange?: (e: VfUiEvent<{ id: string; index: number }>) => void;
}
// Click, arrow keys (following dir="rtl"), Home and End.
function vfTabs(props: VfTabsProps): VfValueInstance<string | null, { select(id: string): void }>;
```

### `vsAccordion` · `vfAccordion` — P

Sections with heading buttons (aria-expanded) and region panels; one or multiple open.

```js
vf.vfAccordion({ items: faq.map((q) => ({ id: q.id, title: q.question, content: q.answer })) })
```

```ts
interface VsAccordionProps {
  items: Array<{ id: string; title: VsContent; content?: VsSlot; open?: boolean; disabled?: boolean }>;
  open?: string[];
  headingLevel?: 2 | 3 | 4 | 5 | 6;
  id?: string;
  ref?: string;
  className?: string;
}
// Heading buttons (data-action "toggle", aria-expanded) with region panels.
function vsAccordion(props: VsAccordionProps): SafeHtml;
interface VfAccordionProps extends Omit<VsAccordionProps, 'items'> {
  items: Array<{ id: string; title: VsContent; content?: VfSlot; open?: boolean; disabled?: boolean }>;
  multiple?: boolean;
  onToggle?: (e: VfUiEvent<{ id: string; open: boolean; openIds: string[] }>) => void;
}
function vfAccordion(props: VfAccordionProps): VfValueInstance<string[], { open(id: string): void; close(id: string): void; toggle(id: string): void; }>;
```

### `vsStepper` · `vfStepper` — P

The steps of a process: complete, current (aria-current="step"), upcoming.

Messages: `stepper.complete`

```js
vf.vfStepper({ steps: ['Cart', 'Shipping', 'Payment'], active: 1 })
```

```ts
interface VsStepperProps {
  steps: Array<VsContent | { label: VsContent; description?: VsContent }>;
  active?: number;
  clickable?: boolean;
  label?: string;
  id?: string;
  ref?: string;
  className?: string;
}
// An `<ol class="vf-stepper">`: data-state complete / current / upcoming, aria-current="step".
function vsStepper(props: VsStepperProps): SafeHtml;
interface VfStepperProps extends VsStepperProps {
  onChange?: (e: VfUiEvent<{ index: number }>) => void;
}
function vfStepper(props: VfStepperProps): VfValueInstance<number, { next(): void; prev(): void; goTo(index: number): void; }>;
```

### `vsSplitButton` · `vfSplitButton` — P

A main action with a menu of related actions (the vfDropdown menu).

Messages: `splitButton.more`

```js
vf.vfSplitButton({ label: 'Save', variant: 'primary', items: [{ label: 'Save as draft', action: 'draft' }], onClick: save, onSelect: (e) => run(e.data.action) })
```

```ts
interface VsSplitButtonProps {
  label: string | SafeHtml;
  action?: string;
  items: VfMenuItem[];
  variant?: 'secondary' | 'primary' | 'danger' | 'ghost';
  size?: 'md' | 'sm' | 'lg';
  disabled?: boolean;
  menuLabel?: string;
  id?: string;
  ref?: string;
  className?: string;
}
// A main button and a menu button (data-action "menu") in a `role="group"`, with a hidden menu.
function vsSplitButton(props: VsSplitButtonProps): SafeHtml;
interface VfSplitButtonProps extends VsSplitButtonProps {
  placement?: VfPlacement;
  onClick?: (e: VfUiEvent<{ action: string | undefined }>) => void;
  onSelect?: (e: VfUiEvent<{ action: string | undefined; item: VfMenuItem }>) => void;
}
function vfSplitButton(props: VfSplitButtonProps): VfuncInstance & { open(): void; close(): void; isOpen(): boolean };
```

## Overlay

### `vfModal` — F

A modal dialog: focus kept inside and returned, Escape for the top layer, page scroll locked; footer actions through onAction.

Messages: `modal.close`

```js
const dialog = vf.vfModal({ title: 'Edit user', content: form, footer: vf.vsButton({ label: 'Save', action: 'save', variant: 'primary' }), onAction: (e) => save() }); dialog.open();
```

```ts
interface VfModalProps {
  title?: VsContent;
  label?: string;
  content?: VfSlot;
  footer?: VsSlot;
  size?: 'md' | 'sm' | 'lg';
  dismissible?: boolean;
  initialFocus?: string;
  closeLabel?: string;
  onOpen?: (e: VfUiEvent<{}>) => void;
  onClose?: (e: VfUiEvent<{ reason: string }>) => void;
  onAction?: (e: VfUiEvent<{ action: string }>) => void;
  id?: string;
  ref?: string;
  className?: string;
}
// A `role="dialog"` with `aria-modal` (no native <dialog>, same path in IE11).
function vfModal(props: VfModalProps): VfuncInstance & VfModalMethods;
```

### `vfDrawer` — F

vfModal as a full-height panel at the end, start or bottom of the screen.

Messages: `modal.close`

```js
vf.vfDrawer({ title: 'Filters', side: 'end', content: filtersHtml }).open()
```

```ts
interface VfDrawerProps extends VfModalProps {
  side?: 'end' | 'start' | 'bottom';
}
// vfModal as a panel at one side of the screen.
function vfDrawer(props: VfDrawerProps): VfuncInstance & VfModalMethods;
```

### `vfConfirm` — F

A yes/no alertdialog; open() returns a Promise of the answer; danger starts on cancel.

Messages: `confirm.cancel`, `confirm.ok`

```js
const c = vf.vfConfirm({ title: 'Delete 3 items?', variant: 'danger', confirmLabel: 'Delete' }); const ok = await c.open(); c.destroy(); if (ok) remove();
```

```ts
interface VfConfirmProps {
  title: VsContent;
  message?: VsContent;
  confirmLabel?: string;
  cancelLabel?: string;
  variant?: 'primary' | 'danger';
  id?: string;
  className?: string;
}
// A `role="alertdialog"`; Escape, the backdrop and the close button answer false.
function vfConfirm(props: VfConfirmProps): VfuncInstance & { open(): Promise<boolean>; close(reason?: string): void; isOpen(): boolean; };
```

### `vfToast` — F

One notification region (live region); show() adds toasts that go after a while and stay while hovered or focused.

Messages: `toast.dismiss`, `toast.region`

```js
const toast = vf.vfToast(); toast.show({ message: 'Saved', variant: 'success' });
```

```ts
interface VfToastProps {
  position?: 'bottom-end' | 'bottom-start' | 'bottom-center' | 'top-end' | 'top-start' | 'top-center';
  duration?: number;
  max?: number;
  label?: string;
}
// The notification region (a live region added to document.body at once). Create it once.
function vfToast(props?: VfToastProps): VfuncInstance & { show(options: VfToastOptions): string; dismiss(id: string): void; clear(): void; };
```

### `vfDropdown` — F

A button that opens a menu of actions (WAI-ARIA menu button: arrows, Home, End, letters, Escape).

```js
vf.vfDropdown({ trigger: { label: 'Actions' }, items: [{ label: 'Rename', action: 'rename' }, { separator: true }, { label: 'Delete', action: 'delete', danger: true }], onSelect: (e) => run(e.data.action) })
```

```ts
interface VfDropdownProps {
  trigger: Omit<VsButtonProps, 'id' | 'action' | 'aria'>;
  items: VfMenuItem[];
  placement?: VfPlacement;
  label?: string;
  onSelect?: (e: VfUiEvent<{ action: string | undefined; item: VfMenuItem }>) => void;
  id?: string;
  ref?: string;
  className?: string;
}
// A menu button (WAI-ARIA): arrows, Home, End, first letters, Escape, Tab.
function vfDropdown(props: VfDropdownProps): VfuncInstance & { open(): void; close(): void; isOpen(): boolean };
```

### `vfPopover` — F

A non-modal dialog next to a button; Escape, an outside press or leaving it closes.

Messages: `modal.close`

```js
vf.vfPopover({ trigger: { label: 'Help' }, title: 'Shortcuts', content: vf.html`<p>Press / to search.</p>` })
```

```ts
interface VfPopoverProps {
  trigger: Omit<VsButtonProps, 'id' | 'action' | 'aria'>;
  content: VfSlot;
  title?: VsContent;
  label?: string;
  placement?: VfPlacement;
  onOpen?: (e: VfUiEvent<{}>) => void;
  onClose?: (e: VfUiEvent<{ reason: string }>) => void;
  id?: string;
  ref?: string;
  className?: string;
}
// A non-modal `role="dialog"` next to its trigger.
function vfPopover(props: VfPopoverProps): VfuncInstance & { open(): void; close(): void; isOpen(): boolean };
```

## Data

### `vsTable` — S

A table with sortable header buttons (aria-sort, data-action="sort"), row keys, empty and loading states; with vfPagination and app state it makes list screens.

Messages: `common.loading`

```js
vf.vsTable({ caption: 'Users', columns: [{ key: 'name', label: 'Name', sortable: true }, { key: 'age', label: 'Age', align: 'end' }], data: rows, sort: state.sort })
```

```ts
interface VsTableProps<T = any> {
  columns: Array<VsTableColumn<T>>;
  data: T[];
  sort?: { key: string; dir: 'asc' | 'desc' } | null;
  rowKey?: string;
  selected?: string[];
  rowAction?: string;
  caption?: VsContent;
  emptyText?: VsContent;
  loading?: boolean;
  indexBase?: number;
  id?: string;
  ref?: string;
  className?: string;
}
// A `<table>` in a `<div class="vf-table">` (horizontal scroll).
function vsTable<T = any>(props: VsTableProps<T>): SafeHtml;
```

### `vsSparkline` — S

A small static SVG trend (line or bars) with a text alternative; for tables and stat cards.

Messages: `sparkline.empty`, `sparkline.summary`

```js
vf.vsSparkline({ data: [3, 5, 4, 8, 7], type: 'line' })
```

```ts
interface VsSparklineProps {
  data: number[];
  type?: 'line' | 'bar';
  label?: string;
  format?: Intl.NumberFormatOptions;
  id?: string;
  ref?: string;
  className?: string;
}
// A small `<svg role="img" class="vf-sparkline">`; color is currentColor, size from CSS.
function vsSparkline(props: VsSparklineProps): SafeHtml;
```

### `vfGrid` — F

A data grid (data file): sorting, paging, filter, row selection, server mode, fixed header; the G-1 grid contract.

File: `vfunc-ui-data.js` (load after `vfunc-ui.js`). Messages: `grid.range`, `grid.select`, `grid.selectAll`, `grid.selectRow`

```js
vf.vfGrid({ columns, data: users, pageSize: 20, selectable: 'multiple', onSelect: (e) => setSelected(e.data.keys) })
```

```ts
interface VfGridProps<T = any> {
  columns: Array<VsTableColumn<T>>;
  data: T[];
  pageSize?: number;
  selectable?: 'none' | 'single' | 'multiple' | boolean;
  height?: number | string;
  rowKey?: string;
  mode?: 'client' | 'server';
  total?: number;
  query?: string;
  sort?: { key: string; dir: 'asc' | 'desc' } | null;
  loading?: boolean;
  caption?: VsContent;
  emptyText?: VsContent;
  onRowClick?: (e: VfUiEvent<{ row: T | null; key: string; index: number }>) => void;
  onSelect?: (e: VfUiEvent<{ keys: string[]; rows: T[] }>) => void;
  onSort?: (e: VfUiEvent<{ key: string; dir: 'asc' | 'desc' }>) => void;
  onPage?: (e: VfUiEvent<{ page: number }>) => void;
  options?: unknown;
  lib?: unknown;
  id?: string;
  ref?: string;
  className?: string;
}
// A data grid on vsTable and vsPagination.
function vfGrid<T = any>(props: VfGridProps<T>): VfuncInstance & VfGridMethods<T>;
```

### `vsChart` · `vfChart` — P

SVG charts (data file): bar, line, area, pie, donut, sparkline; colors from --vf-chart-* tokens. vfChart adds tooltips, legend toggles and redraws at its width.

File: `vfunc-ui-data.js` (load after `vfunc-ui.js`). Messages: `chart.label`, `chart.legend`, `chart.point`

```js
vf.vfChart({ type: 'line', label: 'Monthly sales', data: { labels: ['Jan', 'Feb', 'Mar'], series: [{ name: '2026', data: [12, 19, 15] }] } })
```

```ts
interface VsChartProps {
  type?: VfChartType;
  data: VfChartData;
  label?: string;
  height?: number;
  width?: number;
  valueFormat?: Intl.NumberFormatOptions;
  legend?: boolean;
  dataTable?: boolean;
  id?: string;
  ref?: string;
  className?: string;
}
// A `<figure class="vf-chart">` with an `<svg role="img">`.
function vsChart(props: VsChartProps): SafeHtml;
interface VfChartProps extends VsChartProps {
  onClick?: (e: VfUiEvent<{ series: string | null; index: number; label: string | number; value: number | null }>) => void;
  options?: unknown;
  lib?: unknown;
}
// vsChart with focusable marks and a tooltip, legend toggles, a fade-in on new data, width tracking.
function vfChart(props: VfChartProps): VfuncInstance & VfChartMethods;
```

## Adapters (same props and methods as the built-in grid / chart, plus `lib` and `options`)

### `vfChartChartjs`

File: `vfunc-chart-chartjs.js`

```ts
interface VfChartChartjsProps extends VfChartProps {
  lib?: unknown;
  options?: Record<string, unknown>;
}
function vfChartChartjs(props: VfChartChartjsProps): VfuncInstance & VfChartMethods;
```

### `vfChartEcharts`

File: `vfunc-chart-echarts.js`

```ts
interface VfChartEchartsProps extends VfChartProps {
  lib?: unknown;
  options?: Record<string, unknown>;
}
function vfChartEcharts(props: VfChartEchartsProps): VfuncInstance & VfChartMethods;
```

### `vfGridAg`

File: `vfunc-grid-ag.js`

```ts
interface VfGridAgProps<T = any> extends VfGridProps<T> {
  lib?: unknown;
  options?: Record<string, unknown>;
}
function vfGridAg<T = any>(props: VfGridAgProps<T>): VfuncInstance & VfGridMethods<T>;
```

### `vfGridTabulator`

File: `vfunc-grid-tabulator.js`

```ts
interface VfGridTabulatorProps<T = any> extends VfGridProps<T> {
  lib?: unknown;
  options?: Record<string, unknown>;
}
function vfGridTabulator<T = any>(props: VfGridTabulatorProps<T>): VfuncInstance & VfGridMethods<T>;
```
