Arlan

Figma Vector Editor

Component 29

About

Figma's vector-editing chrome rebuilt for the web, in two parts. FigmaFrame draws the 'selected layer' look around any element: a thin accent border that animates open, four corner handles that pop in one by one, and a live W×H dimension badge tracked with a ResizeObserver. VectorEditor is a live SVG bezier point editor: every anchor is a draggable dot and every bezier handle a draggable diamond joined by a thin tangent arm — drag anchors to move, drag handles to reshape, hold Alt to break a tangent into a corner, with Figma-style mirroring modes (none / angle / angle-length). Includes a lossless SVG path parser/serializer (M/L/H/V/C/S/Q/T/Z, multi-subpath with holes) so you can drop in any path, edit it, and read the new d string back. Zero dependencies, pure pointer math through the live screen CTM.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Figma Vector Editor to your project and fits it to your theme.

Code

How Figma Vector Editor is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import { useState } from "react";
import {
  VectorEditor,
  FigmaFrame,
  parsePath,
  type VectorPath,
  type EditorStyle,
  type FrameStyle,
} from "@/components/ui/vector-editor";

// The "arlan" wordmark, as an SVG path — the shape you drag the points on.
const WORD =
  "M 19.8 150 L 51.8 150 L 62.2 118.2 L 115.8 118.2 L 126 150 L 158.8 150 L 104.6 7 L 73.6 7 L 19.8 150 Z M 69.6 94.6 L 88.8 34.6 L 89.2 34.6 L 108.6 94.6 L 69.6 94.6 Z M 195.8 100.4 C 195.8 81.8 208.8 71 224.6 71 C 226.8 71 228.6 71.2 231.2 71.6 L 231.2 47 C 227.8 46.4 225.2 46.4 222.2 46.4 C 210 46.4 201.2 54 196.2 65.4 L 195.8 65.4 L 195.8 47.8 L 168.4 47.8 L 168.4 150 L 195.8 150 L 195.8 100.4 Z M 242.6 150 L 270 150 L 270 7 L 242.6 7 L 242.6 150 Z M 317 153 C 332.4 153 344 146.6 351.2 136.8 L 351.6 136.8 C 353 146.4 358 151.2 369.6 151.2 C 373.8 151.2 379.2 150.8 384.4 149.8 L 384.4 135.6 C 383.2 135.8 382.6 135.8 382 135.8 C 378.4 135.8 377.8 133 377.8 126.8 L 377.8 84.2 C 377.8 54.8 361.8 44.4 336.6 44.4 C 303.2 44.4 289.4 60.8 288.4 80.4 L 315 80.4 C 316 69 321.4 63.8 335.4 63.8 C 346.8 63.8 351.2 68.4 351.2 74.8 C 351.2 84.8 337.4 86.6 324.6 89.2 C 301.6 93.8 284.2 101.6 284.2 123.4 C 284.2 141.2 296.8 153 317 153 Z M 327 133.4 C 317.4 133.4 312.4 129.2 312.4 120.8 C 312.4 112.2 317.8 107.2 331.8 104.2 C 341.2 102 347.4 100.4 351.2 98.4 L 351.2 109.2 C 351.2 123.2 341.6 133.4 327 133.4 Z M 466.4 88.6 L 466.4 150 L 494 150 L 494 80.2 C 494 56.8 479.8 44.6 460.4 44.6 C 442.4 44.6 432.2 53 426.8 61.8 L 426.4 61.8 L 426.4 47.8 L 399 47.8 L 399 150 L 426.4 150 L 426.4 89.6 C 426.4 74.8 434.8 67.2 448.8 67.2 C 460.8 67.2 466.4 73.8 466.4 88.6 Z";

const VIEW: [number, number, number, number] = [0, 0, 511, 169];

const settings = {
  accent: "#0d99ff",
  fill: "#0d99ff",
  fillOpacity: 0.08,
  strokeWidth: 1.5,
  showRig: true,
  showBadge: true,
};

export default function Demo(props: Partial<typeof settings>) {
  const s = { ...settings, ...props };
  const [path, setPath] = useState<VectorPath>(() => parsePath(WORD));

  const editorStyle: EditorStyle = {
    accent: s.accent,
    arm: "#9bb7d4",
    anchorR: 4,
    handleR: 3.2,
    pointFill: "#ffffff",
    fill: s.fill,
    fillOpacity: s.fillOpacity,
    stroke: s.accent,
    strokeWidth: s.strokeWidth,
    showRig: s.showRig,
    fillRule: "evenodd",
  };

  const frameStyle: FrameStyle = {
    accent: s.accent,
    handleSize: 8,
    handleFill: "#ffffff",
    borderWidth: 1,
    showHandles: true,
    showBadge: s.showBadge,
    badgeBg: s.accent,
    badgeText: "#ffffff",
  };

  return (
    <div className="flex h-screen w-full items-center justify-center bg-background">
      <FigmaFrame style={frameStyle} width={380} height={179}>
        <div
          className="flex items-center justify-center"
          style={{ width: 380, height: 179 }}
        >
          <VectorEditor
            path={path}
            onChange={setPath}
            style={editorStyle}
            viewBox={VIEW}
            width={340}
            height={112}
          />
        </div>
      </FigmaFrame>
    </div>
  );
}

More by Arlan

View all
  • Midjourney ASCII Swirl

    Midjourney ASCII Swirl

    71
  • Dia Gradient

    Dia Gradient

    49
  • Symbols Effect

    Symbols Effect

    33
  • Embroidered Word Patches

    Embroidered Word Patches

    19