Skip to content

User Interface

Woven Canvas provides a complete UI out of the box — toolbar, floating menu, user presence, and more. Every part can be customized or replaced using slots.

┌─────────────────────────────────────────────────────────────┐
│[Back to Content] [Offline Indicator] [User Presence]│
│ │
│ │
│ ┌───────────────┐ │
│ ^ │ Floating Menu │ │
│ cursors └───────────────┘ │
│ ┌───────────────┐ │
│ │ Selection │ │
│ │ │ │
│ └───────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Toolbar │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Replace the entire toolbar with your own:

<script setup lang="ts">
import {
WovenCanvas,
SelectTool,
HandTool,
ShapeTool,
Toolbar,
} from "@woven-canvas/vue";
</script>
<template>
<WovenCanvas>
<template #toolbar>
<Toolbar>
<SelectTool />
<HandTool />
<ShapeTool />
<MyCustomTool />
</Toolbar>
</template>
</WovenCanvas>
</template>

Import and use them directly:

import {
SelectTool,
HandTool,
ShapeTool,
TextTool,
StickyNoteTool,
ImageTool,
PenTool,
EraserTool,
ElbowArrowTool,
} from "@woven-canvas/vue";

When blocks are selected, the menu computes common components across all selected blocks. It shows buttons for each common component (e.g., color, text, shape) and positions itself above the selection bounds. The menu hides during drag operations.

Add buttons for your components:

<template>
<WovenCanvas>
<template #floating-menu>
<FloatingMenuBar>
<!-- Add a button for your custom component -->
<template #button:task-data="{ entityIds }">
<TaskPriorityButton :entity-ids="entityIds" />
</template>
</FloatingMenuBar>
</template>
</WovenCanvas>
</template>

Override built-in buttons:

<template>
<FloatingMenuBar>
<!-- Replace the color button -->
<template #button:color="{ entityIds }">
<MyColorButton :entity-ids="entityIds" />
</template>
<!-- Hide shape buttons -->
<template #button:shape />
</FloatingMenuBar>
</template>
SlotComponentsDescription
button:colorcolorFill color picker
button:shapeshapeShape kind, fill, stroke
button:texttextFont, size, bold, italic, etc.
button:penStrokepenStrokeStroke thickness
button:arrowThicknesselbowArrowLine thickness
button:arrowHeadStartelbowArrowStart arrow style
button:arrowHeadEndelbowArrowEnd arrow style

Shows avatars of connected users in multiplayer mode.

<template>
<WovenCanvas>
<template #user-presence="{ users }">
<div class="my-presence">
<img
v-for="user in users"
:key="user.sessionId"
:src="user.avatar"
:style="{ borderColor: user.color }"
/>
</div>
</template>
</WovenCanvas>
</template>

Shows other users’ cursor positions in real-time. The slot provides camera data for positioning:

<template>
<WovenCanvas>
<template #user-cursors="{ users, currentSessionId, camera }">
<div
v-for="u in users"
:key="u.sessionId"
v-show="u.sessionId !== currentSessionId"
:style="{
position: 'absolute',
left: `${(u.cursorX - camera.left) * camera.zoom}px`,
top: `${(u.cursorY - camera.top) * camera.zoom}px`,
}"
>
<MyCursorIcon :color="u.color" :name="u.name" />
</div>
</template>
</WovenCanvas>
</template>

Choose between grid, dots, or a custom background:

<template>
<!-- Grid background -->
<WovenCanvas :background="{ kind: 'grid' }" />
<!-- Dots background -->
<WovenCanvas :background="{ kind: 'dots' }" />
<!-- Custom background -->
<WovenCanvas>
<template #background>
<div class="my-background" />
</template>
</WovenCanvas>
</template>
SlotPropsDescription
loading{ isLoading }Loading overlay
offline-indicator{ isOnline }Offline status banner
version-mismatch{ versionMismatch }Protocol version warning
back-to-contentButton to pan back to content
file-drop-zoneDrag-drop zone for files

Example:

<template>
<WovenCanvas>
<template #offline-indicator="{ isOnline }">
<div v-if="!isOnline" class="offline-banner">Working offline</div>
</template>
<template #loading="{ isLoading }">
<div v-if="isLoading" class="custom-loader">Loading...</div>
</template>
<!-- Disable file drag-drop -->
<template #file-drop-zone />
</WovenCanvas>
</template>

Woven Canvas uses CSS custom properties for styling. Override them to match your design:

.wov-root {
--wov-primary: #3b82f6;
--wov-primary-light: #60a5fa;
--wov-gray-100: #f3f4f6;
--wov-gray-600: #4b5563;
--wov-gray-700: #374151;
--wov-menu-border-radius: 8px;
}

See theme.css for the full list of variables.