@rogieking/figui3
Version:
A lightweight web components library for building Figma plugin and widget UIs with native look and feel
255 lines (195 loc) • 11.9 kB
Markdown
---
name: figui3
description: Guides development and maintenance of the FigUI3 web components library for Figma-style plugin UIs. Applies when adding or modifying `fig-*` custom elements, updating docs/demo pages, adjusting theme tokens, improving accessibility, or debugging component behavior in `fig.js`, `components.css`, `index.html`, and `README.md`.
user-invocable: false
---
# FigUI3
A lightweight web components library for Figma UI3-style plugin and widget interfaces.
> IMPORTANT: Prefer the project's native scripts and structure. Use `bun dev` for local docs/demo work and `bun build` for production output.
## Current Project Context
```json
!`node -e "const p=require('./package.json'); console.log(JSON.stringify({name:p.name,version:p.version,scripts:p.scripts,exports:p.exports},null,2))" 2>/dev/null || echo '{"error":"package.json not found"}'`
```
The JSON above is the source of truth for package name, build commands, and exported files.
## Principles
1. **Preserve native Web Components patterns.** Keep components framework-agnostic and rooted in custom elements.
2. **Prefer existing `fig-*` components over one-off markup.** Compose from current primitives before inventing new ones.
3. **Keep Figma UI3 visual consistency.** Use existing CSS variables and spacing/radius conventions.
4. **Honor interaction semantics.** Emit `input` while interacting and `change` on committed value changes.
5. **Treat accessibility as required behavior.** Preserve labels, keyboard support, ARIA attributes, and disabled states.
## React + Vite Integration
### Install and bootstrap in React
- Install package: `npm i @rogieking/figui3` (or `pnpm add` / `bun add`).
- Import CSS once in app entry (`main.tsx` / `main.jsx`): `import "@rogieking/figui3/fig.css";`
- Register custom elements before first render. In Vite/React, prefer an explicit bootstrap:
```tsx
import "@rogieking/figui3/fig.css";
const bootstrap = async () => {
// Prevent production tree-shaking from dropping registration side effects.
await import("@rogieking/figui3/fig.js");
createRoot(document.getElementById("app")!).render(<App />);
};
bootstrap();
```
### Vite config guidance
- Standard React Vite config is usually enough:
```ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
});
```
- Keep FigUI3 registration import at the top-level app bootstrap (not inside leaf components).
- If a production build appears to tree-shake element registration, use the explicit dynamic import pattern above.
### React usage rules for web components
- Use DOM attrs on custom elements (`<fig-slider text="true" />`) and read values from `e.target` / `e.detail`.
- In React, use `class` (not `className`) for all FigUI3 web components (`fig-*` and `<dialog is="fig-...">`) to keep attribute behavior consistent.
- Prefer refs + `addEventListener` when wiring complex `input`/`change` behavior.
### React + color picker modes (`fig-input-color` / `fig-fill-picker`)
- `fig-fill-picker` is optional. Import `fig-editor.js` and `fig-editor.css` when full picker behavior is needed.
- Do not use `picker` or `picker-anchor` on `fig-input-color`; components auto-detect `fig-fill-picker` at interaction time.
- `picker-*` attrs on `fig-input-color` are forwarded to `fig-fill-picker` only when the optional picker is registered.
- Example: `picker-dialog-position`, `picker-experimental`, etc.
- For React custom modes, use `fig-fill-picker` + slot API:
- Add a child with `slot="mode-<name>"` (and optional `label`).
- Include `<name>` in the `mode` attribute (e.g. `mode="solid,react-demo"`).
- Listen for `modeready` and render into `e.detail.container`.
- Do not reparent React-owned DOM into the picker after render; use the provided `modeready` container as mount target.
- Keep React lifecycle cleanup explicit for custom mode mounts:
- keep one `root` per mode container
- call `root.unmount()` when the host component unmounts
- remove `modeready` listeners in cleanup to avoid duplicate mounts
- Custom mode content must dispatch `input` / `change` with `detail` payload so picker can store mode data and propagate events.
- Preserve value shape expectations:
- `fig-input-color` expects solid color data (`detail.color`, optional `detail.alpha`) from the picker.
- `fig-fill-picker` custom modes use JSON with `type` set to mode name and remaining data in payload.
## Experimental Attribute Guidance
- Use `experimental` as a feature-flag string for opt-in behavior. Treat it as progressive enhancement, not guaranteed baseline behavior.
- Prefer `experimental="modern"` when enabling modern customizable select/picker UI behavior.
- Keep usage explicit on the component that needs it (for example `fig-dropdown`, `fig-fill-picker`, `fig-input-fill`, `fig-input-color`).
- Preserve pass-through behavior:
- `fig-input-color` and `fig-input-fill` forward experimental-related picker settings into internal `fig-fill-picker` usage.
- Avoid adding hidden implicit defaults that enable experimental behavior globally.
- Backward-compat rule:
- Do not reintroduce the old `neue` variant name for dropdown experimental behavior.
- Use `variant="classic"` only when a slider needs the previous visual style.
- Documentation rule: any new experimental token must be documented with activation syntax, intended scope, and fallback behavior in demos + README + changelog.
## Critical Rules
### Overlay Components (`fig-dialog`, `fig-popup`)
- Choose the overlay primitive intentionally:
- **`<dialog is="fig-dialog">`** for modal/light-dismiss dialog workflows.
- **`<dialog is="fig-popup">`** for anchored floating surfaces (menus, contextual panels, nested popups).
- Keep overlay semantics stable:
- `fig-dialog` should remain dialog-first (title/header/footer patterns, modal semantics).
- `fig-popup` should remain anchor/position-first (offset, collision handling, viewport margins).
- Preserve drag and positioning behavior on both `fig-dialog` and `fig-popup`; do not regress manual placement rules.
- For popup chains, maintain containment and dismissal logic across descendant popups.
- Document any overlay behavior change in demos and changelog with a concrete before/after note.
### Component Architecture
- Extend `HTMLElement` and implement lifecycle cleanup in `disconnectedCallback`.
- Use `observedAttributes` + `attributeChangedCallback` for attribute-driven reactivity.
- Keep attribute names and behavior backward-compatible unless explicitly doing a breaking change.
- Support `disabled` behavior wherever interaction is possible.
- Avoid introducing framework-specific assumptions in component internals.
### Events and Data Contracts
- Emit standard `input` and `change` events for form-like controls.
- Put rich payloads in `event.detail` when needed; keep names stable.
- Do not silently change event payload shape for existing components.
- When adding new events, document trigger timing and payload fields.
### Styling and Theming
- Reuse established design tokens and CSS variables before adding new ones.
- Keep light/dark compatibility working with `color-scheme` and current token strategy.
- Avoid ad-hoc hardcoded colors when semantic tokens already exist.
- Preserve current sizing, spacing, and radius rhythm unless intentionally refactoring system-wide.
### Documentation and Demos
- Update `README.md` component docs when public API or behavior changes.
- Update demo surfaces (`index.html` and `playground/` routes where relevant) for visible behavior changes.
- Prefer realistic examples that mirror plugin/property panel usage.
- If introducing an experimental feature, document activation and fallback behavior clearly.
### Compatibility and Safety
- Keep browser support expectations aligned with current README claims.
- Use progressive enhancement for bleeding-edge CSS features.
- Avoid regressions in existing attributes, defaults, and emitted events.
### Color Picker Mode Extensibility
- Treat custom modes as a `fig-fill-picker` concern, not a standalone `fig-input-color` concern.
- When adding a new mode, update demos/docs with both:
- vanilla slot usage (`slot="mode-*"`)
- React `modeready` usage
- Do not emit `input` from programmatic attribute writes (`value` updates); preserve current loop-avoidance behavior for React.
## Key Patterns
```html
<!-- Modal/dialog content container -->
<dialog is="fig-dialog" drag="true" handle="fig-header">
<fig-header>
Dialog Title
<fig-button variant="ghost" icon close-dialog aria-label="Close dialog">
<fig-icon name="close"></fig-icon>
</fig-button>
</fig-header>
<div>Dialog body</div>
</dialog>
<!-- Anchored popup surface -->
<dialog is="fig-popup" anchor="#trigger" position="bottom left" offset="8 8">
<div>Popup content</div>
</dialog>
```
```js
// Event contract pattern: continuous + committed updates.
this.dispatchEvent(new CustomEvent("input", { detail, bubbles: true }));
this.dispatchEvent(new CustomEvent("change", { detail, bubbles: true }));
// Attribute-driven updates.
static get observedAttributes() { return ["value", "disabled"]; }
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) return;
// sync internal UI state
}
```
```txt
Event contract quick map:
- fig-slider: input/change -> current value on e.target.value
- fig-input-color: input/change -> value on e.target.value, structured color via e.detail (when available)
- fig-input-fill / fig-fill-picker: input/change -> fill payload in e.detail
```
```html
<!-- Typical field composition -->
<fig-field direction="horizontal">
<label>Opacity</label>
<fig-slider value="75" min="0" max="100" text="true" units="%"></fig-slider>
</fig-field>
```
## `fig-popup` vs `fig-dialog`
- **Use `fig-dialog` when the UI is a dialog.**
- Best for modal or primary task flows.
- Works well with explicit dialog structure and close policies.
- **Use `fig-popup` when you need low-level floating control.**
- Best for anchored contextual surfaces and advanced positioning behavior.
- Prefer this when you need explicit anchor/position/offset/viewport tuning.
Rule of thumb: `fig-dialog` = dialog UX, `fig-popup` = popup primitive.
## Workflow
1. **Read existing implementation first.** Check `fig.js`, `components.css`, and related demo usage before editing.
2. **Confirm API surface impact.** Identify affected attributes, events, and slots.
3. **Implement with compatibility in mind.** Preserve defaults and old usage unless explicitly changed.
4. **Update docs/demo in same pass.** Keep examples and behavior synchronized.
5. **Run project checks.** Use `bun dev` for interactive verification and `bun build` for output sanity.
6. **Verify accessibility and theming.** Check keyboard flow, labels, disabled states, and both light/dark appearance.
## Release-Ready Checklist
- Validate in a production build (`bun build`) and confirm custom elements are registered at runtime.
- Verify `input` vs `change` behavior for touched controls in both vanilla usage and React integration.
- Verify light/dark themes and keyboard navigation for any changed component.
- Verify overlay behavior (`fig-dialog`, `fig-popup`) including close/dismiss and drag behavior when applicable.
- Update `README.md`, demos, and `CHANGELOG.md` for any public API or behavior change.
## Quick Reference
```bash
# Start docs/demo server
bun dev
# Build distributable files
bun build
```
## Primary Files
- `fig.js` - component implementations and behavior
- `components.css` - component-level styling and states
- `base.css` - foundational styles and variables
- `index.html` - main interactive docs/demo
- `README.md` - public API and usage documentation
- `CHANGELOG.md` - release history and migration notes