# FigUI3

A lightweight, zero-dependency web components library for building Figma plugin and widget UIs with native look and feel.

[![npm version](https://img.shields.io/npm/v/@rogieking/figui3.svg)](https://www.npmjs.com/package/@rogieking/figui3)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

**[Live Playground & Demos](https://rog.ie/figui3/)**

## Features

- Figma UI3 design system
- Zero dependencies
- ~228 KB JS + ~102 KB CSS minified
- Built with Web Components
- Automatic light/dark theme support
- Accessible with ARIA attributes and keyboard navigation
- Framework agnostic (React, Vue, Svelte, or vanilla JS)

## Quick Start

Install:

```bash
npm install @rogieking/figui3
```

Import:

```js
import "@rogieking/figui3/fig.css";
import "@rogieking/figui3/fig.js";
```

Or use a CDN:

```html
<link rel="stylesheet" href="https://unpkg.com/@rogieking/figui3@latest/dist/fig.css" />
<script type="module" src="https://unpkg.com/@rogieking/figui3@latest/dist/fig.js"></script>
```

Minimal example:

```html
<fig-field direction="horizontal">
  <label>Color</label>
  <fig-input-color value="#FF5733" text="true" alpha="true"></fig-input-color>
</fig-field>
<fig-button variant="primary">Save</fig-button>
```

---

## Components

| Component | Tag | Description |
|---|---|---|
| [Button](#button) | `<fig-button>` | Buttons with variants, toggle, select, upload |
| [Dropdown](#dropdown) | `<fig-dropdown>` | Native select wrapper with Figma styling |
| [Combo Input](#combo-input) | `<fig-combo-input>` | Text input with dropdown suggestions |
| [Checkbox](#checkbox) | `<fig-checkbox>` | Checkbox with indeterminate state |
| [Radio](#radio) | `<fig-radio>` | Radio button |
| [Switch](#switch) | `<fig-switch>` | Toggle switch |
| [Slider](#slider) | `<fig-slider>` | Range, hue, opacity, delta, stepper |
| [Field Slider](#field-slider) | `<fig-field-slider>` | Labeled field + slider combo |
| [Text Input](#text-input) | `<fig-input-text>` | Styled text/textarea input |
| [Number Input](#number-input) | `<fig-input-number>` | Numeric input with units |
| [Input Angle](#input-angle) | `<fig-input-angle>` | Angle/rotation dial and text input |
| [Chit](#chit) | `<fig-chit>` | Color/gradient/image swatch |
| [Color Tip](#color-tip) | `<fig-color-tip>` | Compact color tip with picker |
| [Color Input](#color-input) | `<fig-input-color>` | Color picker with hex/alpha |
| [Input Palette](#input-palette) | `<fig-input-palette>` | Editable multi-color palette |
| [Gradient Input](#gradient-input) | `<fig-input-gradient>` | Gradient editor with stops |
| [Fill Input](#fill-input) | `<fig-input-fill>` | Solid, gradient, image, video fill |
| [Fill Picker](#fill-picker) | `<fig-fill-picker>` | Full fill picker dialog |
| [Tabs](#tabs) | `<fig-tabs>` / `<fig-tab>` | Tabbed navigation |
| [Segmented Control](#segmented-control) | `<fig-segmented-control>` / `<fig-segment>` | Segmented button group |
| [Chooser](#chooser) | `<fig-chooser>` / `<fig-choice>` | Selection list with drag scroll |
| [Field](#field) | `<fig-field>` | Form field wrapper with layout |
| [Joystick](#joystick) | `<fig-joystick>` | 2D position input |
| [Origin Grid](#origin-grid) | `<fig-origin-grid>` | Transform-origin grid |
| [Easing Curve](#easing-curve) | `<fig-easing-curve>` | Bezier/spring curve editor |
| [3D Rotate](#3d-rotate) | `<fig-3d-rotate>` | 3D cube rotation control |
| [Handle](#handle) | `<fig-handle>` | Draggable handle on a surface |
| [Canvas Control](#canvas-control) | `<fig-canvas-control>` | Point with optional radius, angle, or second point |
| [Dialog](#dialog) | `<fig-dialog>` | Modal/non-modal dialog |
| [Popup](#popup) | `<fig-popup>` | Anchored floating surface |
| [Toast](#toast) | `<fig-toast>` | Toast notification |
| [Tooltip](#tooltip) | `<fig-tooltip>` | Hover/click tooltip |
| [Header](#header) | `<fig-header>` | Section header |
| [Layer](#layer) | `<fig-layer>` | Collapsible layer list item |
| [Media](#media) | `<fig-media>` | Shared media host for image/video |
| [Image](#image) | `<fig-image>` | Image display/upload |
| [Video](#video) | `<fig-video>` | Video display/upload with playback controls |
| [Avatar](#avatar) | `<fig-avatar>` | Profile image or initials |
| [Spinner](#spinner) | `<fig-spinner>` | Loading spinner |
| [Shimmer](#shimmer) | `<fig-shimmer>` | Shimmer loading placeholder |
| [Skeleton](#skeleton) | `<fig-skeleton>` | Skeleton loading placeholder |

---

### Form Controls

#### Button

`<fig-button>` — [demo](https://rog.ie/figui3/#button)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `variant` | string | `"primary"` | `"primary"`, `"secondary"`, `"ghost"`, `"link"` |
| `type` | string | `"button"` | `"button"`, `"toggle"`, `"submit"`, `"select"`, `"upload"` |
| `size` | string | — | `"large"`, `"compact"` |
| `selected` | boolean | `false` | Selected state (toggle type) |
| `disabled` | boolean | `false` | Disabled state |
| `icon` | boolean | `false` | Icon-only styling |
| `href` | string | — | URL for link buttons |
| `target` | string | — | Link target (e.g. `"_blank"`) |

```html
<fig-button>Primary</fig-button>
<fig-button variant="secondary">Secondary</fig-button>
<fig-button type="toggle" selected="true">Toggle</fig-button>
<fig-button variant="ghost" icon>
  <svg><!-- icon --></svg>
</fig-button>
```

---

#### Dropdown

`<fig-dropdown>` — [demo](https://rog.ie/figui3/#dropdown)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | Selected value |
| `type` | string | `"select"` | `"select"` or `"dropdown"` |
| `experimental` | string | — | Feature flags (e.g. `"modern"` for `appearance: base-select`) |

```html
<fig-dropdown value="2">
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
</fig-dropdown>
```

---

#### Combo Input

`<fig-combo-input>` — [demo](https://rog.ie/figui3/#combo-input)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `options` | string | — | Comma-separated suggestion list |
| `placeholder` | string | — | Placeholder text |
| `value` | string | — | Current value |
| `disabled` | boolean | `false` | Disabled state |

```html
<fig-combo-input options="House, Apartment, Condo" placeholder="Residence type"></fig-combo-input>
```

---

#### Checkbox

`<fig-checkbox>` — [demo](https://rog.ie/figui3/#checkbox)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `checked` | boolean | `false` | Whether checked |
| `indeterminate` | boolean | `false` | Indeterminate state |
| `disabled` | boolean | `false` | Disabled state |
| `name` | string | — | Form field name |
| `value` | string | — | Value when checked |
| `label` | string | — | Label text (alternative to slotted content) |

```html
<fig-checkbox>Accept terms</fig-checkbox>
<fig-checkbox checked>Selected</fig-checkbox>
<fig-checkbox indeterminate>Parent option</fig-checkbox>
```

---

#### Radio

`<fig-radio>` — [demo](https://rog.ie/figui3/#radio)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `checked` | boolean | `false` | Whether selected |
| `disabled` | boolean | `false` | Disabled state |
| `name` | string | — | Radio group name |
| `value` | string | — | Value when selected |

```html
<fig-radio name="size" value="small">Small</fig-radio>
<fig-radio name="size" value="medium" checked>Medium</fig-radio>
<fig-radio name="size" value="large">Large</fig-radio>
```

---

#### Switch

`<fig-switch>` — [demo](https://rog.ie/figui3/#switch)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `checked` | boolean | `false` | Whether on |
| `disabled` | boolean | `false` | Disabled state |
| `name` | string | — | Form field name |
| `value` | string | — | Value when on |

```html
<fig-switch>Enable notifications</fig-switch>
<fig-switch checked>Active feature</fig-switch>
```

---

### Inputs

#### Slider

`<fig-slider>` — [demo](https://rog.ie/figui3/#slider)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `type` | string | `"range"` | `"range"`, `"hue"`, `"opacity"`, `"delta"`, `"stepper"` |
| `value` | number | — | Current value |
| `min` | number | `0` | Minimum |
| `max` | number | `100` | Maximum |
| `step` | number | `1` | Step increment |
| `default` | number | — | Default/reset value (shown as marker) |
| `text` | boolean | `false` | Show text input |
| `placeholder` | string | `"##"` | Text input placeholder |
| `units` | string | — | Unit label (e.g. `"%"`, `"px"`) |
| `transform` | number | — | Display value multiplier |
| `color` | string | — | Track color (opacity type) |
| `variant` | string | — | Visual variant (e.g. `"neue"`) |
| `precision` | number | — | Decimal places for output |
| `disabled` | boolean | `false` | Disabled state |

**Events:** `input` (continuous), `change` (on release).

```html
<fig-slider min="0" max="100" value="50" text="true" units="%"></fig-slider>
<fig-slider type="hue" value="180"></fig-slider>
<fig-slider type="opacity" value="75" color="#FF5733" text="true" units="%"></fig-slider>
```

---

#### Field Slider

`<fig-field-slider>`

Wraps a `<fig-field>` and `<fig-slider>` into a single labeled control. All slider attributes (except `label`, `direction`) are forwarded to the inner slider.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `label` | string | — | Field label text |
| `direction` | string | `"column"` | Layout direction |
| *slider attrs* | — | — | All `<fig-slider>` attributes are forwarded |

**Events:** `input`, `change` — forwarded from the inner slider.

```html
<fig-field-slider label="Opacity" min="0" max="100" value="75" text="true" units="%"></fig-field-slider>
```

---

#### Text Input

`<fig-input-text>` — [demo](https://rog.ie/figui3/#text-input)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | Input value |
| `placeholder` | string | — | Placeholder text |
| `type` | string | `"text"` | `"text"` or `"number"` |
| `disabled` | boolean | `false` | Disabled state |
| `multiline` | boolean | `false` | Use textarea |
| `min` | number | — | Min (number type) |
| `max` | number | — | Max (number type) |
| `step` | number | — | Step (number type) |
| `transform` | number | — | Display multiplier |

```html
<fig-input-text value="Hello" placeholder="Enter text..."></fig-input-text>
<fig-input-text multiline placeholder="Enter description..."></fig-input-text>
```

---

#### Number Input

`<fig-input-number>` — [demo](https://rog.ie/figui3/#number-input)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | Numeric value |
| `placeholder` | string | — | Placeholder text |
| `min` | number | — | Minimum |
| `max` | number | — | Maximum |
| `step` | number | — | Step increment |
| `units` | string | — | Unit string (e.g. `"px"`, `"%"`) |
| `units-disallow` | string | `"px"` | Comma-separated unit disallow list (e.g. `"px,rem"`) |
| `unit-position` | string | `"suffix"` | `"suffix"` or `"prefix"` |
| `transform` | number | — | Display multiplier |
| `steppers` | boolean | `false` | Show spin buttons |
| `disabled` | boolean | `false` | Disabled state |

```html
<fig-input-number value="100" units="px"></fig-input-number>
<fig-input-number value="50" units="%" min="0" max="100"></fig-input-number>
```

---

#### Input Angle

`<fig-input-angle>` — [demo](https://rog.ie/figui3/#angle-input)

Angle/rotation input with circular dial, optional text input, multi-unit support, and unbounded winding past 360deg. Accepts unit suffixes in text input (`90deg`, `3.14rad`, `0.5turn`).

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | number | `0` | Angle value |
| `precision` | number | `1` | Decimal places |
| `text` | boolean | `false` | Show text input |
| `dial` | boolean | `true` | Show circular dial |
| `min` | number | — | Minimum (omit for unbounded) |
| `max` | number | — | Maximum (omit for unbounded) |
| `units` | string | `"°"` | `"°"` / `"deg"`, `"rad"`, `"turn"` |
| `rotations` | boolean | `false` | Show rotation counter |

**Events:** `input` (continuous), `change` (on release).

```html
<fig-input-angle value="90" text="true"></fig-input-angle>
<fig-input-angle text="true" units="rad" value="3.14159"></fig-input-angle>
<fig-input-angle text="true" rotations value="1080"></fig-input-angle>
```

---

### Color & Fill

#### Chit

`<fig-chit>` — [demo](https://rog.ie/figui3/#chit)

A color/gradient/image swatch element with checkerboard background for alpha.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `background` | string | — | CSS background value |
| `size` | string | `"small"` | `"small"` or `"large"` |
| `selected` | boolean | `false` | Selection ring |
| `disabled` | boolean | `false` | Disabled state |
| `alpha` | number | — | Opacity (0-1) |

```html
<fig-chit background="#FF5733"></fig-chit>
<fig-chit background="linear-gradient(90deg, #FF0000, #0000FF)" size="large"></fig-chit>
```

---

#### Color Tip

`<fig-color-tip>` — [demo](https://rog.ie/figui3/#color-tip)

A compact solid-color swatch that wraps `<fig-fill-picker>`. Used inside gradient handles and other controls.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | Color string (hex/rgb/hsl/named) |
| `selected` | boolean | `false` | Selected state |
| `disabled` | boolean | `false` | Disabled state |
| `alpha` | boolean | `false` | Show alpha controls |
| `control` | string | — | `"add"` or `"remove"` for icon-only mode |

**Events:**

| Event | Detail |
|---|---|
| `input` | `{ color, opacity? }` — while editing |
| `change` | `{ color, opacity? }` — on commit |
| `add` | — (when `control="add"` is clicked) |
| `remove` | — (when `control="remove"` is clicked) |

```html
<fig-color-tip value="#FF5733"></fig-color-tip>
<fig-color-tip value="#00AAFF" alpha="true"></fig-color-tip>
```

---

#### Color Input

`<fig-input-color>` — [demo](https://rog.ie/figui3/#color-input)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | Hex color (e.g. `"#FF5733"` or `"#FF573380"`) |
| `text` | boolean | `false` | Show hex text input |
| `alpha` | boolean | `false` | Show alpha slider |
| `picker` | string | `"native"` | `"native"`, `"figma"`, `"false"` |
| `mode` | string | — | Color mode (`"hex"`, `"rgb"`, `"hsl"`) |
| `experimental` | string | — | Feature flags |
| `disabled` | boolean | `false` | Disabled state |

**Events:**

| Event | Detail |
|---|---|
| `input` | `{ color, alpha, hsv: { h, s, v, a } }` |
| `change` | `{ color, alpha, hsv: { h, s, v, a } }` |

```html
<fig-input-color value="#FF5733" text="true" alpha="true"></fig-input-color>
<fig-input-color value="#FF5733" text="true" alpha="true" picker="figma"></fig-input-color>
```

---

#### Input Palette

`<fig-input-palette>`

An editable palette of solid colors, each rendered as a `<fig-input-color>` swatch with add/remove support.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | JSON array of hex strings or `{color, alpha}` objects |
| `disabled` | boolean | `false` | Disabled state |
| `min` | number | `2` | Minimum number of colors |
| `max` | number | `8` | Maximum (add button hidden at max) |
| `expanded` | boolean | `false` | Show text + alpha inputs per color |
| `add` | boolean | `true` | Show add-color button (`add="false"` hides it) |

**Events:**

| Event | Detail |
|---|---|
| `input` | Full color array (during editing) |
| `change` | Full color array (on commit or add) |

```html
<fig-input-palette value='["#FF0000","#00FF00","#0000FF"]'></fig-input-palette>
<fig-input-palette value='[{"color":"#FF0000","alpha":0.5},{"color":"#00FF00","alpha":1}]' open></fig-input-palette>
```

---

#### Gradient Input

`<fig-input-gradient>`

A gradient editor with draggable stops. Opens `<fig-fill-picker>` locked to gradient mode.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | JSON gradient fill data |
| `disabled` | boolean | `false` | Disabled state |
| `experimental` | string | — | Picker feature flags |
| `picker-*` | string | — | Passthrough picker attributes |
| `picker-anchor` | string | `"self"` | Anchor selector or `"self"` |

Supported interpolation spaces: `srgb`, `srgb-linear`, `display-p3`, `oklab`, `oklch` (with `hueInterpolation`: `shorter`, `longer`, `increasing`, `decreasing`).

**Events:**

| Event | Detail |
|---|---|
| `input` | `{ type, gradient, css }` |
| `change` | `{ type, gradient, css }` |

```html
<fig-input-gradient
  value='{"type":"gradient","gradient":{"type":"linear","angle":90,"interpolationSpace":"oklab","stops":[{"position":0,"color":"#FF0000","opacity":100},{"position":100,"color":"#0000FF","opacity":100}]}}'
></fig-input-gradient>
```

---

#### Fill Input

`<fig-input-fill>` — [demo](https://rog.ie/figui3/#fill-input)

A comprehensive fill input supporting solid, gradient, image, and video fills.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | JSON fill data |
| `disabled` | boolean | `false` | Disabled state |
| `mode` | string | — | Lock to a fill mode |
| `experimental` | string | — | Feature flags |
| `alpha` | boolean | `true` | Show alpha controls |

**Events:**

| Event | Detail |
|---|---|
| `input` | `{ type, color?, gradient?, image?, video?, css }` |
| `change` | `{ type, color?, gradient?, image?, video?, css }` |

```html
<fig-input-fill value='{"type":"solid","color":"#FF5733","opacity":100}'></fig-input-fill>
```

---

#### Fill Picker

`<fig-fill-picker>` — [demo](https://rog.ie/figui3/#fill-picker)

Full fill picker dialog supporting solid, gradient, image, video, and webcam. Wraps a trigger element (e.g. `<fig-chit>`).

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | JSON fill value |
| `disabled` | boolean | `false` | Disabled state |
| `alpha` | boolean | `true` | Show alpha controls |
| `mode` | string | — | Lock to mode: `"solid"`, `"gradient"`, `"image"`, `"video"`, `"webcam"` |
| `experimental` | string | — | Feature flags |

**Events:**

| Event | Detail |
|---|---|
| `input` | `{ type, gradient?, color?, css }` |
| `change` | `{ type, gradient?, color?, css }` |

```html
<fig-fill-picker value='{"type":"solid","color":"#FF5733"}'>
  <fig-chit></fig-chit>
</fig-fill-picker>
```

---

### Selection

#### Tabs

`<fig-tabs>` / `<fig-tab>` — [demo](https://rog.ie/figui3/#tabs)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | Selected tab value |
| `name` | string | — | Tabs group identifier |
| `disabled` | boolean | `false` | Disable all tabs |

**Events:** `input`, `change` with selected tab value.

```html
<fig-tabs value="tab1">
  <fig-tab value="tab1">General</fig-tab>
  <fig-tab value="tab2">Advanced</fig-tab>
</fig-tabs>
```

---

#### Segmented Control

`<fig-segmented-control>` / `<fig-segment>` — [demo](https://rog.ie/figui3/#segmented-control)

| Attribute | Type | Default | Description |
|---|---|---|---|
| `name` | string | — | Group identifier |
| `value` | string | — | Selected segment value |
| `animated` | boolean | `false` | Animate indicator transitions |
| `sizing` | string | `"equal"` | `"equal"` or `"auto"` width mode |

**Events:** `input`, `change` — detail contains the selected value.

```html
<fig-segmented-control>
  <fig-segment value="left" selected="true">Left</fig-segment>
  <fig-segment value="center">Center</fig-segment>
  <fig-segment value="right">Right</fig-segment>
</fig-segmented-control>
```

---

#### Chooser

`<fig-chooser>` / `<fig-choice>`

A selection list controller. `<fig-choice>` elements are selectable options within a `<fig-chooser>`.

**fig-chooser attributes:**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | Selected choice value |
| `choice-element` | string | `"fig-choice"` | CSS selector for child choices |
| `layout` | string | `"vertical"` | `"vertical"`, `"horizontal"`, `"grid"` |
| `disabled` | boolean | `false` | Disabled state |
| `drag` | boolean | `false` | Enable drag-to-scroll |
| `overflow` | string | — | Overflow behavior |
| `loop` | boolean | `false` | Loop keyboard navigation |

**fig-choice attributes:**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | Choice identifier |
| `selected` | boolean | `false` | Selected state |
| `disabled` | boolean | `false` | Disabled state |

**Events (on fig-chooser):** `input`, `change` — detail is the selected value string.

```html
<fig-chooser value="opt1">
  <fig-choice value="opt1">Option 1</fig-choice>
  <fig-choice value="opt2">Option 2</fig-choice>
  <fig-choice value="opt3">Option 3</fig-choice>
</fig-chooser>
```

---

### Spatial Controls

#### Joystick

`<fig-joystick>`

A 2D position input control with optional X/Y fields.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | `"50% 50%"` | Position as percentages |
| `precision` | number | — | Decimal places |
| `transform` | number | — | Output scaling |
| `fields` | boolean | `false` | Show X/Y inputs |
| `coordinates` | string | `"screen"` | `"screen"` (0,0 top-left) or `"math"` (0,0 bottom-left) |
| `aspect-ratio` | string | `"1 / 1"` | Plane ratio |

**Events:**

| Event | Detail |
|---|---|
| `input` | `{ x, y, value }` — while dragging |
| `change` | `{ x, y, value }` — on release |

```html
<fig-joystick value="50% 50%" fields="true" precision="2"></fig-joystick>
```

---

#### Origin Grid

`<fig-origin-grid>`

A transform-origin grid control with a draggable handle and optional X/Y percentage fields.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | `"50% 50%"` | CSS transform-origin pair |
| `precision` | number | `0` | Decimal places |
| `aspect-ratio` | string | — | Grid aspect ratio |
| `drag` | boolean | `true` | Enable handle dragging |
| `fields` | boolean | `false` | Show X/Y fields |

**Events:**

| Event | Detail |
|---|---|
| `input` | `{ value, x, y }` — while dragging |
| `change` | `{ value, x, y }` — on release |

```html
<fig-origin-grid value="50% 50%" drag="true" fields="true"></fig-origin-grid>
```

---

#### Easing Curve

`<fig-easing-curve>`

An interactive bezier or spring easing curve editor with draggable control points and an optional preset dropdown.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | Bezier: `"0.42, 0, 0.58, 1"` or Spring: `"spring(200, 15, 1)"` |
| `precision` | number | `2` | Decimal places |
| `aspect-ratio` | string | — | Editor aspect ratio |
| `dropdown` | boolean | `false` | Show preset dropdown |

**Static:** `FigEasingCurve.PRESETS` — built-in preset array. `FigEasingCurve.curveIcon(value)` — SVG icon helper.

**Events:**

| Event | Detail |
|---|---|
| `input` | `{ mode, value, cssValue, preset }` — while dragging |
| `change` | `{ mode, value, cssValue, preset }` — on release |

```html
<fig-easing-curve value="0.42, 0, 0.58, 1" dropdown="true"></fig-easing-curve>
<fig-easing-curve value="spring(200, 15, 1)"></fig-easing-curve>
```

---

#### 3D Rotate

`<fig-3d-rotate>`

An interactive 3D cube for setting rotation values. Supports drag interaction and optional X/Y/Z number fields.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | CSS transform, e.g. `"rotateX(20deg) rotateY(-35deg) rotateZ(0deg)"` |
| `precision` | number | `1` | Decimal places |
| `aspect-ratio` | string | — | Cube container ratio |
| `fields` | boolean | `false` | Show X/Y/Z number inputs |
| `perspective` | string | — | CSS perspective value |
| `perspective-origin` | string | — | CSS perspective-origin |
| `transform-origin` | string | — | CSS transform-origin |
| `selected` | string | — | Highlighted face |
| `drag` | boolean | `true` | Enable drag rotation |

**Events:**

| Event | Detail |
|---|---|
| `input` | `{ value, rotateX, rotateY, rotateZ }` — while dragging |
| `change` | `{ value, rotateX, rotateY, rotateZ }` — on release |

```html
<fig-3d-rotate value="rotateX(20deg) rotateY(-35deg) rotateZ(0deg)" fields="true"></fig-3d-rotate>
```

---

#### Handle

`<fig-handle>`

A draggable handle element. Positioned on a `drag-surface` container with axis constraints and snapping. Used internally by gradient editors and spatial controls, but also available standalone.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | — | Position as `"X% Y%"` |
| `color` | string | — | Handle color |
| `selected` | boolean | `false` | Selected state |
| `disabled` | boolean | `false` | Disabled state |
| `drag` | boolean | `false` | Enable dragging |
| `drag-surface` | string | — | CSS selector for drag container (defaults to parent) |
| `drag-axes` | string | `"xy"` | Constrain axes: `"x"`, `"y"`, `"xy"` |
| `drag-snapping` | string | — | Snapping behavior |
| `type` | string | — | `"color"` for color handle with `fig-color-tip` |
| `control` | string | — | `"add"` or `"remove"` delegated to color tip |
| `hit-area` | string | — | Expanded interaction zone (unitless px). `"8"`, `"8 12"` (v h), or `"8 circle"` |
| `hit-area-mode` | string | `"handle"` | `"handle"` proxies to handle drag/select; `"delegate"` emits `hitareadown` event |

**Events:**

| Event | Detail |
|---|---|
| `input` | `{ x, y, px, py, shiftKey }` — while dragging |
| `change` | `{ x, y, px, py }` — on release |
| `add` | — (when control="add") |
| `remove` | — (when control="remove") |
| `hitareadown` | `{ originalEvent }` — when `hit-area-mode="delegate"` and the hit area is clicked |

```html
<div style="position: relative; width: 200px; height: 200px; background: #eee;">
  <fig-handle drag="true" value="50% 50%"></fig-handle>
</div>
```

---

#### Canvas Control

`<fig-canvas-control>` — [demo](https://rog.ie/figui3/#canvas-control)

A composite point control with optional radius circle, angle handle, or second point. Place inside a positioned container; the component uses `display: contents` and does not create its own box.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `type` | string | `"point"` | `"point"`, `"color"`, `"point-radius"`, `"point-radius-angle"`, `"point-point"` |
| `value` | JSON string | — | `{ "x": 50, "y": 50 }` — see type-specific shapes below |
| `name` | string | — | Tooltip label(s). Comma-separated for two handles: `"Start, End"` |
| `color` | string | — | Passthrough color for `type="color"` handle |
| `tooltips` | string | `"true"` | Show value tooltips on interaction |
| `disabled` | boolean | `false` | Disable all interaction |
| `drag-surface` | string | `"parent"` | Forwarded to inner `fig-handle`s |
| `snapping` | string | `"false"` | `"false"`, `"true"`, `"modifier"` — applies to all handles |

**Value shapes by type:**

| Type | Value shape |
|---|---|
| `point`, `color` | `{ x, y }` |
| `point-radius` | `{ x, y, radius }` — radius: number (px) or `"25%"` |
| `point-radius-angle` | `{ x, y, radius, angle }` — angle in degrees |
| `point-point` | `{ x, y, x2, y2 }` — angle and length inferred |

**Events:**

| Event | Detail |
|---|---|
| `input` | Value object (shape depends on type) — while dragging |
| `change` | Value object (shape depends on type) — on release |

For `point-point`, both handles support direct drag (with a dynamic directional resize cursor) and rotation via their hit area (dragging from the hit area rotates around the opposite handle at fixed distance, with a rotate cursor).

```html
<div style="position: relative; width: 200px; height: 200px; background: #eee;">
  <fig-canvas-control
    type="point-point"
    name="Start, End"
    value='{"x":25,"y":25,"x2":75,"y2":75}'
    snapping="modifier"
  ></fig-canvas-control>
</div>
```

---

### Layout & Feedback

#### Field

`<fig-field>` — [demo](https://rog.ie/figui3/#field)

A form field wrapper with flexible layout. Automatically links `<label>` to the first `fig-*` child for accessibility.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `direction` | string | `"column"` | `"column"`, `"row"`, `"horizontal"` |
| `columns` | string | — | Split preset: `"thirds"` or `"half"` |
| `label` | string | — | Programmatic label text |

```html
<fig-field direction="horizontal" columns="thirds">
  <label>Opacity</label>
  <fig-slider value="50" text="true" units="%"></fig-slider>
</fig-field>
```

---

#### Dialog

`<fig-dialog>` — [demo](https://rog.ie/figui3/#dialog)

A modal/non-modal dialog. Uses `is="fig-dialog"` on a native `<dialog>` element.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `open` | boolean | `false` | Visible state |
| `modal` | boolean | `false` | Modal mode |
| `drag` | boolean | `false` | Draggable |
| `handle` | string | — | CSS selector for drag handle |
| `position` | string | — | `"center center"`, `"top left"`, etc. |

```html
<dialog is="fig-dialog" id="myDialog" modal drag handle="fig-header" position="center center">
  <fig-header>Dialog Title</fig-header>
  <fig-content><p>Content here.</p></fig-content>
</dialog>
```

---

#### Popup

`<fig-popup>` — [demo](https://rog.ie/figui3/#popup)

An anchored floating surface built on `<dialog>` with collision-aware positioning.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `anchor` | string | — | CSS selector for anchor element |
| `position` | string | `"top center"` | Placement |
| `offset` | string | `"0 0"` | X/Y offset |
| `viewport-margin` | string | `"8"` | Viewport safety margin |
| `variant` | string | — | `"popover"` for beak styling |
| `theme` | string | — | `"light"`, `"dark"`, `"menu"` |
| `closedby` | string | `"any"` | `"any"`, `"closerequest"`, `"none"` |
| `open` | boolean | `false` | Open state |
| `drag` | boolean | `false` | Draggable |
| `handle` | string | — | CSS selector for drag handle |
| `autoresize` | boolean | `false` | Auto-resize to content |

```html
<dialog is="fig-popup" anchor="#my-button" position="center right" variant="popover">
  <fig-header><h3>Popup</h3></fig-header>
</dialog>
```

---

#### Toast

`<fig-toast>` — [demo](https://rog.ie/figui3/#toast)

A toast notification. Uses `is="fig-toast"` on a native `<dialog>`.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `duration` | number | `5000` | Auto-dismiss ms (0 = no dismiss) |
| `offset` | number | `16` | Distance from bottom |
| `theme` | string | `"dark"` | `"dark"`, `"light"`, `"danger"`, `"brand"`, `"auto"` |

```html
<dialog is="fig-toast" id="myToast" theme="brand" duration="3000">
  Settings saved!
</dialog>
<fig-button onclick="document.getElementById('myToast').showToast()">Show</fig-button>
```

---

#### Tooltip

`<fig-tooltip>` — [demo](https://rog.ie/figui3/#tooltip)

Contextual tooltip on hover or click. Auto-repositions when the child element moves.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `text` | string | — | Tooltip text |
| `action` | string | `"hover"` | `"hover"`, `"click"`, `"manual"` |
| `delay` | number | `500` | Show delay in ms |
| `offset` | string | — | `"left,top,right,bottom"` |
| `show` | boolean | `false` | Persistent show state |
| `open` | boolean | `false` | Programmatic show/hide |

```html
<fig-tooltip text="Helpful info">
  <fig-button>Hover me</fig-button>
</fig-tooltip>
```

---

#### Header

`<fig-header>` — [demo](https://rog.ie/figui3/#header)

A section header component.

```html
<fig-header>Section Title</fig-header>
```

---

#### Layer

`<fig-layer>` — [demo](https://rog.ie/figui3/#layer)

A collapsible layer list item with expand/collapse and visibility toggling. Supports nesting.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `open` | boolean | `false` | Whether children are expanded |
| `visible` | boolean | `true` | Whether the layer is visible |

**Events:** `openchange` (detail: `{ open }`), `visibilitychange` (detail: `{ visible }`).

```html
<fig-layer open="true">
  <div class="fig-layer-row">
    <span class="fig-layer-icon"></span>
    <span class="fig-layer-name">Group 1</span>
  </div>
  <fig-layer>
    <div class="fig-layer-row">
      <span class="fig-layer-icon"></span>
      <span class="fig-layer-name">Child 1</span>
    </div>
  </fig-layer>
</fig-layer>
```

---

#### Media

`<fig-media>`

Unified media component that supports image/video modes and shared sizing/upload behavior. Renders a real `<img>` or `<video>` inside the host. By default the host shrinkwraps to the inner media's intrinsic size; set `size` for a token-sized square, or `aspect-ratio` to fill the container width with a fixed ratio.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `type` | string | `"image"` | `"image"` or `"video"` |
| `src` | string | — | Media URL |
| `alt` | string | `""` | Alt text forwarded to the generated `<img>` (image mode) |
| `upload` | boolean | `false` | Show upload overlay (`fig-input-file`) |
| `label` | string | `"Upload"` | Upload button label |
| `size` | string | — | `small` \| `medium` \| `large` \| `auto` (token-sized square) |
| `aspect-ratio` | string | — | CSS aspect-ratio (e.g. `"16 / 9"`); fills container width |
| `fit` | string | `"contain"` | CSS object-fit (`"cover"`, `"contain"`, etc.) |
| `checkerboard` | boolean | `false` | Show checkerboard behind transparent media |
| `controls` | boolean | `false` | Show playback controls for video |
| `autoplay` | boolean | `false` | Video autoplay |
| `loop` | boolean | `false` | Video loop |
| `muted` | boolean | `false` | Video muted |
| `poster` | string | — | Video poster URL |

```html
<fig-media type="image" src="photo.jpg"></fig-media>
<fig-media type="image" src="photo.jpg" aspect-ratio="16 / 9" fit="cover"></fig-media>
<fig-media type="video" src="clip.mp4" controls muted></fig-media>
```

---

#### Image

`<fig-image>` — [demo](https://rog.ie/figui3/#image)

An image display component with optional upload, aspect ratio, and object-fit control. Renders a real `<img>` inside the host; by default the host shrinkwraps to the image's intrinsic size.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `src` | string | — | Image URL |
| `alt` | string | `""` | Alt text forwarded to the generated `<img>` |
| `upload` | boolean | `false` | Show upload overlay (`fig-input-file`) |
| `label` | string | `"Upload"` | Upload button label |
| `size` | string | — | `small` \| `medium` \| `large` \| `auto` (token-sized square) |
| `aspect-ratio` | string | — | CSS aspect-ratio (e.g. `"16 / 9"`); fills container width |
| `fit` | string | `"contain"` | CSS object-fit (`"cover"`, `"contain"`, etc.) |
| `checkerboard` | boolean | `false` | Show checkerboard behind transparent images |

```html
<fig-image src="photo.jpg"></fig-image>
<fig-image src="photo.jpg" aspect-ratio="16 / 9" fit="cover"></fig-image>
<fig-image upload label="Upload Image"></fig-image>
```

---

#### Video

`<fig-video>`

Video display/upload component with the same host styling model as `fig-image`. Renders a real `<video>` inside the host; by default the host shrinkwraps to the video's intrinsic size.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `src` | string | — | Video URL |
| `upload` | boolean | `false` | Show upload overlay (`fig-input-file`) |
| `label` | string | `"Upload"` | Upload button label |
| `size` | string | — | `small` \| `medium` \| `large` \| `auto` (token-sized square) |
| `aspect-ratio` | string | — | CSS aspect-ratio (e.g. `"16 / 9"`); fills container width |
| `fit` | string | `"contain"` | CSS object-fit (`"cover"`, `"contain"`, etc.) |
| `controls` | boolean | `false` | Show playback controls |
| `autoplay` | boolean | `false` | Autoplay video |
| `loop` | boolean | `false` | Loop video |
| `muted` | boolean | `false` | Mute video |
| `poster` | string | — | Poster image URL (forwarded to inner `<video>`) |

```html
<fig-video src="clip.mp4"></fig-video>
<fig-video src="clip.mp4" aspect-ratio="16 / 9" controls></fig-video>
<fig-video upload label="Upload Video" controls muted></fig-video>
```

---

#### Avatar

`<fig-avatar>` — [demo](https://rog.ie/figui3/#avatar)

Profile image or initials fallback.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `src` | string | — | Image URL |
| `name` | string | — | Name for initials fallback |
| `size` | string | — | `"large"` |

```html
<fig-avatar src="https://example.com/photo.jpg" name="John Doe"></fig-avatar>
<fig-avatar name="Jane Smith" size="large"></fig-avatar>
```

---

#### Spinner

`<fig-spinner>` — [demo](https://rog.ie/figui3/#spinner)

A loading spinner.

```html
<fig-spinner></fig-spinner>
```

---

#### Shimmer

`<fig-shimmer>` — [demo](https://rog.ie/figui3/#shimmer)

A shimmer loading placeholder.

| Attribute | Type | Default | Description |
|---|---|---|---|
| `duration` | string | `"1.5s"` | Animation cycle duration |
| `playing` | boolean | `true` | Whether animating |

```html
<fig-shimmer style="width: 200px; height: 20px;"></fig-shimmer>
```

---

#### Skeleton

`<fig-skeleton>`

Extends `<fig-shimmer>` with no additional attributes. Use for structured loading placeholders.

```html
<fig-skeleton style="width: 100%; height: 1rem; border-radius: 4px;"></fig-skeleton>
```

---

## Theming

FigUI3 adapts to light and dark themes via CSS custom properties using Figma's naming convention:

```css
--figma-color-bg
--figma-color-bg-secondary
--figma-color-bg-hover
--figma-color-text
--figma-color-text-secondary
--figma-color-border
--figma-color-icon
/* ... and more */
```

In Figma plugins, these variables are provided automatically. For standalone usage, the library includes fallback values that respond to `prefers-color-scheme`.

Force a theme manually:

```html
<body style="color-scheme: dark;">
  <!-- Forces dark theme -->
</body>
```

---

## Framework Integration

### React

```jsx
import { useRef, useEffect } from 'react';
import '@rogieking/figui3/fig.css';
import '@rogieking/figui3/fig.js';

function ColorPicker({ value, onChange }) {
  const ref = useRef(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const handleChange = (e) => onChange(e.detail);
    el.addEventListener('change', handleChange);
    return () => el.removeEventListener('change', handleChange);
  }, [onChange]);

  useEffect(() => {
    if (ref.current) ref.current.setAttribute('value', value);
  }, [value]);

  return <fig-input-color ref={ref} text="true" alpha="true" picker="figma" />;
}
```

> **Note:** Avoid setting `value` directly in JSX during re-renders — use refs to prevent infinite loops from `attributeChangedCallback`.
>
> **SSR (Next.js/Remix/Astro):** Import `fig.js` only on the client to keep server rendering safe.

### Vue

```vue
<template>
  <fig-input-color :value="color" text="true" alpha="true" @input="onInput" @change="onChange" />
</template>

<script setup>
import { ref } from 'vue';
import '@rogieking/figui3/fig.css';
import '@rogieking/figui3/fig.js';

const color = ref('#FF5733');
const onInput = (e) => { color.value = e.detail.color; };
const onChange = (e) => { console.log('Final:', e.detail); };
</script>
```

### Svelte

```svelte
<script>
  import '@rogieking/figui3/fig.css';
  import '@rogieking/figui3/fig.js';
  let color = '#FF5733';
</script>

<fig-input-color value={color} text="true" alpha="true"
  on:input={(e) => color = e.detail.color}
  on:change={(e) => console.log('Saved:', e.detail)} />
```

---

## Development

```bash
git clone https://github.com/rogie/figui3.git
cd figui3
bun install
bun dev                # Component docs at http://localhost:3000
npm run dev:playground # Playground at http://localhost:5173 (/figui3, /propkit, /sandbox)
npm run build          # Build minified dist/ (JS + CSS)
npm run build:css      # Build minified CSS only
```

### Build Output

`npm run build` produces minified files in `dist/`:

| Source | Minified | Tool |
|---|---|---|
| `fig.js` (416 KB) | `dist/fig.js` (228 KB) | Bun `--minify` |
| `fig.css` (133 KB) | `dist/fig.css` (102 KB) | lightningcss `--minify --nesting --bundle` |
| `base.css` (1.9 KB) | `dist/base.css` (1.5 KB) | lightningcss |
| `components.css` (131 KB) | `dist/components.css` (100 KB) | lightningcss |

Default imports resolve to minified `dist/` files. Unminified source is available via `@rogieking/figui3/src/*`:

```js
import "@rogieking/figui3/fig.css";      // minified (default)
import "@rogieking/figui3/src/fig.css";  // unminified source
```

### Playground

The playground is the fastest way to explore and validate component markup:

- **`/figui3`** — component examples with attribute controls
- **`/propkit`** — property panel patterns
- **`/sandbox`** — styled React sample app

---

## Browser Support

- Chrome/Edge 67+
- Firefox 63+
- Safari 10.1+

---

## License

[MIT License](LICENSE) &copy; Rogie King
