npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fabric-editor-kit

v0.1.1

Published

Professional production-grade React editor toolkit for Fabric.js — contextual toolbars, layer panel, export pipeline, context menu and dynamic theming.

Readme

fabric-editor-kit

A professional, production-grade React wrapper for Fabric.js.
Drop-in contextual toolbars, layer panel, export pipeline, right-click context menu, and dynamic theming — everything a Canva-style editor needs, out of the box.

Getting Started · Components · Theming · Hooks · Roadmap


Why fabric-editor-kit?

Raw Fabric.js gives you a powerful canvas engine — but zero UI. Building a Canva-style editor means hand-crafting toolbars for every object type, managing undo/redo, writing export logic, and wiring up a layer panel. That is weeks of work before you write a single line of your actual product.

fabric-editor-kit solves all of that.

| Without fabric-editor-kit | With fabric-editor-kit | |---|---| | Build toolbar for every object type | Drop in <TextToolbar />, <ShapeToolbar /> etc. | | Write undo/redo from scratch | Built into FabricKitProvider | | Custom context menu | useContextMenu() hook, ready to wire | | No layer management UI | <LayerPanel /> with drag reorder | | Manual PNG/SVG/JSON export | <ExportPanel /> or useExport() hook | | Hard-coded colors, no theming | createTheme() with full token system |


Getting Started

Installation

npm install fabric-editor-kit fabric
# or
yarn add fabric-editor-kit fabric

Peer requirements: React 18+, Fabric.js 6.x, TypeScript 5.x (optional but recommended)


Minimal Setup

TSX

import { useRef } from 'react'
import { FabricKitProvider, ThemeProvider, createTheme } from 'fabric-editor-kit'

export default function App() {
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const theme = createTheme('dark') // or 'light'

  return (
    <ThemeProvider theme={theme}>
      <FabricKitProvider canvasRef={canvasRef} width={800} height={500}>
        {/* Your editor UI here */}
        <canvas ref={canvasRef} />
      </FabricKitProvider>
    </ThemeProvider>
  )
}

JSX

import { useRef } from 'react'
import { FabricKitProvider, ThemeProvider, createTheme } from 'fabric-editor-kit'

export default function App() {
  const canvasRef = useRef(null)
  const theme = createTheme('dark') // or 'light'

  return (
    <ThemeProvider theme={theme}>
      <FabricKitProvider canvasRef={canvasRef} width={800} height={500}>
        {/* Your editor UI here */}
        <canvas ref={canvasRef} />
      </FabricKitProvider>
    </ThemeProvider>
  )
}

Full Editor Setup

TSX

import { useRef, useEffect } from 'react'
import type { RefObject } from 'react'
import { Rect, Circle, Textbox } from 'fabric'
import {
  FabricKitProvider, useFabricKit,
  ThemeProvider, createTheme,
  CanvasToolbar, TextToolbar, ShapeToolbar,
  ImageToolbar, MultiToolbar,
  ContextMenu, useContextMenu,
  LayerPanel, ExportPanel,
} from 'fabric-editor-kit'

function EditorInner({ canvasRef }: { canvasRef: RefObject<HTMLCanvasElement | null> }) {
  const { canvas, selectionType, undo, redo, canUndo, canRedo } = useFabricKit()

  useEffect(() => {
    if (!canvas) return
    canvas.add(
      new Rect({ left: 60, top: 60, width: 160, height: 100, fill: '#6366f1', rx: 8 }),
      new Circle({ left: 280, top: 60, radius: 55, fill: '#0ea5e9' }),
      new Textbox('Double-click to edit', { left: 60, top: 220, fontSize: 20, fill: '#1e293b', width: 240 }),
    )
    canvas.renderAll()
  }, [canvas])

  const { menu, open, close } = useContextMenu({
    canvas, onUndo: undo, onRedo: redo, canUndo, canRedo,
  })

  useEffect(() => {
    if (!canvas) return
    const el = canvas.getElement().parentElement
    if (!el) return
    el.addEventListener('contextmenu', open)
    return () => el.removeEventListener('contextmenu', open)
  }, [canvas, open])

  return (
    <div style={{
      minHeight: '100vh',
      width: '100%',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      backgroundColor: '#f1f5f9',
      padding: '32px',
      boxSizing: 'border-box',
    }}>
      {menu && <ContextMenu x={menu.x} y={menu.y} items={menu.items} onClose={close} />}

      <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', alignItems: 'center' }}>

        {/* Toolbar — centered above canvas */}
        <div style={{ display: 'flex', justifyContent: 'center' }}>
          {selectionType === 'none'  && <CanvasToolbar />}
          {selectionType === 'text'  && <TextToolbar />}
          {selectionType === 'shape' && <ShapeToolbar />}
          {selectionType === 'image' && <ImageToolbar />}
          {selectionType === 'group' && <MultiToolbar />}
          {selectionType === 'multi' && <MultiToolbar />}
        </div>

        {/* Canvas + side panels row */}
        <div style={{ display: 'flex', gap: '16px', alignItems: 'flex-start' }}>

          <div style={{
            borderRadius: '12px',
            overflow: 'hidden',
            border: '1.5px solid #cbd5e1',
            boxShadow: '0 4px 24px 0 rgba(0,0,0,0.10), 0 1.5px 6px 0 rgba(0,0,0,0.07)',
            background: '#ffffff',
            lineHeight: 0,
          }}>
            <canvas ref={canvasRef} style={{ display: 'block' }} />
          </div>

          {/* Side panels */}
          <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
            <LayerPanel width={220} />
            <ExportPanel width={220} filename="my-design" />
          </div>

        </div>
      </div>
    </div>
  )
}

export default function App() {
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const theme = createTheme('dark')

  return (
    <ThemeProvider theme={theme}>
      <FabricKitProvider canvasRef={canvasRef} width={800} height={500} backgroundColor="#ffffff">
        <EditorInner canvasRef={canvasRef} />
      </FabricKitProvider>
    </ThemeProvider>
  )
}

JSX

import { useRef, useEffect } from 'react'
import { Rect, Circle, Textbox } from 'fabric'
import {
  FabricKitProvider, useFabricKit,
  ThemeProvider, createTheme,
  CanvasToolbar, TextToolbar, ShapeToolbar,
  ImageToolbar, MultiToolbar,
  ContextMenu, useContextMenu,
  LayerPanel, ExportPanel,
} from 'fabric-editor-kit'

function EditorInner({ canvasRef }) {
  const { canvas, selectionType, undo, redo, canUndo, canRedo } = useFabricKit()

  useEffect(() => {
    if (!canvas) return
    canvas.add(
      new Rect({ left: 60, top: 60, width: 160, height: 100, fill: '#6366f1', rx: 8 }),
      new Circle({ left: 280, top: 60, radius: 55, fill: '#0ea5e9' }),
      new Textbox('Double-click to edit', { left: 60, top: 220, fontSize: 20, fill: '#1e293b', width: 240 }),
    )
    canvas.renderAll()
  }, [canvas])

  const { menu, open, close } = useContextMenu({
    canvas, onUndo: undo, onRedo: redo, canUndo, canRedo,
  })

  useEffect(() => {
    if (!canvas) return
    const el = canvas.getElement().parentElement
    if (!el) return
    el.addEventListener('contextmenu', open)
    return () => el.removeEventListener('contextmenu', open)
  }, [canvas, open])

  return (
    <div style={{
      minHeight: '100vh',
      width: '100%',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      backgroundColor: '#f1f5f9',
      padding: '32px',
      boxSizing: 'border-box',
    }}>
      {menu && <ContextMenu x={menu.x} y={menu.y} items={menu.items} onClose={close} />}

      <div style={{ display: 'flex', flexDirection: 'column', gap: '12px', alignItems: 'center' }}>
        
        {/* Toolbar — centered above canvas */}
        <div style={{ display: 'flex', justifyContent: 'center' }}>
          {selectionType === 'none'  && <CanvasToolbar />}
          {selectionType === 'text'  && <TextToolbar />}
          {selectionType === 'shape' && <ShapeToolbar />}
          {selectionType === 'image' && <ImageToolbar />}
          {selectionType === 'group' && <MultiToolbar />}
          {selectionType === 'multi' && <MultiToolbar />}
        </div>

        <div style={{ display: 'flex', gap: '16px', alignItems: 'flex-start' }}>
          
          <div style={{
            borderRadius: '12px',
            overflow: 'hidden',
            border: '1.5px solid #cbd5e1',
            boxShadow: '0 4px 24px 0 rgba(0,0,0,0.10), 0 1.5px 6px 0 rgba(0,0,0,0.07)',
            background: '#ffffff',
            lineHeight: 0,
          }}>
            <canvas ref={canvasRef} style={{ display: 'block' }} />
          </div>

          {/* Side panels */}
          <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
            <LayerPanel width={220} />
            <ExportPanel width={220} filename="my-design" />
          </div>
        </div>

      </div>
    </div>
  )
}

export default function App() {
  const canvasRef = useRef(null)
  const theme = createTheme('dark')

  return (
    <ThemeProvider theme={theme}>
      <FabricKitProvider canvasRef={canvasRef} width={800} height={500} backgroundColor="#ffffff">
        <EditorInner canvasRef={canvasRef} />
      </FabricKitProvider>
    </ThemeProvider>
  )
}

Components

<FabricKitProvider />

The root provider. Initializes the Fabric.js canvas and exposes context to all child components.

<FabricKitProvider
  canvasRef={canvasRef}   // RefObject<HTMLCanvasElement>
  width={800}             // Canvas width in px
  height={500}            // Canvas height in px
  backgroundColor="#fff"  // Optional canvas background
>
  {children}
</FabricKitProvider>

Toolbars

All toolbars auto-read from canvas selection state via useFabricKit(). No props required.

<CanvasToolbar />

Shows when nothing is selected. Controls: zoom in/out/reset, undo/redo, clear canvas, background color.

{selectionType === 'none' && <CanvasToolbar />}

<TextToolbar />

Shows when a text/textbox object is selected. Controls: font family, font size, bold, italic, underline, strikethrough, alignment, color, letter spacing, line height, text case.

{selectionType === 'text' && <TextToolbar />}
// Add to existing fonts
{selectionType === 'text' &&
  <TextToolbar
    replaceDefaultFonts // Replace all defaults with only your fonts
    fonts={[
      { label: 'Ubuntu', value: 'Ubuntu' },
    ]}
  />
}

<ShapeToolbar />

Shows when a rect/circle/polygon/path is selected. Controls: fill color, stroke color, stroke width, border radius, opacity, flip H/V, bring to front/send to back.

{selectionType === 'shape' && <ShapeToolbar />}

<ImageToolbar />

Shows when an image object is selected. Controls: brightness, contrast, saturation, blur, grayscale toggle, sepia toggle, opacity, flip H/V, replace image.

{selectionType === 'image' && <ImageToolbar />}

<MultiToolbar />

Shows when multiple objects are drag-selected, or when a group is selected. Controls: align (left/center/right/top/middle/bottom), distribute (horizontal/vertical), group/ungroup, flip H/V, opacity, bring to front/send to back.

{selectionType === 'multi' && <MultiToolbar />}
{selectionType === 'group' && <MultiToolbar />}

<LayerPanel />

Displays all canvas objects as a stacked layer list with full management controls.

<LayerPanel
  width={220}   // Panel width in px (default: 220)
/>

Features:

  • Click row → selects object on canvas
  • Drag row → reorders z-index
  • 👁 Eye icon → toggle visibility
  • 🔒 Lock icon → toggle selectability
  • 🗑 Delete icon → removes from canvas
  • Double-click name → inline rename

<ExportPanel />

Export or import the canvas with a format selector and quality controls.

<ExportPanel
  width={220}            // Panel width in px (default: 220)
  filename="my-design"   // Base filename without extension (default: 'canvas-export')
/>

Supported formats: | Format | Notes | |---|---| | PNG | Lossless, supports transparency. Scale 1×/2×/3× | | JPEG | Lossy, smaller files. Quality slider 10–100% | | SVG | Full vector. Images embedded as base64 | | JSON | Complete canvas state. Re-importable |


<ContextMenu />

Renders a floating context menu at given coordinates.

{menu && (
  <ContextMenu
    x={menu.x}
    y={menu.y}
    items={menu.items}
    onClose={close}
  />
)}

Hooks

useFabricKit()

Access canvas instance and selection state anywhere inside FabricKitProvider.

const {
  canvas,          // Fabric.Canvas instance
  activeObject,    // Currently selected FabricObject | null
  selectedObjects, // Array of selected objects (multi-select)
  selectionType,   // 'none' | 'text' | 'shape' | 'image' | 'group' | 'multi'
  undo,            // () => void
  redo,            // () => void
  canUndo,         // boolean
  canRedo,         // boolean
} = useFabricKit()

Example — add a rectangle programmatically:

import { Rect } from 'fabric'
import { useFabricKit } from 'fabric-editor-kit'

function AddShapeButton() {
  const { canvas } = useFabricKit()

  const addRect = () => {
    if (!canvas) return
    const rect = new Rect({
      left: 100, top: 100,
      width: 200, height: 120,
      fill: '#6366f1',
      rx: 8,
    })
    canvas.add(rect)
    canvas.setActiveObject(rect)
    canvas.renderAll()
  }

  return <button onClick={addRect}>Add Rectangle</button>
}

useContextMenu()

Builds a context-aware right-click menu based on what is selected.

const { menu, open, close } = useContextMenu({
  canvas,
  onUndo: undo,
  onRedo: redo,
  canUndo,
  canRedo,
})

// Attach to canvas wrapper
useEffect(() => {
  if (!canvas) return
  const el = canvas.getElement().parentElement
  if (!el) return
  el.addEventListener('contextmenu', open)
  return () => el.removeEventListener('contextmenu', open)
}, [canvas, open])

Menu items shown:

  • Empty canvas: Undo, Redo, Select All
  • Object selected: Duplicate, Delete, Undo, Redo, Bring to Front, Send to Back
  • Multi-select: + Group option
  • Group selected: + Ungroup option

useExport()

Programmatic export without the UI panel. Use this when you want to wire export to your own buttons.

const {
  exportPNG,   // (multiplier?: number) => void
  exportJPEG,  // (quality?: number, multiplier?: number) => void
  exportSVG,   // () => void
  exportJSON,  // () => void
  importJSON,  // (jsonString: string) => Promise<void>
} = useExport('my-filename')

Example:

import { useFabricKit, useExport } from 'fabric-editor-kit'

function ExportControls() {
  const { canvas } = useFabricKit()
  const { exportPNG, exportJSON, importJSON } = useExport('my-design')

  // Save canvas state to localStorage
  const handleSave = () => {
    if (!canvas) return
    localStorage.setItem('canvas', JSON.stringify(canvas.toJSON()))
  }

  // Restore canvas state from localStorage
  const handleLoad = async () => {
    const saved = localStorage.getItem('canvas')
    if (saved) await importJSON(saved)
  }

  return (
    <div style={{ display: 'flex', gap: '8px' }}>
      <button onClick={() => exportPNG(2)}>Download PNG @2×</button>
      <button onClick={exportJSON}>Export JSON</button>
      <button onClick={handleSave}>Save to localStorage</button>
      <button onClick={handleLoad}>Load from localStorage</button>
    </div>
  )
}

Theming

createTheme(mode)

Create a light or dark theme with one call:

import { createTheme, ThemeProvider } from 'fabric-editor-kit'

const theme = createTheme('dark')  // or 'light'

<ThemeProvider theme={theme}>
  {/* all components inherit the theme */}
</ThemeProvider>

Custom Brand Theme

Override any token to match your product's design system:

import { createTheme } from 'fabric-editor-kit'

const theme = createTheme('light', {
  accentColor:   '#7c3aed',     // Your brand primary color
  accentText:    '#ffffff',     // Text on accent backgrounds
  toolbarBg:     '#1e1e2e',     // Toolbar background
  toolbarBorder: '#2e2e3e',     // Toolbar border
  toolbarRadius: '10px',        // Toolbar border radius
  toolbarShadow: '0 8px 32px rgba(0,0,0,0.24)', // Toolbar shadow
  textPrimary:   '#f8f8f2',     // Primary text
  textSecondary: '#a6adc8',     // Secondary/muted text
  textDisabled:  '#6c7086',     // Disabled state text
  iconColor:     '#cdd6f4',     // Icon color
  bgHover:       '#313244',     // Button hover background
  bgActive:      '#45475a',     // Button active/pressed background
  bgTertiary:    '#181825',     // Input/select background
  inputBg:       '#313244',     // Text input background
  borderDefault: '#45475a',     // Default border
  borderSubtle:  '#313244',     // Subtle/faint border
  dangerColor:   '#f38ba8',     // Danger/delete actions
})

Available Theme Tokens

| Token | Description | |---|---| | accentColor | Primary brand color used on active states and buttons | | accentText | Text color on accent-colored backgrounds | | toolbarBg | Background of all toolbars and panels | | toolbarBorder | Border of all toolbars and panels | | toolbarRadius | Border radius of toolbars and panels | | toolbarShadow | Box shadow of toolbars and panels | | textPrimary | Main text color | | textSecondary | Muted/label text color | | textDisabled | Disabled state text and shortcut hints | | iconColor | Default icon color | | bgHover | Background on mouse hover | | bgActive | Background on active/selected state | | bgTertiary | Input and select backgrounds | | inputBg | Text input field background | | borderDefault | Standard border color | | borderSubtle | Faint separator/divider color | | dangerColor | Delete and destructive action color |


Keyboard Shortcuts

All shortcuts work when the canvas wrapper is focused.

| Shortcut | Action | |---|---| | Ctrl+Z | Undo | | Ctrl+Y / Ctrl+Shift+Z | Redo | | Delete / Backspace | Delete selected object(s) | | Ctrl+D | Duplicate selected | | Ctrl+A | Select all objects | | Ctrl+G | Group selected objects | | Ctrl+Shift+G | Ungroup selected group | | Ctrl+] | Bring to front | | Ctrl+[ | Send to back | | Escape | Deselect / close context menu |


Project Structure

src/
├── core/
│   └── FabricKitProvider.tsx   ← Canvas init, undo/redo, selection tracking
├── hooks/
│   ├── useFabricKit.ts         ← Access canvas context
│   └── useExport.ts            ← Programmatic export
├── theme/
│   ├── ThemeProvider.tsx       ← Theme context
│   ├── createTheme.ts          ← Theme factory function
│   └── tokens.ts               ← Default light/dark tokens
├── toolbars/
│   ├── CanvasToolbar.tsx       ← No-selection toolbar
│   ├── TextToolbar.tsx         ← Text object toolbar
│   ├── ShapeToolbar.tsx        ← Shape object toolbar
│   ├── ImageToolbar.tsx        ← Image object toolbar
│   ├── MultiToolbar.tsx        ← Multi-select toolbar
│   └── shared/                 ← ToolbarButton, ToolbarInput etc.
├── context-menu/
│   ├── ContextMenu.tsx         ← Menu renderer
│   ├── useContextMenu.tsx      ← Menu builder hook
│   └── types.ts                ← ContextMenuItem interface
└── panels/
    ├── LayerPanel.tsx          ← Layer management panel
    └── ExportPanel.tsx         ← Export/import panel

License

MIT © fabric-editor-kit contributors