Skip to content

Latest commit

History

629 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

FigUI3

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

npm versionLicense

Live Playground & Demos

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)

Accessibility Coverage

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-radius tokens 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.

Quick Start

Install:

npm install @rogieking/figui3

Import:

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 — core fig.js components
  • fig-editorfig-select and fig-fill-picker
  • fig-lab — experimental propskit-*, canvas, AI, angle, wheel, reorder
  • propkit/propskit property-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>

Components

ComponentTagDescription
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

Form Controls

Button

<fig-button>demo

AttributeTypeDefaultDescription
variantstring"primary""primary", "secondary", "destructive", "destructiveSecondary", "destructiveGhost", "destructiveLink", "ghost", "link"
typestring"button""button", "toggle", "submit", "select", "upload"
sizestring"large", "compact"
selectedbooleanfalseSelected state (toggle type)
disabledbooleanfalseDisabled state
iconbooleanfalseIcon-only styling
alignstring"center"Content alignment: "start", "center", or "end"
hrefstringURL for link buttons
targetstringLink 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.


Dropdown

<fig-dropdown>demo

AttributeTypeDefaultDescription
valuestringSelected value
typestring"select""select" or "dropdown"
labelstringAccessible label for the generated native <select>
variantstring"ghost" for a borderless control with secondary hover fill
disabledbooleanfalseDisabled 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.


Select

<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.

AttributeTypeDefaultDescription
valuestringSelected option value
labelstringClosed-state / accessible label
optionsstringComma, newline, or JSON options if no authored fig-select-option children
variantstring"ghost" for a borderless control with secondary hover fill
subtlebooleanfalseUse the secondary hover/focus fill for every option
fullbooleanfalseStretch to available width
positionstring"bottom left"Popup position
disabledbooleanfalseDisabled 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).


Combo Input

<fig-combo-input>demo

AttributeTypeDefaultDescription
optionsstringComma-separated suggestion list
placeholderstringPlaceholder text
valuestringCurrent value
disabledbooleanfalseDisabled state
<fig-combo-inputoptions="House, Apartment, Condo" placeholder="Residence type"></fig-combo-input>

Checkbox

<fig-checkbox>demo

AttributeTypeDefaultDescription
checkedbooleanfalseWhether checked
indeterminatebooleanfalseIndeterminate state
disabledbooleanfalseDisabled state
namestringForm field name
valuestringValue when checked
labelstringLabel text (alternative to slotted content)
<fig-checkbox>Accept terms</fig-checkbox><fig-checkboxchecked>Selected</fig-checkbox><fig-checkboxindeterminate>Parent option</fig-checkbox>

Radio

<fig-radio>demo

AttributeTypeDefaultDescription
checkedbooleanfalseWhether selected
disabledbooleanfalseDisabled state
namestringRadio group name
valuestringValue 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>

Switch

<fig-switch>demo

AttributeTypeDefaultDescription
checkedbooleanfalseWhether on
disabledbooleanfalseDisabled state
namestringForm field name
valuestringValue when on
<fig-switch>Enable notifications</fig-switch><fig-switchchecked>Active feature</fig-switch>

Inputs

Slider

<fig-slider>demo

AttributeTypeDefaultDescription
typestring"range""range", "hue", "opacity", "delta", "stepper"
valuenumbermidpoint for type="range"Current value
minnumber0Minimum
maxnumber100Maximum
stepnumber1Step increment
defaultnumberDefault/reset value (shown as marker)
textbooleantrueShow text input; set text="false" to hide
placeholderstring"##"Text input placeholder
unitsstringUnit label (e.g. "%", "px")
transformnumberDisplay value multiplier
colorstringTrack color (opacity type)
variantstringUse "classic" to opt into the previous slider appearance
precisionnumberDecimal places for output
disabledbooleanfalseDisabled 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

<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.

AttributeTypeDefaultDescription
labelstring"Label"Field label text; use an empty value to hide it
namestringOptional event payload name
defaultnumber/stringinitial valueRight-click reset target
disabledbooleanfalseDisable interaction
number attrsAll <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

<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

<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

<propskit-gradient>

Uses a plain horizontal surface with <fig-input-gradient>. Defaults to edit="picker" — click the surface to open the fill picker.

AttributeTypeDefaultDescription
labelstring"Label"Field label text; use an empty value to hide it
valueJSON stringdefault gradientCanonical { "type": "gradient", "gradient": { ... } } data
defaultJSON stringinitial valueRight-click and group reset target
editboolean/string"picker"true (inline stops), false, or "picker"
modestring"handle""handle" or "tip" stop presentation
disabledbooleanfalseDisabled 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

<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

<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

<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

<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

<propskit-slider>

Uses a plain horizontal surface with <fig-slider>. Slider attributes except host-only PropsKit attributes are forwarded to the inner slider.

AttributeTypeDefaultDescription
labelstring"Label"Field label text; use an empty value to hide it
namestringOptional event payload name
defaultnumber/stringinitial valueDouble-click and right-click reset target
disabledbooleanfalseDisable interaction
slider attrsAll <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>

Input Wheel

<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.

AttributeTypeDefaultDescription
valuenumber0Current numeric value
stepnumber1Scrub increment
spinboolean/stringtrueKeep ticks synchronized to value; set "false" to leave them stationary
minnumberInclusive lower bound; omit for no minimum
maxnumberInclusive upper bound; omit for no maximum
disabledbooleanfalseDisable 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

<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.

AttributeTypeDefaultDescription
labelstring"Label"Empty removes the visible label
namestringOptional event payload name
unitsstringOptional arbitrary units. seconds / milliseconds normalize to s / ms
valuenumber0Numeric value in units. Unbounded unless min/max are set
minnumberInclusive lower bound. Omit for no minimum
maxnumberInclusive upper bound. Omit for no maximum
stepnumber1; 0.1 (s) / 100 (ms)Drag, keyboard, and mouse wheel increment
precisionnumber0; 2 (s) / 0 (ms)Displayed decimal places on the optional number field
elasticboolean/stringtrueStretch the composed row past the wheel edges; set "false" to disable row stretch. The handle still pulls
spinboolean/stringtrueKeep wheel ticks synchronized to value; set "false" to update only the value and number field
textboolean/stringtrueInclude the editable fig-input-number; set "false" for only fig-input-wheel
defaultnumber/stringinitial valueRight-click reset target
disabledbooleanfalseDisable 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

<propskit-position>

A compact X/Y field with optional percentage units.

AttributeTypeDefaultDescription
xnumber50Horizontal value
ynumber50Vertical value
defaultJSON stringinitial { x, y }Right-click and group reset target
labelstring"Label"Field label; empty removes the visible label
unitsstring"percent" shows %; omit for no units
disabledbooleanfalseDisable 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

<propskit-joystick>

A vertical PropsKit surface with a label above a square fig-joystick and its X/Y percentage fields.

AttributeTypeDefaultDescription
valueJSON string{"x":50,"y":50}Current percentage coordinates
defaultJSON stringinitial valueRight-click and group reset target
labelstring"Label"Field label; empty removes the visible label
axis-labelsstringOne, two, or four labels forwarded to the joystick
coordinatesstring"screen""screen" or "math" coordinate mode
precisionnumber3X/Y field display precision
disabledbooleanfalseDisable 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

<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

<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

<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

<propskit-color-point>

A compact <fig-group> wrapper that combines <propskit-color> and <propskit-position>. The group is collapsible and open by default.

AttributeTypeDefaultDescription
labelstringPassed to the internal fig-group as name
valueJSON string{"x":50,"y":50,"color":"#D9D9D9"}Current color-point value
collapsiblebooleantruePassed to the internal fig-group
openbooleantruePassed to the internal fig-group
disabledbooleanfalseDisable 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

<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.

AttributeTypeDefaultDescription
labelstringPassed to the internal fig-group as name
valueJSON string{"x":50,"y":50,"radius":0}Current point-radius value
collapsiblebooleantruePassed to the internal fig-group
openbooleantruePassed to the internal fig-group
unitsstring"percent" passes percentage units to position and radius; omit for no units
disabledbooleanfalseDisable 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

<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.

AttributeTypeDefaultDescription
labelstringPassed to the internal fig-group as name
valueJSON string{"x":50,"y":50,"radius":0,"angle":0}Current point-radius-angle value
collapsiblebooleantruePassed to the internal fig-group
openbooleantruePassed to the internal fig-group
unitsstring"percent" passes percentage units to position and radius; omit for no units
disabledbooleanfalseDisable 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

<propskit-point-point>

A compact <fig-group> wrapper combining start and end <propskit-position> controls. The group is collapsible and open by default.

AttributeTypeDefaultDescription
labelstringPassed to the internal fig-group as name
valueJSON string{"x":50,"y":50,"x2":75,"y2":75}Current point-point value
collapsiblebooleantruePassed to the internal fig-group
openbooleantruePassed to the internal fig-group
unitsstring"percent" passes percentage units to both positions; omit for no units
disabledbooleanfalseDisable 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>

Text Input

<fig-input-text>demo

AttributeTypeDefaultDescription
valuestringInput value
placeholderstringPlaceholder text
typestring"text""text" or "number"
disabledbooleanfalseDisabled state
multilinebooleanfalseUse textarea
minnumberMin (number type)
maxnumberMax (number type)
stepnumberStep (number type)
transformnumberDisplay multiplier
<fig-input-textvalue="Hello" placeholder="Enter text..."></fig-input-text><fig-input-textmultilineplaceholder="Enter description..."></fig-input-text>

Number Input

<fig-input-number>demo

AttributeTypeDefaultDescription
valuestringNumeric value
placeholderstringPlaceholder text
minnumberMinimum
maxnumberMaximum
stepnumberStep increment
unitsstringUnit string (e.g. "px", "%")
units-disallowstring"px"Comma-separated unit disallow list (e.g. "px,rem")
unit-positionstring"suffix""suffix" or "prefix"
transformnumberDisplay multiplier
precisionnumberFixed displayed decimal places; omitted values use up to two places
steppersbooleanfalseShow spin buttons
disabledbooleanfalseDisabled state
<fig-input-numbervalue="100" units="px"></fig-input-number><fig-input-numbervalue="50" units="%" min="0" max="100"></fig-input-number>

Input Angle

<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).

AttributeTypeDefaultDescription
valuenumber0Angle value
precisionnumber1Decimal places
textbooleanfalseShow text input
dialbooleantrueShow circular dial
minnumberMinimum (omit for unbounded)
maxnumberMaximum (omit for unbounded)
unitsstring"°""°" / "deg", "rad", "turn"
rotationsbooleanfalseShow 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

<propskit-oscillator>

Waveform oscillator input with composable wave functions, live SVG waveform preview, draggable parameter handles, and direct numeric controls.

AttributeTypeDefaultDescription
valueJSON string{"waves":[{"type":"sine","frequency":1,"amplitude":1,"phase":0,"offset":0}]}
namestringOptional event payload name
defaultJSON stringinitial valueRight-click reset target
precisionnumber2Decimal places
aspect-ratiostring"2 / 1"Editor aspect ratio
editbooleantrueShow editor and number fields; set to "false" for preview only
disabledbooleanfalseDisable 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:

EventDetail
inputShared PropsKit { control, value, name? } envelope while dragging or editing
changeShared 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>

Color & Fill

Swatch

<fig-swatch>demo

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

AttributeTypeDefaultDescription
backgroundstringCSS background value
sizestring"small""small" or "large"
selectedbooleanfalseSelection ring
disabledbooleanfalseDisabled state
alphanumberOpacity (0-1)
<fig-swatchbackground="#FF5733"></fig-swatch><fig-swatchbackground="linear-gradient(90deg, #FF0000, #0000FF)" size="large"></fig-swatch>

Color Tip

<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.

AttributeTypeDefaultDescription
valuestringColor string (hex/rgb/hsl/named)
selectedbooleanfalseSelected state
disabledbooleanfalseDisabled state
alphabooleanfalseShow alpha controls
controlstring"add" or "remove" for icon-only mode

Events:

EventDetail
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>

Color Input

<fig-input-color>demo

AttributeTypeDefaultDescription
valuestringHex color (e.g. "#FF5733" or "#FF573380")
textbooleanfalseShow hex text input
alphabooleantrueShow alpha slider; set alpha="false" to hide opacity controls
modestringColor mode ("hex", "rgb", "hsl")
picker-*stringForwarded to <fig-fill-picker> when the optional picker is registered
disabledbooleanfalseDisabled state

Events:

EventDetail
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.


Input Palette

<fig-input-palette>

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

AttributeTypeDefaultDescription
valuestringJSON array of hex strings or {color, alpha} objects
disabledbooleanfalseDisabled state
minnumber2Minimum number of colors
maxnumber8Maximum (add button hidden at max)
openbooleanfalseExpanded rows with text inputs per color
fixedbooleanfalseLock palette length — no add or remove (fixed or fixed="true")

Events:

EventDetail
inputFull color array (during editing)
changeFull 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.


Gradient Input

<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.

AttributeTypeDefaultDescription
valuestringJSON gradient fill data
editboolean/stringtruetrue, false, or "picker"
modestring"handle""handle" renders color stop handles without tips; "tip" renders persistent color tips
disabledbooleanfalseDisabled state
picker-*stringPassthrough picker attributes

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

Events:

EventDetail
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>

Fill Input

<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.

AttributeTypeDefaultDescription
valuestringJSON fill data
disabledbooleanfalseDisabled state
modestringLock to a fill mode
alphabooleantrueShow alpha controls
webcam-modestringliveForwarded to the picker: live or snapshot
default-videostringForwarded sample clip URL when Video has no file
picker-*stringForwarded 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:

EventDetail
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>

Fill Picker

<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.

AttributeTypeDefaultDescription
valuestringJSON fill value
disabledbooleanfalseDisabled state
alphabooleantrueShow alpha controls
modestringLock to mode: "solid", "gradient", "image", "video", "webcam"
webcam-modestringlivelive keeps the camera after close; Capture always writes an image still
default-videostringSample 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:

EventDetail
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>

Selection

Tabs

<fig-tabs> / <fig-tab>demo

AttributeTypeDefaultDescription
valuestringSelected tab value
namestringTabs group identifier
disabledbooleanfalseDisable 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>

Segmented Control

<fig-segmented-control> / <fig-segment>demo

AttributeTypeDefaultDescription
namestringGroup identifier
valuestringSelected segment value
animatedbooleanfalseAnimate indicator transitions
sizingstring"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>

Chooser

<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:

AttributeTypeDefaultDescription
valuestringSelected choice value. Omit to select the first choice; value="" means none.
choice-elementstring"fig-choice"CSS selector for child choices
layoutstring"vertical""vertical", "horizontal", "grid"
columnsnumber2Grid column count when layout="grid"
disabledbooleanfalseDisabled state
dragbooleanfalseEnable drag-to-scroll
overflowstringOverflow behavior
loopbooleanfalseLoop keyboard navigation
auto-scrollbooleantrueAutomatically center the selection after selection, layout, resize, or media changes. Set to "false" to disable.
scroll-behaviorstring"smooth"Selection and overflow-button scrolling behavior: "smooth" or "auto". The CSS scroll-behavior property can also override it.

fig-choice attributes:

AttributeTypeDefaultDescription
valuestringChoice identifier
selectedbooleanfalseSelected state
disabledbooleanfalseDisabled 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>

Spatial Controls

Joystick

<fig-joystick>

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

AttributeTypeDefaultDescription
valuestring"50% 50%"Position as percentages
precisionnumberDecimal places
transformnumberOutput scaling
fieldsbooleanfalseShow X/Y inputs
coordinatesstring"screen""screen" (0,0 top-left) or "math" (0,0 bottom-left)
aspect-ratiostring"1 / 1"Plane ratio
axis-labelsstringComma- or space-delimited labels. 1 value: top. 2 values: x y. 4 values: left right top bottom

Events:

EventDetail
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.


Origin Grid

<fig-origin-grid>

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

AttributeTypeDefaultDescription
valuestring"50% 50%"CSS transform-origin pair
precisionnumber0Decimal places
aspect-ratiostringGrid aspect ratio
dragbooleantrueEnable handle dragging
fieldsbooleanfalseShow X/Y fields

Events:

EventDetail
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.


Easing Curve

<fig-easing-curve>

An interactive bezier or spring easing curve editor with a preset dropdown and manual value input.

AttributeTypeDefaultDescription
valuestringBezier: "0.42, 0, 0.58, 1" or Spring: "spring(200, 15, 1)"
modestringOptional "bezier" or "spring" constraint; filters presets and rejects the other value type
precisionnumber2Decimal places
aspect-ratiostringEditor aspect ratio
editbooleantrueShow 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:

EventDetail
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.


3D Rotate

<fig-3d-rotate>

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

AttributeTypeDefaultDescription
valuestringCSS transform, e.g. "rotateX(20deg) rotateY(-35deg) rotateZ(0deg)"
precisionnumber1Decimal places
aspect-ratiostringCube container ratio
fieldsbooleanfalseShow X/Y/Z number inputs
perspectivestringCSS perspective value
perspective-originstringCSS perspective-origin
transform-originstringCSS transform-origin
selectedstringHighlighted face
dragbooleantrueEnable drag rotation

Events:

EventDetail
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>

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.

AttributeTypeDefaultDescription
valuestringPosition as "X% Y%"
colorstringHandle color
selectedbooleanfalseSelected state
disabledbooleanfalseDisabled state
dragbooleanfalseEnable dragging
drag-surfacestringCSS selector for drag container (defaults to parent)
drag-axesstring"xy"Constrain axes: "x", "y", "xy"
drag-snappingstringSnapping behavior
precisionnumber2Decimal places for percentage values and pixel coordinates; use 0 for whole numbers
typestring"color" for a color handle with direct picker activation
tipstring"color", "add", or "remove" to show a persistent fig-color-tip
hit-areastringExpanded interaction zone (unitless px). "8", "8 12" (v h), or "8 circle"
hit-area-modestring"handle""handle" proxies to handle drag/select; "delegate" emits hitareadown event

Events:

EventDetail
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.


Canvas Control

<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.

AttributeTypeDefaultDescription
typestring"point""point", "color", "point-radius", "point-radius-angle", "point-point"
valueJSON string{ "x": 50, "y": 50 } — see type-specific shapes below
namestringTooltip label(s). Comma-separated for two handles: "Start, End"
colorstringPassthrough color for type="color" handle
tooltipsstring"true"Show value tooltips on interaction
disabledbooleanfalseDisable all interaction
drag-surfacestring"parent"Forwarded to inner fig-handles
snappingstring"false""false", "true", "modifier" — applies to all handles
precisionnumber2Decimal places for positions, radius, angle, and internal handle dragging

Value shapes by type:

TypeValue 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:

EventDetail
inputValue object (shape depends on type) — while dragging
changeValue 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>

Layout & Feedback

Field

<fig-field>demo

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

AttributeTypeDefaultDescription
directionstring"column""column", "row", "horizontal"
columnsstringSplit preset: "thirds" or "half"
labelstringProgrammatic label text
<fig-fielddirection="horizontal" columns="thirds"><label>Opacity</label><fig-slidervalue="50" units="%"></fig-slider></fig-field>

Dialog

<fig-dialog>demo

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

AttributeTypeDefaultDescription
openbooleanfalseVisible state
modalbooleanfalseModal mode
dragbooleanfalseDraggable
handlestringCSS selector for drag handle
positionstring"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.


Popup

<fig-popup>demo

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

AttributeTypeDefaultDescription
anchorstringCSS selector for anchor element
positionstring"top center"Placement
offsetstring"0 0"X/Y offset
viewport-marginstring"8"Viewport safety margin
variantstring"popover" for beak styling
themestring"light", "dark", "menu"
closedbystring"any""any", "closerequest", "none"
openbooleanfalseOpen state
dragbooleanfalseDraggable
handlestringCSS selector for drag handle
autoresizebooleanfalseAuto-resize to content
titlestringAuto-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.


Toast

<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.

AttributeTypeDefaultDescription
durationnumber5000Auto-dismiss ms (0 = no dismiss)
offsetnumber16Distance from bottom
themestring"dark""dark", "light", "danger", "brand", "auto"
livestring"assertive" for urgent announcements
iconstringOptional fig-icon name prepended when set; always rendered at full size
dismissbooleanfalseAppends 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>

Tooltip

<fig-tooltip>demo

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

AttributeTypeDefaultDescription
textstringTooltip text
actionstring"hover""hover", "click", "manual"
delaynumber500Show delay in ms
offsetstring"left,top,right,bottom"
showbooleanfalsePersistent show state
openbooleanfalseProgrammatic 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.


Separator

<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>

Menu

<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.

AttributeTypeDefaultDescription
openbooleanfalseOpen state
disabledbooleanfalseDisable trigger/menu
positionstring"bottom left"Popup placement
offsetstringPopup offset
closedbystringPopup close behavior
triggerstringUse "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.


Header

<fig-header>demo

A section header component.

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

Layer

<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.

AttributeTypeDefaultDescription
openbooleanfalseWhether children are expanded
visiblebooleantrueWhether 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>

Preview

<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.

AttributeTypeDefaultDescription
aspect-ratiostring"4/3"CSS aspect ratio such as "1/1" or "16/9"
fitstringcontainObject fit for direct media children
fullbooleanfalseStretch to the available width
checkerboardbooleanfalseShow 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>

Media

<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.

AttributeTypeDefaultDescription
typestring"image""image" or "video"
srcstringMedia URL
altstring""Alt text forwarded to the generated <img> (image mode)
uploadbooleanfalseShow upload overlay (fig-input-file)
loading-indicatorbooleantrueSet to "false" to disable the delayed loading spinner for generated images
labelstring"Upload"Upload button label
sizestringsmall | medium | large | auto (token-sized square)
aspect-ratiostringCSS aspect-ratio (e.g. "16 / 9"); fills container width
fitstring"contain"CSS object-fit ("cover", "contain", etc.)
checkerboardbooleanfalseShow checkerboard behind transparent media
captionstringCaption text rendered below the media preview
controlsbooleanfalseShow playback controls for video
autoplaybooleanfalseVideo autoplay
loopbooleanfalseVideo loop
mutedbooleanfalseVideo muted
posterstringVideo poster URL
aria-labelstringAccessible 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.


Image

<fig-image>demo

An image display component with optional upload, aspect ratio, and object-fit control. Renders a real <img> inside a fig-preview.

AttributeTypeDefaultDescription
srcstringImage URL
altstring""Alt text forwarded to the generated <img>
uploadbooleanfalseShow upload overlay (fig-input-file)
loading-indicatorbooleantrueSet to "false" to disable the delayed loading spinner
labelstring"Upload"Upload button label
sizestringsmall | medium | large | auto (token-sized square)
aspect-ratiostringCSS aspect-ratio (e.g. "16 / 9"); fills container width
fitstring"contain"CSS object-fit ("cover", "contain", etc.)
checkerboardbooleanfalseShow checkerboard behind transparent images
captionstringCaption 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.


Card

<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.

AttributeTypeDefaultDescription
srcstringImage URL forwarded to generated fig-image
altstring""Alt text forwarded to generated fig-image
labelstringCard title (preferred over text)
textstringAlias for label
sublabelstringSecondary one-line text under the label
selectedbooleanfalseSelected chrome only (no click-toggle)
disabledbooleanfalseDim + non-interactive
fullbooleanfalseStretch to the available width (cards are already width: 100% by default)
sizestringdefaultSet to "large" for --spacer-2 card padding and --spacer-1 label spacing
aspect-ratiostring"1/1"Forwarded to generated fig-image
fitstring"contain"Forwarded to generated fig-image
label-line-clampstring"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.


Video

<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.

AttributeTypeDefaultDescription
srcstringVideo URL
uploadbooleanfalseShow upload overlay (fig-input-file)
labelstring"Upload"Upload button label
sizestringsmall | medium | large | auto (token-sized square)
aspect-ratiostringCSS aspect-ratio (e.g. "16 / 9"); fills container width
fitstring"contain"CSS object-fit ("cover", "contain", etc.)
controlsbooleanfalseShow playback controls
autoplaybooleanfalseAutoplay video
loopbooleanfalseLoop video
mutedbooleanfalseMute video
posterstringPoster image URL (forwarded to inner <video>)
aria-labelstringAccessible label forwarded to the generated <video>
captionstringCaption 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>

Avatar

<fig-avatar>demo

Profile image or initials fallback.

AttributeTypeDefaultDescription
srcstringImage URL
namestringName for initials fallback
sizestring"large"
<fig-avatarsrc="https://example.com/photo.jpg" name="John Doe"></fig-avatar><fig-avatarname="Jane Smith" size="large"></fig-avatar>

Icon

<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.

AttributeTypeDefaultDescription
namestringIcon name: add, send, close, chevron, arrow-left, etc.
sizestringmediummedium (1.5rem) or small (1rem)
colorstringAlias (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.


Spinner

<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>

Shimmer

<fig-shimmer>demo

A shimmer loading placeholder.

AttributeTypeDefaultDescription
durationstring"1.5s"Animation cycle duration
playingbooleantrueWhether 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>

Skeleton

<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>

Theming

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.


Framework Integration

React

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 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

<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>

Svelte

<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)} />

Development

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/:

SourceMinifiedTool
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.cssdist/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 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

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

About

A lightweight, customizable web component library that uses Figmas UI3 style for modern web applications, but specifically for Figma plugins.

Resources

Contributing

Stars

65 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages