A lightweight, zero-dependency web components library for building Figma plugin and widget UIs with native look and feel.
- 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)
FigUI3 components are built to preserve native semantics where possible and add ARIA only where custom elements need extra state or naming.
- Form primitives forward accessible names and state to their native controls, including combo inputs, dropdowns, text, number, slider, checkbox, radio, switch, color, and fill inputs.
- Selection components use standard keyboard patterns: tabs use roving focus and
aria-controls, segmented controls expose a radio-group pattern with focus following arrow selection, choosers expose listbox/options, and menus support trigger state, item focus, Escape close, and disabled items. - Dialog, popup, tooltip, and toast surfaces expose names, close affordances, live-region behavior, Escape dismissal, and focus return behavior appropriate to their role.
- Media components render their visual surface inside
fig-preview; image/video semantics stay on the native media element, upload controls remain keyboard reachable, slotted image overlays stay in light DOM for framework ownership, and generated video controls render below the preview instead of as an overlay. - Display and pointer components expose useful semantics when interactive or informative: handles, swatches, color tips, layers, spinners, shimmers, and skeletons sync names, busy states, disabled states, keyboard movement, inert states, or hidden states as appropriate.
- Focus styling uses shared
--figma-focus-outline,--figma-focus-outline-offset, and--figma-focus-outline-radiustokens so visible focus treatment stays consistent across components. - Component contracts include Playwright keyboard/focus coverage plus an axe smoke suite for representative form, media, overlay, selection, and loading fixtures.
Install:
npm install @rogieking/figui3Import:
import"@rogieking/figui3/fig.css";import"@rogieking/figui3/fig.js";Opt into <fig-layer> when you need collapsible layer lists:
import"@rogieking/figui3/fig-layer.css";import"@rogieking/figui3/fig-layer.js";Opt into editor components like <fig-select> and the full Figma-style fill picker when you need them:
import"@rogieking/figui3/fig-editor.css";import"@rogieking/figui3/fig-editor.js";Opt into experimental Lab and PropsKit components after the core imports:
import"@rogieking/figui3/fig-lab.css";import"@rogieking/figui3/fig-lab.js";Or use a CDN:
<linkrel="stylesheet" href="https://unpkg.com/@rogieking/figui3@latest/dist/fig.css" /><scripttype="module" src="https://unpkg.com/@rogieking/figui3@latest/dist/fig.js"></script>Agent skills ship in .cursor/skills/ (included in the npm package):
figui3— corefig.jscomponentsfig-editor—fig-selectandfig-fill-pickerfig-lab— experimentalpropskit-*, canvas, AI, angle, wheel, reorderpropkit—/propskitproperty-row composition
Minimal example:
<fig-fielddirection="horizontal"><label>Color</label><fig-input-colorvalue="#FF5733" text="true"></fig-input-color></fig-field><fig-buttonvariant="primary">Save</fig-button>| Component | Tag | Description |
|---|---|---|
| Button | <fig-button> | Buttons with variants, toggle, select, upload |
| Dropdown | <fig-dropdown> | Native select wrapper with Figma styling |
| Select | <fig-select> | Custom listbox select (requires fig-editor.js) |
| Combo Input | <fig-combo-input> | Text input with dropdown suggestions |
| Checkbox | <fig-checkbox> | Checkbox with indeterminate state |
| Radio | <fig-radio> | Radio button |
| Switch | <fig-switch> | Toggle switch |
| Slider | <fig-slider> | Range, hue, opacity, delta, stepper |
| Propskit Slider | <propskit-slider> | Labeled slider surface |
| Input Wheel | <fig-input-wheel> | Standalone SVG tick-and-handle numeric scrubber |
| Propskit Wheel | <propskit-wheel> | Labeled input wheel with optional number field |
| Propskit Color | <propskit-color> | Full-surface labeled color control |
| Propskit Fill | <propskit-fill> | Full-surface labeled fill control |
| Propskit Gradient | <propskit-gradient> | Full-surface labeled gradient control |
| Propskit Palette | <propskit-palette> | Labeled palette selector |
| Propskit Number | <propskit-number> | Full-surface labeled number control |
| Propskit Position | <propskit-position> | Compact X/Y control |
| Propskit Joystick | <propskit-joystick> | Labeled two-axis joystick |
| Propskit Origin | <propskit-origin> | Labeled transform-origin grid |
| Propskit Easing | <propskit-easing> | Labeled cubic-bezier editor |
| Propskit Spring | <propskit-spring> | Labeled spring editor |
| Propskit Color Point | <propskit-color-point> | Collapsible color and position group |
| Propskit Point Point | <propskit-point-point> | Collapsible start and end position group |
| Propskit Point Radius | <propskit-point-radius> | Collapsible position and radius group |
| Propskit Point Radius Angle | <propskit-point-radius-angle> | Collapsible position, radius, and angle group |
| Propskit Select | <propskit-select> | Full-surface labeled select control |
| Propskit Switch | <propskit-switch> | Full-surface labeled switch control |
| Propskit Text | <propskit-text> | Full-surface labeled text control |
| Text Input | <fig-input-text> | Styled text/textarea input |
| Number Input | <fig-input-number> | Numeric input with units |
| Input Angle | <fig-input-angle> | Angle/rotation dial and text input |
| Propskit Oscillator | <propskit-oscillator> | Waveform oscillator editor |
| Swatch | <fig-swatch> | Color/gradient/image swatch |
| Color Tip | <fig-color-tip> | Compact color tip with picker |
| Color Input | <fig-input-color> | Color picker with hex/alpha |
| Input Palette | <fig-input-palette> | Editable multi-color palette |
| Gradient Input | <fig-input-gradient> | Gradient editor with stops |
| Fill Input | <fig-input-fill> | Solid, gradient, image, video fill |
| Fill Picker | <fig-fill-picker> | Full fill picker dialog |
| Tabs | <fig-tabs> / <fig-tab> | Tabbed navigation |
| Segmented Control | <fig-segmented-control> / <fig-segment> | Segmented button group |
| Chooser | <fig-chooser> / <fig-choice> | Selection list with drag scroll |
| Field | <fig-field> | Form field wrapper with layout |
| Joystick | <fig-joystick> | 2D position input |
| Origin Grid | <fig-origin-grid> | Transform-origin grid |
| Easing Curve | <fig-easing-curve> | Bezier/spring curve editor |
| 3D Rotate | <fig-3d-rotate> | 3D cube rotation control |
| Handle | <fig-handle> | Draggable handle on a surface |
| Canvas Control | <fig-canvas-control> | Point with optional radius, angle, or second point |
| Dialog | <fig-dialog> | Modal/non-modal dialog |
| Popup | <fig-popup> | Anchored floating surface |
| Toast | <fig-toast> | Toast notification |
| Tooltip | <fig-tooltip> | Hover/click tooltip |
| Separator | <fig-separator> / <fig-menu-separator> | Visual divider with an optional label |
| Menu | <fig-menu> | Triggered menu with keyboard navigation |
| Header | <fig-header> | Section header |
| Layer | <fig-layer> | Collapsible layer list item from fig-layer.js |
| Preview | <fig-preview> | Thin visual preview layer |
| Media | <fig-media> | Shared media host for image/video |
| Image | <fig-image> | Image display/upload |
| Card | <fig-card> | Media card with label, link, and selection chrome |
| Video | <fig-video> | Video display/upload with playback controls |
| Avatar | <fig-avatar> | Profile image or initials |
| Icon | <fig-icon> | Masked icon from design tokens |
| Spinner | <fig-spinner> | Loading spinner |
| Shimmer | <fig-shimmer> | Shimmer loading placeholder |
| Skeleton | <fig-skeleton> | Skeleton loading placeholder |
<fig-button> — demo
| Attribute | Type | Default | Description |
|---|---|---|---|
variant | string | "primary" | "primary", "secondary", "destructive", "destructiveSecondary", "destructiveGhost", "destructiveLink", "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 |
align | string | "center" | Content alignment: "start", "center", or "end" |
href | string | — | URL for link buttons |
target | string | — | Link target (e.g. "_blank") |
<fig-button>Primary</fig-button><fig-buttonvariant="secondary">Secondary</fig-button><fig-buttonvariant="destructive">Delete</fig-button><fig-buttonvariant="destructiveSecondary">Destructive secondary</fig-button><fig-buttonvariant="destructiveGhost">Destructive ghost</fig-button><fig-buttonvariant="destructiveLink">Destructive link</fig-button><fig-buttonfullalign="start">Start aligned</fig-button><fig-buttontype="toggle" selected="true">Toggle</fig-button><fig-buttonvariant="ghost" icon><svg><!-- icon --></svg></fig-button>type="select" and type="upload" are visual wrappers for native select/file controls. They avoid nested native buttons, show the shared focus outline on the wrapper, and open the native picker from keyboard activation where supported.
<fig-dropdown> — demo
| Attribute | Type | Default | Description |
|---|---|---|---|
value | string | — | Selected value |
type | string | "select" | "select" or "dropdown" |
label | string | — | Accessible label for the generated native <select> |
variant | string | — | "ghost" for a borderless control with secondary hover fill |
disabled | boolean | false | Disabled state |
<fig-dropdownvalue="2"><optionvalue="1">Option 1</option><optionvalue="2">Option 2</option></fig-dropdown>Keyboard activation follows the native select pattern. Enter opens the closed picker.
<fig-select> — demo
Custom listbox select with overflow chevrons, grouped options, and sticky separators. Import fig-editor.js and fig-editor.css. Prefer this over fig-dropdown for Figma-style menus.
| Attribute | Type | Default | Description |
|---|---|---|---|
value | string | — | Selected option value |
label | string | — | Closed-state / accessible label |
options | string | — | Comma, newline, or JSON options if no authored fig-select-option children |
variant | string | — | "ghost" for a borderless control with secondary hover fill |
subtle | boolean | false | Use the secondary hover/focus fill for every option |
full | boolean | false | Stretch to available width |
position | string | "bottom left" | Popup position |
disabled | boolean | false | Disabled state |
Author options in <fig-select-options>, or pass options. Add subtle to one <fig-select-option> for the secondary hover/focus fill, or to <fig-select> to apply it to every option. Use label on <fig-select-option> when the option content is rich. fig-separator with sticky pins group labels while scrolling.
<fig-selectvalue="center" label="Align"><fig-select-options><fig-select-optionvalue="left">Left</fig-select-option><fig-select-optionvalue="center">Center</fig-select-option><fig-select-optionvalue="right">Right</fig-select-option></fig-select-options></fig-select>Events:input, change, optionhover (detail is the hovered option value).
<fig-combo-input> — demo
| Attribute | Type | Default | Description |
|---|---|---|---|
options | string | — | Comma-separated suggestion list |
placeholder | string | — | Placeholder text |
value | string | — | Current value |
disabled | boolean | false | Disabled state |
<fig-combo-inputoptions="House, Apartment, Condo" placeholder="Residence type"></fig-combo-input><fig-checkbox> — demo
| 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) |
<fig-checkbox>Accept terms</fig-checkbox><fig-checkboxchecked>Selected</fig-checkbox><fig-checkboxindeterminate>Parent option</fig-checkbox><fig-radio> — demo
| Attribute | Type | Default | Description |
|---|---|---|---|
checked | boolean | false | Whether selected |
disabled | boolean | false | Disabled state |
name | string | — | Radio group name |
value | string | — | Value when selected |
<fig-radioname="size" value="small">Small</fig-radio><fig-radioname="size" value="medium" checked>Medium</fig-radio><fig-radioname="size" value="large">Large</fig-radio><fig-switch> — demo
| Attribute | Type | Default | Description |
|---|---|---|---|
checked | boolean | false | Whether on |
disabled | boolean | false | Disabled state |
name | string | — | Form field name |
value | string | — | Value when on |
<fig-switch>Enable notifications</fig-switch><fig-switchchecked>Active feature</fig-switch><fig-slider> — demo
| Attribute | Type | Default | Description |
|---|---|---|---|
type | string | "range" | "range", "hue", "opacity", "delta", "stepper" |
value | number | midpoint for type="range" | 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 | true | Show text input; set text="false" to hide |
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 | — | Use "classic" to opt into the previous slider appearance |
precision | number | — | Decimal places for output |
disabled | boolean | false | Disabled state |
Events:input (continuous), change (on release).
<fig-slidermin="0" max="100" value="50" units="%"></fig-slider><fig-slidertype="hue" value="180" text="false"></fig-slider><fig-slidertype="opacity" value="75" color="#FF5733" units="%"></fig-slider>For type="range", omitting value follows native range behavior and starts at the midpoint of min and max. Arrow keys move by step; hold Shift to move by a larger step.
<propskit-number>
Uses a plain horizontal surface with <fig-input-number>. Number attributes are forwarded to the inner input.
All PropsKit inputs expose defaultValue, isDefault, and resetToDefault(). A
propskit-group uses this shared contract to track its dirty state and reset
each nested input to its own current default.
Surface labels default to "Label" when omitted; label="" removes the visible
label. PropsKit input and change events dispatch from the outer control with
detail: { control, value, name? }, and event.target.value is the same typed
value. name is included only when a non-empty name attribute is authored.
Switch values are boolean. Number, slider, and wheel values are finite numbers
or null; structured and
serialized controls expose their exact public .value. Select optionhover
uses the same envelope. openchange and group reset keep their semantic
details.
Shared surface variables are --propskit-padding-block,
--propskit-padding-inline, --propskit-background, --propskit-border,
--propskit-color, --propskit-hover-background, --propskit-hover-border,
--propskit-hover-color, --propskit-label-inline-size, and
--propskit-input-inline-size. Override one component with the matching prefix,
such as --propskit-select-background or
--propskit-number-input-inline-size.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | "Label" | Field label text; use an empty value to hide it |
name | string | — | Optional event payload name |
default | number/string | initial value | Right-click reset target |
disabled | boolean | false | Disable interaction |
| number attrs | — | — | All <fig-input-number> attributes are forwarded |
Events:input, change — shared PropsKit envelope with value: number | null.
Right-click and choose Reset, or call resetToDefault(), to restore default (falling back to the initial value).
<propskit-numberlabel="Width" value="24" min="0" max="100" units="px"></propskit-number><propskit-color>
Uses a plain horizontal surface with a solid fig-fill-picker swatch. Clicking the surface opens the color picker. There is no hex/opacity text field.
Attributes:label, value, default, alpha, disabled
Events:input, change — shared PropsKit envelope; value is the public color string.
Right-click and choose Reset, or call resetToDefault(), to restore default or the initial color.
<propskit-colorlabel="Background" value="#0D99FF" alpha="true"></propskit-color><propskit-fill>
Uses a plain horizontal surface with a fig-fill-picker swatch, sharing chrome with propskit-color. Clicking the surface opens the fill picker for solid, gradient, image, video, webcam, and custom modes. There is no hex/opacity text field.
Attributes:label, value (fill JSON), default, mode, alpha, webcam-mode, default-video, disabled
Events:input, change — shared PropsKit envelope; value is the host's serialized fill value.
Right-click and choose Reset, or call resetToDefault(), to restore default or the initial fill. Slot mode-* children onto the host to add custom picker tabs.
<propskit-filllabel="Fill"
value='{"type":"solid","color":"#0D99FF","alpha":1}'
></propskit-fill><propskit-gradient>
Uses a plain horizontal surface with <fig-input-gradient>. Defaults to edit="picker" — click the surface to open the fill picker.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | "Label" | Field label text; use an empty value to hide it |
value | JSON string | default gradient | Canonical { "type": "gradient", "gradient": { ... } } data |
default | JSON string | initial value | Right-click and group reset target |
edit | boolean/string | "picker" | true (inline stops), false, or "picker" |
mode | string | "handle" | "handle" or "tip" stop presentation |
disabled | boolean | false | Disabled state |
Events:input, change — shared PropsKit envelope; value is the host's serialized gradient value.
Methods and state:defaultValue, isDefault, and resetToDefault(). JSON defaults use structural equality, so object key order does not affect dirty state.
Click the field to open the fill picker. With edit="true", the first stop receives focus; Arrow keys move the selected stop, Shift+Arrow moves by 5%, Tab cycles stops, and Delete/Backspace removes a stop while preserving the two-stop minimum.
<propskit-gradientlabel="Fill"
value='{"type":"gradient","gradient":{"type":"linear","angle":90,"interpolationSpace":"srgb","hueInterpolation":"shorter","stops":[{"position":0,"color":"#0D99FF","opacity":100},{"position":100,"color":"#9747FF","opacity":100}]}}'
default='{"type":"gradient","gradient":{"type":"linear","angle":90,"interpolationSpace":"srgb","hueInterpolation":"shorter","stops":[{"position":0,"color":"#0D99FF","opacity":100},{"position":100,"color":"#9747FF","opacity":100}]}}'
></propskit-gradient><propskit-palette>
Uses a plain horizontal surface with a selection-only <fig-input-palette> preview inside <fig-select>. Import fig-editor.js and fig-editor.css. Clicking anywhere on the surface opens the palette menu.
options is a JSON array of palettes. Each palette accepts color strings or { "color", "alpha" } objects. The first palette is selected when value is omitted.
Attributes:label, name, value (palette JSON), default (palette JSON), options, disabled
Events:input, change, optionhover — shared PropsKit envelope with a typed Array<{ color, alpha }> value. optionhover does not change the selection.
Methods and state:defaultValue, isDefault, and resetToDefault().
<propskit-palettelabel="Palette"
options='[["#0D99FF","#14AE5C"],[{"color":"#FFCD29","alpha":0.5},"#F24822"]]'
></propskit-palette><propskit-switch>
Uses a plain horizontal surface with <fig-switch> by default. The entire
surface toggles the switch. Set variant="segmented-control" to render an
Off/On <fig-segmented-control> instead.
Attributes:label, checked, default, disabled, name, value, variant (switch | segmented-control)
Events:input, change — shared PropsKit envelope with the public boolean value.
Right-click and choose Reset, or call resetToDefault(), to restore the default checked state.
<propskit-switchlabel="Visible" checked></propskit-switch><propskit-select>
Uses a plain horizontal surface and always composes <fig-select>. Import fig-editor.js and fig-editor.css; delayed registration upgrades the authored select. Options can come from the options attribute (same formats as fig-options: comma-separated, newline-delimited, or a JSON array), or from an authored <fig-select-options> child for rich option content.
Attributes:label, value, default, options, disabled
Events:input, change, optionhover — shared PropsKit envelope. optionhover uses the hovered option as detail.value without changing the selection.
Right-click and choose Reset, or call resetToDefault(), to restore default or the initial selection.
<propskit-selectlabel="Alignment" value="Center" options="Left,Center,Right"></propskit-select><propskit-selectlabel="Interpolation" value="srgb"><fig-select-optionsslot="panel"><fig-select-optionvalue="srgb" label="Classic — sRGB Linear"><div><h3>Classic</h3><span>sRGB Linear</span></div></fig-select-option></fig-select-options></propskit-select><propskit-text>
Uses a plain horizontal surface with <fig-input-text type="text">. The inner input defaults to multiline and autoresize, starts at one line, and grows to a maximum of four lines. Set either attribute to "false" to disable that behavior. Other text input attributes and adornment slots are forwarded to the inner control; type is not forwarded.
Attributes:label, value, default, placeholder, disabled, readonly, multiline, autoresize
Events:input, change — shared PropsKit envelope with the public string value.
Right-click and choose Reset, or call resetToDefault(), to restore default or the initial text.
<propskit-textlabel="Name" value="Layer 1" placeholder="Enter a name"></propskit-text><propskit-slider>
Uses a plain horizontal surface with <fig-slider>. Slider attributes except host-only PropsKit attributes are forwarded to the inner slider.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | "Label" | Field label text; use an empty value to hide it |
name | string | — | Optional event payload name |
default | number/string | initial value | Double-click and right-click reset target |
disabled | boolean | false | Disable interaction |
| slider attrs | — | — | All <fig-slider> attributes except host-only PropsKit attributes are forwarded |
Events:input, change — shared PropsKit envelope with value: number | null.
Double-click or right-click and choose Reset to restore default, falling back to the initial value.
Methods:resetToDefault() triggers the same reset behavior.
<propskit-sliderlabel="Opacity" min="0" max="100" value="75" units="%"></propskit-slider><fig-input-wheel>
A standalone interactive SVG tick-and-handle control for scrubbing numeric values. It is experimental and requires fig-lab.js and fig-lab.css.
| Attribute | Type | Default | Description |
|---|---|---|---|
value | number | 0 | Current numeric value |
step | number | 1 | Scrub increment |
spin | boolean/string | true | Keep ticks synchronized to value; set "false" to leave them stationary |
min | number | — | Inclusive lower bound; omit for no minimum |
max | number | — | Inclusive upper bound; omit for no maximum |
disabled | boolean | false | Disable interaction |
The value, min, max, and step properties mirror their attributes. aria-valuetext is numeric. spinTo(value) animates to a new value when spin is enabled. focus(), beginScrub(), updateScrub(), and endScrub() expose the interaction lifecycle for imperative integrations.
Events: numeric input while scrubbing and change on commit. Both bubble across shadow boundaries.
<fig-input-wheel></fig-input-wheel><fig-input-wheelvalue="50" min="0" max="100"></fig-input-wheel><fig-input-wheelvalue="1.5" step="0.25"></fig-input-wheel><propskit-wheel>
A labeled numeric scrubber that composes <fig-input-wheel> with an optional <fig-input-number>. The host provides row chrome and reset behavior; there is no fig-field or fig-slider.
Omitted label renders "Label"; label="" removes the visible label. Units are omitted by default and arbitrary values such as px pass through unchanged. Units remain wrapper and number-field behavior: time aliases receive time-focused defaults, and the effective step is applied to the child wheel without setting a child units attribute.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | "Label" | Empty removes the visible label |
name | string | — | Optional event payload name |
units | string | — | Optional arbitrary units. seconds / milliseconds normalize to s / ms |
value | number | 0 | Numeric value in units. Unbounded unless min/max are set |
min | number | — | Inclusive lower bound. Omit for no minimum |
max | number | — | Inclusive upper bound. Omit for no maximum |
step | number | 1; 0.1 (s) / 100 (ms) | Drag, keyboard, and mouse wheel increment |
precision | number | 0; 2 (s) / 0 (ms) | Displayed decimal places on the optional number field |
elastic | boolean/string | true | Stretch the composed row past the wheel edges; set "false" to disable row stretch. The handle still pulls |
spin | boolean/string | true | Keep wheel ticks synchronized to value; set "false" to update only the value and number field |
text | boolean/string | true | Include the editable fig-input-number; set "false" for only fig-input-wheel |
default | number/string | initial value | Right-click reset target |
disabled | boolean | false | Disable wheel and number |
Events:input while dragging or typing; change on commit. Both use the shared PropsKit envelope with value: number | null. Dragging moves by step on every input; hold Shift to scrub at 10× step. precision only formats the displayed number. Arrow keys on the focused wheel move by step; Shift+arrow moves by 10× step.
<propskit-wheellabel="Duration" value="1.5" default="0" units="seconds"></propskit-wheel><propskit-wheellabel="Delay" value="240" default="0" min="0" max="1000" units="ms"></propskit-wheel><propskit-wheellabel="Frames" value="12" text="false"></propskit-wheel><propskit-wheellabel="Amount" value="12" spin="false"></propskit-wheel><propskit-position>
A compact X/Y field with optional percentage units.
| Attribute | Type | Default | Description |
|---|---|---|---|
x | number | 50 | Horizontal value |
y | number | 50 | Vertical value |
default | JSON string | initial { x, y } | Right-click and group reset target |
label | string | "Label" | Field label; empty removes the visible label |
units | string | — | "percent" shows %; omit for no units |
disabled | boolean | false | Disable both number inputs |
Properties and methods:x, y, and value expose the current coordinates; defaultValue returns the normalized reset object; isDefault compares both coordinates; resetToDefault() restores both values.
Events:input and change use the shared PropsKit envelope with the public { x, y, units } value.
<propskit-positionlabel="Position"
x="50"
y="50"
units="percent"
default='{"x":50,"y":50}'
></propskit-position><propskit-joystick>
A vertical PropsKit surface with a label above a square fig-joystick and its X/Y percentage fields.
| Attribute | Type | Default | Description |
|---|---|---|---|
value | JSON string | {"x":50,"y":50} | Current percentage coordinates |
default | JSON string | initial value | Right-click and group reset target |
label | string | "Label" | Field label; empty removes the visible label |
axis-labels | string | — | One, two, or four labels forwarded to the joystick |
coordinates | string | "screen" | "screen" or "math" coordinate mode |
precision | number | 3 | X/Y field display precision |
disabled | boolean | false | Disable the joystick and both fields |
The inner joystick always uses fields="true" and aspect-ratio="1 / 1". The .value, defaultValue, and event values are typed { x, y } percentage objects. isDefault compares both axes and resetToDefault() restores the reset value.
<propskit-joysticklabel="Position"
value='{"x":35,"y":65}'
default='{"x":50,"y":50}'
axis-labels="X Y"
></propskit-joystick><propskit-origin>
A vertical PropsKit surface with a label above a square fig-origin-grid and its X/Y percentage fields. The inner grid always uses fields="true" and aspect-ratio="1 / 1".
Its .value, defaultValue, and event values are typed { x, y } objects. The value and default attributes serialize that shape as JSON. precision and drag pass through to the grid.
<propskit-originlabel="Transform origin"
value='{"x":50,"y":50}'
default='{"x":50,"y":50}'
></propskit-origin><propskit-easing>
A vertical PropsKit surface around fig-easing-curve, constrained to cubic-bezier presets. Its typed value is { x1, y1, x2, y2 }; value and default serialize that shape as JSON.
<propskit-easinglabel="Easing"
value='{"x1":0.42,"y1":0,"x2":0.58,"y2":1}'
default='{"x1":0.42,"y1":0,"x2":0.58,"y2":1}'
></propskit-easing><propskit-spring>
A vertical PropsKit surface around fig-easing-curve, constrained to spring presets. Its typed value is { stiffness, damping, mass }; value and default serialize that shape as JSON.
<propskit-springlabel="Spring"
value='{"stiffness":200,"damping":15,"mass":1}'
default='{"stiffness":200,"damping":15,"mass":1}'
></propskit-spring>Both curve controls forward precision and edit, support disabled and variant="minimal", expose isDefault, and implement resetToDefault(). Their input and change events use the shared PropsKit envelope.
<propskit-color-point>
A compact <fig-group> wrapper that combines <propskit-color> and <propskit-position>. The group is collapsible and open by default.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | — | Passed to the internal fig-group as name |
value | JSON string | {"x":50,"y":50,"color":"#D9D9D9"} | Current color-point value |
collapsible | boolean | true | Passed to the internal fig-group |
open | boolean | true | Passed to the internal fig-group |
disabled | boolean | false | Disable both internal controls |
The internal group always has compact. value uses the same { x, y, color } shape as <fig-canvas-control type="color">.
Events:input and change use the shared PropsKit envelope with the public color-point value. openchange mirrors the internal group's expanded state.
<propskit-color-pointlabel="Light"
value='{"x":50,"y":50,"color":"#FF00BF"}'
></propskit-color-point><propskit-point-radius>
A compact <fig-group> wrapper that combines <propskit-position> and <propskit-number> for point-radius values. The group is collapsible and open by default.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | — | Passed to the internal fig-group as name |
value | JSON string | {"x":50,"y":50,"radius":0} | Current point-radius value |
collapsible | boolean | true | Passed to the internal fig-group |
open | boolean | true | Passed to the internal fig-group |
units | string | — | "percent" passes percentage units to position and radius; omit for no units |
disabled | boolean | false | Disable both internal controls |
The internal group always has compact. value uses the same { x, y, radius } shape as <fig-canvas-control type="point-radius">. Numeric radius values use pixels; percentage strings preserve %.
Events:input and change use the shared PropsKit envelope with the public point-radius value. openchange mirrors the internal group's expanded state.
<propskit-point-radiuslabel="Blur"
units="percent"
value='{"x":50,"y":50,"radius":"25%"}'
></propskit-point-radius><propskit-point-radius-angle>
A compact <fig-group> wrapper combining <propskit-position> with radius and angle <propskit-number> controls. The group is collapsible and open by default.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | — | Passed to the internal fig-group as name |
value | JSON string | {"x":50,"y":50,"radius":0,"angle":0} | Current point-radius-angle value |
collapsible | boolean | true | Passed to the internal fig-group |
open | boolean | true | Passed to the internal fig-group |
units | string | — | "percent" passes percentage units to position and radius; omit for no units |
disabled | boolean | false | Disable every internal control |
The internal group always has compact. value uses the same { x, y, radius, angle } shape as <fig-canvas-control type="point-radius-angle">. Numeric radius values use pixels; percentage strings preserve %. Angles are degrees.
Events:input and change use the shared PropsKit envelope with the public point-radius-angle value. openchange mirrors the internal group's expanded state.
<propskit-point-radius-anglelabel="Gradient"
units="percent"
value='{"x":50,"y":50,"radius":"25%","angle":45}'
></propskit-point-radius-angle><propskit-point-point>
A compact <fig-group> wrapper combining start and end <propskit-position> controls. The group is collapsible and open by default.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | — | Passed to the internal fig-group as name |
value | JSON string | {"x":50,"y":50,"x2":75,"y2":75} | Current point-point value |
collapsible | boolean | true | Passed to the internal fig-group |
open | boolean | true | Passed to the internal fig-group |
units | string | — | "percent" passes percentage units to both positions; omit for no units |
disabled | boolean | false | Disable both position controls |
The internal group always has compact. value uses the same { x, y, x2, y2 } shape as <fig-canvas-control type="point-point">.
Events:input and change use the shared PropsKit envelope with the public point-point value. openchange mirrors the internal group's expanded state.
<propskit-point-pointlabel="Gradient"
units="percent"
value='{"x":25,"y":25,"x2":75,"y2":75}'
></propskit-point-point><fig-input-text> — demo
| 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 |
<fig-input-textvalue="Hello" placeholder="Enter text..."></fig-input-text><fig-input-textmultilineplaceholder="Enter description..."></fig-input-text><fig-input-number> — demo
| 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 |
precision | number | — | Fixed displayed decimal places; omitted values use up to two places |
steppers | boolean | false | Show spin buttons |
disabled | boolean | false | Disabled state |
<fig-input-numbervalue="100" units="px"></fig-input-number><fig-input-numbervalue="50" units="%" min="0" max="100"></fig-input-number><fig-input-angle> — demo
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).
<fig-input-anglevalue="90" text="true"></fig-input-angle><fig-input-angletext="true" units="rad" value="3.14159"></fig-input-angle><fig-input-angletext="true" rotationsvalue="1080"></fig-input-angle><propskit-oscillator>
Waveform oscillator input with composable wave functions, live SVG waveform preview, draggable parameter handles, and direct numeric controls.
| Attribute | Type | Default | Description |
|---|---|---|---|
value | JSON string | — | {"waves":[{"type":"sine","frequency":1,"amplitude":1,"phase":0,"offset":0}]} |
name | string | — | Optional event payload name |
default | JSON string | initial value | Right-click reset target |
precision | number | 2 | Decimal places |
aspect-ratio | string | "2 / 1" | Editor aspect ratio |
edit | boolean | true | Show editor and number fields; set to "false" for preview only |
disabled | boolean | false | Disable interaction |
Supported type values: "sine", "square", "sawtooth", "triangle".
Properties:value returns a normalized JSON string. data returns { waves }. Single-wave JSON values are still accepted and normalized into waves.
Right-click and choose Reset, or call resetToDefault(), to restore the oscillator value.
Events:
| Event | Detail |
|---|---|
input | Shared PropsKit { control, value, name? } envelope while dragging or editing |
change | Shared PropsKit { control, value, name? } envelope on release or committed edit |
<propskit-oscillatorvalue='{"waves":[{"type":"sine","frequency":1,"amplitude":1,"phase":0,"offset":0},{"type":"triangle","frequency":2,"amplitude":0.5,"phase":0,"offset":0}]}'
></propskit-oscillator><fig-swatch> — demo
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) |
<fig-swatchbackground="#FF5733"></fig-swatch><fig-swatchbackground="linear-gradient(90deg, #FF0000, #0000FF)" size="large"></fig-swatch><fig-color-tip> — demo
A compact solid-color swatch. Uses <fig-fill-picker> when the optional picker is registered, otherwise falls back to the native color input.
| 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, alpha, opacity } — while editing |
change | { color, alpha, opacity } — on commit |
add | — (when control="add" is clicked) |
remove | — (when control="remove" is clicked) |
<fig-color-tipvalue="#FF5733"></fig-color-tip><fig-color-tipvalue="#00AAFF" alpha="true"></fig-color-tip><fig-input-color> — demo
| Attribute | Type | Default | Description |
|---|---|---|---|
value | string | — | Hex color (e.g. "#FF5733" or "#FF573380") |
text | boolean | false | Show hex text input |
alpha | boolean | true | Show alpha slider; set alpha="false" to hide opacity controls |
mode | string | — | Color mode ("hex", "rgb", "hsl") |
picker-* | string | — | Forwarded to <fig-fill-picker> when the optional picker is registered |
disabled | boolean | false | Disabled state |
Events:
| Event | Detail |
|---|---|
input | { value, hex, rgba, color, alpha, opacity } |
change | { value, hex, rgba, color, alpha, opacity } |
value, hex, and rgba retain their legacy values. The additive aliases use opaque #RRGGBB for color, 0–1 for alpha, and 0–100 for opacity.
<fig-input-colorvalue="#FF5733" text="true"></fig-input-color>When fig-editor.js is imported, swatch activation opens <fig-fill-picker>. Without it, the native color input is used.
<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) |
open | boolean | false | Expanded rows with text inputs per color |
fixed | boolean | false | Lock palette length — no add or remove (fixed or fixed="true") |
Events:
| Event | Detail |
|---|---|
input | Full color array (during editing) |
change | Full color array (on commit or add) |
<fig-input-palettevalue='["#FF0000","#00FF00","#0000FF"]'></fig-input-palette><fig-input-palettevalue='[{"color":"#FF0000","alpha":0.5},{"color":"#00FF00","alpha":1}]' open></fig-input-palette>The collapsed palette is a single tab stop. Enter or Space expands it, and focus styling uses the shared focus outline tokens on the visible swatch row.
<fig-input-gradient>
A gradient editor with draggable stops. With edit="picker" and the optional picker registered, it opens <fig-fill-picker> locked to gradient mode; otherwise it falls back to inline stop editing.
| Attribute | Type | Default | Description |
|---|---|---|---|
value | string | — | JSON gradient fill data |
edit | boolean/string | true | true, false, or "picker" |
mode | string | "handle" | "handle" renders color stop handles without tips; "tip" renders persistent color tips |
disabled | boolean | false | Disabled state |
picker-* | string | — | Passthrough picker attributes |
Supported interpolation spaces: srgb, srgb-linear, display-p3, oklab, oklch (with hueInterpolation: shorter, longer, increasing, decreasing).
Events:
| Event | Detail |
|---|---|
input | { type, gradient } |
change | { type, gradient } |
<fig-input-gradientvalue='{"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><fig-input-fill> — demo
A comprehensive fill input supporting solid, gradient, image, video, and webcam fills. Without the optional picker, it renders a passive preview.
| Attribute | Type | Default | Description |
|---|---|---|---|
value | string | — | JSON fill data |
disabled | boolean | false | Disabled state |
mode | string | — | Lock to a fill mode |
alpha | boolean | true | Show alpha controls |
webcam-mode | string | live | Forwarded to the picker: live or snapshot |
default-video | string | — | Forwarded sample clip URL when Video has no file |
picker-* | string | — | Forwarded to <fig-fill-picker> when the optional picker is registered |
Add aria-label to name the generated picker, hex field, and opacity field as one fill control group.
Solid alpha is 0–1 (canonical). opacity 0–100 is also emitted for compatibility. Gradient values include css. Webcam JSON is { type, webcam } — never a live MediaStream. Read webcamStream or listen for webcamstream. Video swatches use video.poster, not the mp4 URL.
Events:
| Event | Detail |
|---|---|
input | { type, color?, gradient?, image?, video?, webcam?, css } |
change | { type, color?, gradient?, image?, video?, webcam?, css } |
webcamstream | { stream, deviceId } |
<fig-input-fillvalue='{"type":"solid","color":"#FF5733","opacity":100}'></fig-input-fill><fig-fill-picker> — demo
Optional full fill picker dialog supporting solid, gradient, image, video, and webcam. Import fig-editor.js and fig-editor.css to register and style it.
| 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" |
webcam-mode | string | live | live keeps the camera after close; Capture always writes an image still |
default-video | string | — | Sample clip URL when Video is selected with no file |
Webcam JSON is { type: "webcam", webcam: { live, snapshot, deviceId, scaleMode, scale, opacity } }. The live MediaStream is webcamStream / webcamstream, not value. Closing the dialog does not stop a live camera; call releaseWebcam() or disconnect the element. Capture in live mode only updates webcam.snapshot. Video JSON includes poster; the swatch paints that, never url(file.mp4).
Events:
| Event | Detail |
|---|---|
input | { type, gradient?, color?, image?, video?, webcam?, css } |
change | { type, gradient?, color?, image?, video?, webcam?, css } |
webcamstream | { stream, deviceId } |
<fig-fill-pickervalue='{"type":"solid","color":"#FF5733"}'><fig-swatch></fig-swatch></fig-fill-picker><fig-tabs> / <fig-tab> — demo
| 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.
Tabs use role="tablist" / role="tab" and roving focus. Use content="#panel-id" on each <fig-tab> to associate generated tab panels. Focus-visible tabs use the shared focus outline tokens.
<fig-tabsvalue="tab1"><fig-tabvalue="tab1">General</fig-tab><fig-tabvalue="tab2">Advanced</fig-tab></fig-tabs><fig-segmented-control> / <fig-segment> — demo
| 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.
Segmented controls expose a radio-group pattern. Arrow keys, Home, and End move selection between enabled segments and move focus to the selected segment.
<fig-segmented-control><fig-segmentvalue="left" selected="true">Left</fig-segment><fig-segmentvalue="center">Center</fig-segment><fig-segmentvalue="right">Right</fig-segment></fig-segmented-control><fig-chooser> / <fig-choice>
A selection list controller. <fig-choice> elements are selectable options within a <fig-chooser>.
When app code rebuilds a chooser by setting fig-chooser.innerHTML, the chooser restores its overflow buttons automatically. Choices remain direct light-DOM children (React-safe).
fig-chooser attributes:
| Attribute | Type | Default | Description |
|---|---|---|---|
value | string | — | Selected choice value. Omit to select the first choice; value="" means none. |
choice-element | string | "fig-choice" | CSS selector for child choices |
layout | string | "vertical" | "vertical", "horizontal", "grid" |
columns | number | 2 | Grid column count when layout="grid" |
disabled | boolean | false | Disabled state |
drag | boolean | false | Enable drag-to-scroll |
overflow | string | — | Overflow behavior |
loop | boolean | false | Loop keyboard navigation |
auto-scroll | boolean | true | Automatically center the selection after selection, layout, resize, or media changes. Set to "false" to disable. |
scroll-behavior | string | "smooth" | Selection and overflow-button scrolling behavior: "smooth" or "auto". The CSS scroll-behavior property can also override it. |
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.
Selection follows the native <select> pattern: set chooser.value with a choice value, or set chooser.selectedChoice with a choice element. Neither programmatic selection nor scrolling emits events.
When auto-scroll="false", reveal the current selection explicitly with chooser.scrollSelectionIntoView(options?). It accepts native-style ScrollIntoViewOptions (behavior, block, and inline) and centers by default.
<fig-chooservalue="opt1"><fig-choicevalue="opt1">Option 1</fig-choice><fig-choicevalue="opt2">Option 2</fig-choice><fig-choicevalue="opt3">Option 3</fig-choice></fig-chooser><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 |
axis-labels | string | — | Comma- or space-delimited labels. 1 value: top. 2 values: x y. 4 values: left right top bottom |
Events:
| Event | Detail |
|---|---|
input | { x, y, value } — while dragging |
change | { x, y, value } — on release |
<fig-joystickvalue="50% 50%" fields="true" precision="2"></fig-joystick>Keyboard focus lands on the internal handle. Arrow keys move the handle and keep focus on it during interaction.
<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 |
<fig-origin-gridvalue="50% 50%" drag="true" fields="true"></fig-origin-grid>The internal handle uses the shared focus outline and supports Arrow, Shift+Arrow, Home, and End keyboard movement.
<fig-easing-curve>
An interactive bezier or spring easing curve editor with a preset dropdown and manual value input.
| Attribute | Type | Default | Description |
|---|---|---|---|
value | string | — | Bezier: "0.42, 0, 0.58, 1" or Spring: "spring(200, 15, 1)" |
mode | string | — | Optional "bezier" or "spring" constraint; filters presets and rejects the other value type |
precision | number | 2 | Decimal places |
aspect-ratio | string | — | Editor aspect ratio |
edit | boolean | true | Show the editor and custom bezier/spring options; set to "false" for preset-only |
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 |
<fig-easing-curvevalue="0.42, 0, 0.58, 1"></fig-easing-curve><fig-easing-curvevalue="spring(200, 15, 1)" edit="false"></fig-easing-curve>Editable bezier and spring handles are keyboard operable. Bezier handles keep tab order aligned with the visual handle order.
<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 |
<fig-3d-rotatevalue="rotateX(20deg) rotateY(-35deg) rotateZ(0deg)" fields="true"></fig-3d-rotate><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 |
precision | number | 2 | Decimal places for percentage values and pixel coordinates; use 0 for whole numbers |
type | string | — | "color" for a color handle with direct picker activation |
tip | string | — | "color", "add", or "remove" to show a persistent fig-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 |
input | { color, alpha, opacity } — while editing a type="color" handle |
change | { color, alpha, opacity } — when committing a type="color" handle |
add | — (when tip="add") |
remove | — (when tip="remove") |
hitareadown | { originalEvent } — when hit-area-mode="delegate" and the hit area is clicked |
<divstyle="position: relative; width: 200px; height: 200px; background: #eee;"><fig-handledrag="true" value="50% 50%"></fig-handle></div>When drag="true", focused handles support Arrow key movement, Home/End jumps, and a tokenized focus outline with a 1px offset.
<fig-canvas-control> — demo
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-handles |
snapping | string | "false" | "false", "true", "modifier" — applies to all handles |
precision | number | 2 | Decimal places for positions, radius, angle, and internal handle dragging |
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 type="color", color edits add { color, alpha, opacity } to the positional value object.
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).
<divstyle="position: relative; width: 200px; height: 200px; background: #eee;"><fig-canvas-controltype="point-point"
name="Start, End"
value='{"x":25,"y":25,"x2":75,"y2":75}'
snapping="modifier"
></fig-canvas-control></div><fig-field> — demo
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 |
<fig-fielddirection="horizontal" columns="thirds"><label>Opacity</label><fig-slidervalue="50" units="%"></fig-slider></fig-field><fig-dialog> — demo
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. |
<dialogis="fig-dialog" id="myDialog" modaldraghandle="fig-header" position="center center"><fig-header>Dialog Title</fig-header><fig-contentpadding><p>Content here.</p></fig-content></dialog>Use padding on fig-content for prose-only content. Leave it off when
rendering fig-field or fig-group children, which provide their own gutters.
Dialog close paths restore focus to the element that opened the dialog.
<fig-popup> — demo
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 |
title | string | — | Auto-generated header (same as fig-dialog) |
<dialogis="fig-popup" anchor="#my-button" position="center right" variant="popover"><fig-header><h3>Popup</h3></fig-header></dialog>Popups restore focus on close. Escape dismissal is scoped so nested menu and overlay behavior can keep its own keyboard handling.
<fig-toast> — demo
A toast notification. Uses is="fig-toast" on a native <dialog>.
Defaults to role="status", aria-live="polite", and aria-atomic="true". Use live="assertive" or theme="danger" for assertive announcements.
| 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" |
live | string | — | "assertive" for urgent announcements |
icon | string | — | Optional fig-icon name prepended when set; always rendered at full size |
dismiss | boolean | false | Appends a ghost close button that hides the toast |
<dialogis="fig-toast" id="myToast" theme="brand" icon="warning" dismiss="true" duration="3000">
Settings saved!
</dialog><fig-buttononclick="document.getElementById('myToast').showToast()">Show</fig-button><fig-tooltip> — demo
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 |
<fig-tooltiptext="Helpful info"><fig-button>Hover me</fig-button></fig-tooltip>Escape dismisses an open tooltip and returns focus to its trigger.
<fig-separator> — demo
A visual divider between content groups. The optional label attribute adds a group label and accessible name. Add borderless to hide the separator line. <fig-menu-separator> is a backwards-compatible alias.
<fig-separator></fig-separator><fig-separatorlabel="More"></fig-separator><fig-separatorlabel="First group" borderless></fig-separator><fig-menu-separator></fig-menu-separator><fig-menu> / <fig-menu-item> / <fig-separator> / <fig-menu-separator> — demo
Triggered menu with native keyboard patterns. The trigger gets aria-haspopup="menu", aria-expanded, and aria-controls; menu items use role="menuitem" and disabled items are skipped by keyboard navigation.
Items stay in the menu's light DOM and project into the popup through slots (same pattern as fig-select / fig-select-options). The trigger is assigned slot="trigger" automatically; items use the default slot. React can add or remove fig-menu-item children without removeChild errors.
fig-menu-item also works as a list row outside fig-menu — typically in <dialog is="fig-popup"> with <fig-content padding="none">, sticky <fig-separator>s, and a nested <fig-menu> for row actions. Item color inherits from the parent surface.
| Attribute | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Open state |
disabled | boolean | false | Disable trigger/menu |
position | string | "bottom left" | Popup placement |
offset | string | — | Popup offset |
closedby | string | — | Popup close behavior |
trigger | string | — | Use "contextmenu" to open on right-click |
Keyboard: Arrow keys move between enabled items, Home/End jump to edges, Enter/Space selects, Escape closes and returns focus to the trigger.
Methods:showAt(x, y) opens the menu at viewport coordinates, useful for custom context menu behavior.
<fig-menuposition="bottom left"><fig-buttonfig-menu-trigger>Actions</fig-button><fig-menu-itemvalue="copy">Copy</fig-menu-item><fig-menu-itemvalue="paste">Paste</fig-menu-item><fig-separator></fig-separator><fig-menu-itemvalue="delete" disabled>Delete</fig-menu-item><fig-separatorlabel="More"></fig-separator><fig-menu-itemvalue="settings">Settings</fig-menu-item></fig-menu>Popup list (no wrapping fig-menu):
<dialogis="fig-popup" title="Version history" anchor="#versions" position="bottom left"><fig-contentpadding="none"><fig-separatorstickylabel="Today"></fig-separator><fig-menu-itemvalue="v9" subtle>
Version 9
<fig-menuposition="bottom right"><fig-buttonfig-menu-triggervariant="ghost" iconaria-label="More"><fig-iconname="more"></fig-icon></fig-button><fig-menu-itemvalue="restore">Restore this version</fig-menu-item></fig-menu></fig-menu-item></fig-content></dialog>fig-separator and fig-menu-separator accept optional label — renders the rule, then secondary group text underneath.
<fig-header> — demo
A section header component.
<fig-header>Section Title</fig-header><fig-layer> — demo
A collapsible layer list item with expand/collapse and visibility toggling. Supports nesting and exposes role="treeitem", aria-expanded, aria-hidden, aria-disabled, and a keyboard-toggleable chevron button.
Import fig-layer.js and fig-layer.css to register and style it.
| 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 }).
<fig-layeropen="true"><divclass="fig-layer-row"><spanclass="fig-layer-icon"></span><spanclass="fig-layer-name">Group 1</span></div><fig-layer><divclass="fig-layer-row"><spanclass="fig-layer-icon"></span><spanclass="fig-layer-name">Child 1</span></div></fig-layer></fig-layer><fig-preview>
A thin styled layer for arbitrary visual content. Use it for generated previews, canvas output, SVG, images, or other custom rendered surfaces when you do not need media upload behavior.
| Attribute | Type | Default | Description |
|---|---|---|---|
aspect-ratio | string | "4/3" | CSS aspect ratio such as "1/1" or "16/9" |
fit | string | contain | Object fit for direct media children |
full | boolean | false | Stretch to the available width |
checkerboard | boolean | false | Show checkerboard behind transparent content |
Set --fig-preview-background to customize the surface color, including transparent to remove it.
<fig-previewfullstyle="height: 96px"><canvaswidth="320" height="180"></canvas></fig-preview><fig-previewcheckerboard><imgsrc="photo.png" alt="Preview"></fig-preview><fig-media>
Unified media component that supports image/video modes and shared sizing/upload behavior. The media surface is rendered inside a fig-preview; generated video controls render below that preview rather than as an overlay. 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) |
loading-indicator | boolean | true | Set to "false" to disable the delayed loading spinner for generated images |
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 |
caption | string | — | Caption text rendered below the media preview |
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 |
aria-label | string | — | Accessible label forwarded to generated videos |
Use meaningful alt text for informative images. Use alt="" only when the image is decorative or already described by nearby text.
<fig-mediatype="image" src="photo.jpg" alt="Selected image"></fig-media><fig-mediatype="image" src="photo.jpg" alt="Cover image" aspect-ratio="16 / 9" fit="cover"></fig-media><fig-mediatype="video" src="clip.mp4" aria-label="Product demo video" caption="Looping product demo" controlsmuted></fig-media><fig-mediatype="image" src="photo.jpg" alt="Selected image"><figcaption>Selected image from the current document.</figcaption></fig-media>Use the caption attribute for a plain-text caption, or a direct <figcaption> child for authored caption content.
Native load lifecycle events are re-emitted from the fig-media host as bubbling, composed CustomEvents. Image mode emits load and error. Video mode forwards loadstart, progress, suspend, abort, error, emptied, stalled, loadedmetadata, loadeddata, canplay, canplaythrough, playing, waiting, seeking, seeked, durationchange, timeupdate, ratechange, resize, and volumechange; existing play, pause, and ended events remain available. Event detail includes src, media, and originalEvent, plus video timing and readiness state.
For generated images, a spinner appears when loading exceeds 150ms and is removed on load or error. The host reflects aria-busy="true" while pending. Authored media or preview content does not receive an automatic spinner.
<fig-image> — demo
An image display component with optional upload, aspect ratio, and object-fit control. Renders a real <img> inside a fig-preview.
| 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) |
loading-indicator | boolean | true | Set to "false" to disable the delayed loading spinner |
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 |
caption | string | — | Caption text rendered below the image preview |
Use meaningful alt text for informative images. Use alt="" for decorative previews, thumbnails with visible labels, or upload placeholders.
The generated image's native load and error events are re-emitted from fig-image as bubbling, composed CustomEvents with src, media, and originalEvent in event.detail.
<fig-imagesrc="photo.jpg" alt="Selected image"></fig-image><fig-imagesrc="photo.jpg" alt="Cover image" aspect-ratio="16 / 9" fit="cover" caption="Cover image"></fig-image><fig-imageuploadlabel="Upload Image" alt=""></fig-image><fig-imagesrc="photo.jpg" alt="Selected image"><figcaption>Selected image from the current document.</figcaption><fig-input-fileslot="overlay" variant="overlay" label="Change image"></fig-input-file></fig-image>Use slot="overlay" for custom overlay controls. Slotted overlays stay as direct light-DOM children so frameworks like React keep ownership of their nodes, while CSS places them over the preview and keeps them visible on hover, focus, and active interaction.
<fig-card> — demo
A media card with a truncated label and attribute-only selection chrome. With src, it composes a generated fig-image. The label and sublabel attributes render inside a generated fig-footer.
| Attribute | Type | Default | Description |
|---|---|---|---|
src | string | — | Image URL forwarded to generated fig-image |
alt | string | "" | Alt text forwarded to generated fig-image |
label | string | — | Card title (preferred over text) |
text | string | — | Alias for label |
sublabel | string | — | Secondary one-line text under the label |
selected | boolean | false | Selected chrome only (no click-toggle) |
disabled | boolean | false | Dim + non-interactive |
full | boolean | false | Stretch to the available width (cards are already width: 100% by default) |
size | string | default | Set to "large" for --spacer-2 card padding and --spacer-1 label spacing |
aspect-ratio | string | "1/1" | Forwarded to generated fig-image |
fit | string | "contain" | Forwarded to generated fig-image |
label-line-clamp | string | "1" | "1" or "2" line clamp for the label |
<fig-cardsrc="photo.jpg" label="Autumn field"></fig-card><fig-cardsrc="photo.jpg" label="Large card" size="large"></fig-card><fig-cardsrc="photo.jpg" label="Shader pill" sublabel="Generative tools/effects" selected></fig-card><fig-cardsrc="photo.jpg" label="Wide card" aspect-ratio="16/9" full></fig-card><fig-cardlabel="Custom preview"><fig-preview>...</fig-preview></fig-card><fig-card><fig-preview>...</fig-preview><fig-footer><label>Authored footer</label></fig-footer></fig-card>When src is omitted, authored direct children such as fig-image, fig-media, or fig-preview remain direct children of the card. An authored fig-footer is preserved; when no label, text, or sublabel attributes are present, the card does not generate another footer.
Place cards in a CSS grid for multi-column layouts — there is no built-in columns attribute. Prefer full in fluid layouts for consistency with other FigUI controls.
<fig-video>
Video display/upload component with the same preview styling model as fig-image. Renders a real <video> inside a fig-preview; generated playback controls tack onto the bottom.
| 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>) |
aria-label | string | — | Accessible label forwarded to the generated <video> |
caption | string | — | Caption text rendered below the video preview |
Prefer controls for videos that play motion. Use native <track> text tracks when the video includes speech or essential audio.
<fig-videosrc="clip.mp4" aria-label="Product demo video" caption="Product demo" controls></fig-video><fig-videosrc="clip.mp4" aria-label="Product demo video" aspect-ratio="16 / 9" controls></fig-video><fig-videouploadlabel="Upload Video" aria-label="Uploaded video preview" controlsmuted><figcaption>Uploaded video preview.</figcaption></fig-video><fig-avatar> — demo
Profile image or initials fallback.
| Attribute | Type | Default | Description |
|---|---|---|---|
src | string | — | Image URL |
name | string | — | Name for initials fallback |
size | string | — | "large" |
<fig-avatarsrc="https://example.com/photo.jpg" name="John Doe"></fig-avatar><fig-avatarname="Jane Smith" size="large"></fig-avatar><fig-icon> — demo
Masked icon using --icon-16-* and --icon-24-* design tokens (SVG artboard size). Display size is controlled separately via the size attribute.
| Attribute | Type | Default | Description |
|---|---|---|---|
name | string | — | Icon name: add, send, close, chevron, arrow-left, etc. |
size | string | medium | medium (1.5rem) or small (1rem) |
color | string | — | Alias (primary, secondary, tertiary, disabled, brand, component, danger, success, warning, selected, hover, pressed, onbrand, …); CSS variables and explicit CSS colors also accepted |
<fig-iconname="close"></fig-icon><fig-iconname="send" color="brand"></fig-icon><fig-iconname="chevron" size="small"></fig-icon>Legacy: <span class="fig-mask-icon" style="--icon: var(--icon-24-add)"></span> still works.
<fig-spinner> — demo
A loading spinner.
Defaults to role="status" and aria-label="Loading"; override the label when the loading target needs a more specific name.
<fig-spinner></fig-spinner><fig-shimmer> — demo
A shimmer loading placeholder.
| Attribute | Type | Default | Description |
|---|---|---|---|
duration | string | "1.5s" | Animation cycle duration |
playing | boolean | true | Whether animating |
Shimmer and skeleton placeholders are hidden from assistive tech unless you add aria-label or aria-labelledby; named placeholders expose role="status" and aria-busy.
<fig-shimmerstyle="width: 200px; height: 20px;"></fig-shimmer><fig-skeleton>
Extends <fig-shimmer> for structured loading placeholders. Skeletons are inert by default, so any placeholder inputs or buttons inside them are removed from tab focus while loading.
<fig-skeletonstyle="width: 100%; height: 1rem; border-radius: 4px;"></fig-skeleton>FigUI3 adapts to light and dark themes via CSS custom properties using Figma's naming convention:
--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:
<bodystyle="color-scheme: dark;"><!-- Forces dark theme --></body>Focus indicators are controlled with shared tokens:
--figma-focus-outline--figma-focus-outline-offset--figma-focus-outline-radius--figma-focus-outline-radius defaults to inherit, so focused controls can inherit their component radius unless a component overrides it for a specific shape.
import{useRef,useEffect}from'react';import'@rogieking/figui3/fig.css';import'@rogieking/figui3/fig.js';functionColorPicker({ value, onChange }){constref=useRef(null);useEffect(()=>{constel=ref.current;if(!el)return;consthandleChange=(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-colorref={ref}text="true"alpha="true"/>;}Note: Avoid setting
valuedirectly in JSX during re-renders — use refs to prevent infinite loops fromattributeChangedCallback.SSR (Next.js/Remix/Astro): Import
fig.jsonly on the client to keep server rendering safe.
<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';constcolor=ref('#FF5733');constonInput= (e) => { color.value=e.detail.color; };constonChange= (e) => { console.log('Final:', e.detail); };</script><script>import'@rogieking/figui3/fig.css';import'@rogieking/figui3/fig.js';let color ='#FF5733';</script>
<fig-input-colorvalue={color} text="true"alpha="true"on:input={(e) =>color=e.detail.color}
on:change={(e) =>console.log('Saved:', e.detail)} />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 onlynpm run build produces minified files in dist/:
| Source | Minified | Tool |
|---|---|---|
fig.js (413 KB) | dist/fig.js (223 KB) | Bun --minify |
fig-editor.js (67 KB) | dist/fig-editor.js (37 KB) | Bun --minify |
fig.css | dist/fig.css (102 KB) | lightningcss --minify --nesting --bundle |
components.css (130 KB) | dist/components.css (100 KB) | lightningcss |
fig-editor.css (6 KB) | dist/fig-editor.css (4 KB) | lightningcss |
base.css (2 KB) | dist/base.css (2 KB) | lightningcss |
Default imports resolve to minified dist/ files. Unminified source is available via @rogieking/figui3/src/*:
import"@rogieking/figui3/fig.css";// minified (default)import"@rogieking/figui3/src/fig.css";// unminified sourceThe 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
- Chrome/Edge 67+
- Firefox 63+
- Safari 10.1+
Split license. See LICENSE.
- MIT:
fig.js,fig-layer.js, core CSS,polyfills/ - PolyForm Shield 1.0.0 (not OSI open source):
fig-editor.js,fig-lab.js, and their CSS
© Rogie King