# Vue

> Use Fabric.js with Vue 3: keep the canvas out of deep reactivity with shallowRef and toRaw, and drive your toolbar from createDocumentStateStore.

Source: https://fabricjs-document-engine.jscrate.dev/docs/frameworks/vue
Last updated: 2026-09-28

Fabric.js Vue 3 apps have one classic bug: putting the Fabric.js canvas in a deep `ref`. Vue wraps it in a reactive proxy, and Fabric.js then fails in strange ways, such as `loadFromJSON` not rendering. Keep the canvas and the engine in `shallowRef` or plain variables, and use `createDocumentStateStore` for the toolbar state.

## The component

```vue
<script setup lang="ts">
import { Canvas, Rect } from "fabric";
import {
  createDocumentEngine,
  createDocumentStateStore,
  type DocumentEngine,
  type DocumentState,
} from "fabricjs-document-engine";
import { onBeforeUnmount, onMounted, shallowRef, useTemplateRef } from "vue";

const element = useTemplateRef<HTMLCanvasElement>("element");
const engine = shallowRef<DocumentEngine>();
const state = shallowRef<DocumentState>();
let cleanup = () => {};

onMounted(() => {
  const canvas = new Canvas(element.value!, { width: 800, height: 500 });
  const created = createDocumentEngine({ canvas });
  const store = createDocumentStateStore(created);

  state.value = store.getSnapshot();
  const unsubscribe = store.subscribe(() => {
    state.value = store.getSnapshot();
  });
  engine.value = created;

  cleanup = () => {
    unsubscribe();
    store.destroy();
    created.destroy();
    canvas.dispose();
  };
});

onBeforeUnmount(() => cleanup());

function addSquare() {
  engine.value?.canvas.add(
    new Rect({ width: 80, height: 60, fill: "#2a9d8f" })
  );
}
</script>

<template>
  <div role="toolbar">
    <button @click="addSquare">Square</button>
    <button :disabled="!state?.canUndo" @click="engine?.undo()">
      Undo {{ state?.undoLabel }}
    </button>
    <button :disabled="!state?.canRedo" @click="engine?.redo()">Redo</button>
  </div>
  <canvas ref="element" aria-label="Drawing canvas" />
</template>
```

## Why shallowRef

`shallowRef` only tracks when you replace `.value`. It never wraps the Fabric.js objects inside, so Fabric.js sees the real objects. The `state` snapshot is a new object on each change, so `shallowRef` still updates the template.

## When you already have a reactive canvas

If a Fabric.js canvas ended up inside `reactive()` or a deep `ref`, pass the raw object with `toRaw`:

```ts
import { toRaw } from "vue";

const engine = createDocumentEngine({ canvas: toRaw(canvasRef.value) });
```

The same applies in Pinia stores: keep Fabric.js objects out of store state, or mark them with `markRaw`.

The rest of the Fabric.js Vue 3 setup is the same as in [plain JavaScript](https://fabricjs-document-engine.jscrate.dev/docs/frameworks/vanilla-js). For saving, add a [storage adapter](https://fabricjs-document-engine.jscrate.dev/docs/storage/storage-adapters).
