Frames
Frames are rectangular zones on a board that enable presentations, slides, and spatial organization. Think PowerPoint meets infinite canvas - combine the structure of slides with the freedom of a whiteboard.
What Are Frames?
A Frame is a rectangular area with defined boundaries on your infinite canvas. Frames allow you to:
- Divide a board into logical sections (introduction, practice, discussion)
- Control viewport navigation for participants (soft/hard locks)
- Create sequential presentations with "Next/Previous" navigation
- Clone frames to set up parallel breakout rooms
- Organize complex content into manageable zones
Visual Concept
┌─────────────────────────────────────────────────────────────────┐
│ INFINITE CANVAS │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ FRAME 1 │ │ FRAME 2 │ │
│ │ "Introduction" │ │ "Practice" │ │
│ │ │ │ │ │
│ │ [Components] │ │ [Components] │ │
│ │ [Objects] │ │ [Objects] │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ FRAME 3 │ │
│ │ "Discussion" │ │
│ │ │ │
│ │ [Components] │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Why Frames?
Problem: Infinite Canvas Chaos
Without frames, collaborative whiteboards face these challenges:
- Lost participants: Students scroll around randomly, can't find the current topic
- No structure: No clear beginning/middle/end to lessons
- Distraction: Students see everything at once, including future content
- No grouping: Can't divide class into parallel workspaces
Solution: Structured Flexibility
Frames provide structure while maintaining the flexibility of an infinite canvas:
- For Teachers: Control what students see and when
- For Students: Clear navigation, reduced cognitive load
- For Presentations: Sequential slides with spatial relationships
- For Group Work: Isolated zones that don't interfere with each other
Key Concepts
Frame Properties
interface Frame {
id: string; // UUID
board_id: string; // Parent board
name: string; // Display name ("Slide 1", "Introduction")
order: number; // Sequence position (0, 1, 2, ...)
bounds_x: number; // X coordinate in pixels
bounds_y: number; // Y coordinate in pixels
bounds_width: number; // Width in pixels (min: 100)
bounds_height: number; // Height in pixels (min: 100)
lock_mode: 'none' | 'soft' | 'hard'; // Viewport control
background_color?: string; // Hex color or null
visibility: 'active' | 'hidden' | 'archived';
parent_frame_id?: string; // For tracking clones
created_at: string; // ISO 8601 timestamp
updated_at: string;
}Lock Modes
Frames support three levels of viewport control:
| Mode | Behavior | Visual Indicator | Use Case |
|---|---|---|---|
| none | Free navigation, no restrictions | None | Open collaboration, free exploration |
| soft | Can escape, but indicator shows "Return to lesson" | Orange border on canvas | Guided work with flexibility |
| hard | Cannot leave frame bounds | Red border, pan/zoom disabled outside | Exams, strict control, focused tasks |
Lock Mode Examples
Soft Lock:
┌───────────────────────────────────────┐
│ ┌─────────────────────────────┐ │
│ │ FRAME: "Practice Exercise" │ │
│ │ Lock: Soft │ │
│ │ │ │
│ │ [Student can work here] │ │
│ │ │ │
│ │ If student scrolls out: │ │
│ │ ⚠️ "You're outside the │ │
│ │ lesson frame" │ │
│ │ [Return to Lesson] │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────────┘Hard Lock:
┌───────────────────────────────────────┐
│ ┌─────────────────────────────┐ │
│ │ FRAME: "Exam Question 1" │ │
│ │ Lock: Hard 🔒 │ │
│ │ │ │
│ │ [Student works here] │ │
│ │ │ │
│ │ Pan/zoom disabled outside │ │
│ │ Scroll attempts ignored │ │
│ │ │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────────┘Frame Visibility
| State | Description | When to Use |
|---|---|---|
| active | Visible to all, included in navigation | Normal frames |
| hidden | Not shown in frame list, but physically present | Temporarily hide content |
| archived | Soft-deleted, excluded by default | Deleted frames (restorable) |
Use Cases
1. Presentation Mode (Slides)
Create sequential slides for lessons or presentations.
// Create 5 slides arranged horizontally
const slides = [
'Introduction',
'Key Concepts',
'Practice Exercise',
'Group Discussion',
'Summary'
];
for (let i = 0; i < slides.length; i++) {
await fetch('https://api.boardapi.io/api/v1/boards/board-uuid/frames', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: slides[i],
order: i,
bounds_x: 1300 * i, // Horizontal layout
bounds_y: 0,
bounds_width: 1200,
bounds_height: 800,
lock_mode: 'soft'
})
});
}Teacher navigation:
// Navigate all students to next frame
socket.emit('frame:navigate', {
frameId: 'frame-uuid',
smooth: true // Smooth scroll animation
});Result:
- Students automatically scroll to the frame
- Teacher panel shows "12/15 on current frame"
- "Next/Previous" buttons for easy navigation
2. Breakout Rooms (Cloned Frames)
Clone a frame to create parallel workspaces for groups.
// Create template frame
const template = await fetch('/api/v1/boards/board-uuid/frames', {
method: 'POST',
headers: { 'X-API-Key': 'your-api-key', 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Group Work Template',
bounds_x: 0,
bounds_y: 0,
bounds_width: 1200,
bounds_height: 1000,
lock_mode: 'soft',
background_color: '#F0F8FF'
})
}).then(r => r.json());
// Clone for 3 groups
const groups = ['Blue Team', 'Red Team', 'Green Team'];
for (let i = 0; i < groups.length; i++) {
await fetch(`/api/v1/boards/board-uuid/frames/${template.id}/clone`, {
method: 'POST',
headers: { 'X-API-Key': 'your-api-key', 'Content-Type': 'application/json' },
body: JSON.stringify({
offsetX: 1300 * (i + 1), // Space groups horizontally
offsetY: 0,
name: groups[i]
})
});
}Result:
┌────────────────────────────────────────────────────────────┐
│ [Template] [Blue Team] [Red Team] [Green Team] │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
│ Each group works in parallel, teacher sees all │
└────────────────────────────────────────────────────────────┘3. Exam Mode (Hard Locks)
Set up isolated exam areas with strict viewport control.
// Create exam frame with hard lock
const examFrame = await fetch('/api/v1/boards/board-uuid/frames', {
method: 'POST',
headers: { 'X-API-Key': 'your-api-key', 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Exam Question 1',
order: 0,
bounds_x: 0,
bounds_y: 0,
bounds_width: 1200,
bounds_height: 800,
lock_mode: 'hard', // Cannot escape
background_color: '#FFF3E0'
})
}).then(r => r.json());
// Navigate all students to exam frame
socket.emit('frame:navigate', {
frameId: examFrame.id,
smooth: false // Instant navigation for exams
});
// Lock is enforced - students cannot scroll outBehavior:
- Students cannot pan/zoom outside frame bounds
- Scroll attempts are blocked
- Viewport locked to frame dimensions
- Teacher can still navigate freely
4. Spatial Organization
Organize complex content into logical zones.
┌──────────────────────────────────────────────────────────┐
│ PROJECT BOARD │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Planning │ │ Design │ │ Develop │ │ Review │ │
│ │ Frame │ │ Frame │ │ Frame │ │ Frame │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
│ │
│ ┌────────────────────────────────┐ │
│ │ Resources Frame │ │
│ │ (Documentation, Links) │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘Integration with Presenter Modes
Frames work seamlessly with Follow Me and Spectate modes.
Follow Me + Frames
When Follow Me is active, frame navigation automatically syncs all followers:
// Teacher has Follow Me enabled
socket.emit('followme:start', {
boardId: 'board-uuid',
allowBreakaway: true
});
// Teacher navigates to frame
socket.emit('frame:navigate', {
boardId: 'board-uuid',
frameId: 'frame-uuid',
animate: true
});
// Result:
// 1. All following students receive frame:navigated event
// 2. Students' viewports scroll to frame bounds
// 3. Subsequent viewport updates keep students synced within frameLearn more about Presenter Modes →
Spectate + Frames
Spectate mode shows which frame a student is currently viewing:
socket.on('spectate:viewport', (data) => {
// Detect frame from student's viewport
const frames = await getFrames(boardId);
const currentFrame = frames.find(frame =>
data.scrollX >= frame.bounds_x &&
data.scrollX <= frame.bounds_x + frame.bounds_width &&
data.scrollY >= frame.bounds_y &&
data.scrollY <= frame.bounds_y + frame.bounds_height
);
if (currentFrame) {
console.log(`Student is in: ${currentFrame.name}`);
// Show in UI: "Spectating Ivan - Frame: Practice Exercise"
} else {
console.log('Student is outside frames');
}
});API Reference
Create Frame
POST /api/v1/boards/{boardId}/frames
Content-Type: application/json
X-API-Key: your-api-key
{
"name": "Introduction",
"order": 0,
"bounds_x": 0,
"bounds_y": 0,
"bounds_width": 1200,
"bounds_height": 800,
"lock_mode": "soft",
"background_color": "#F0F8FF"
}Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"board_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Introduction",
"order": 0,
"bounds_x": 0,
"bounds_y": 0,
"bounds_width": 1200,
"bounds_height": 800,
"lock_mode": "soft",
"background_color": "#F0F8FF",
"visibility": "active",
"created_at": "2025-11-26T10:00:00Z",
"updated_at": "2025-11-26T10:00:00Z"
}List Frames
GET /api/v1/boards/{boardId}/frames
X-API-Key: your-api-keyResponse: Array of frame objects, ordered by order field.
Update Frame
PATCH /api/v1/boards/{boardId}/frames/{frameId}
Content-Type: application/json
X-API-Key: your-api-key
{
"lock_mode": "hard",
"name": "Exam - Question 1"
}Use cases:
- Switch to exam mode (change lock to hard)
- Reorder frames (update order field)
- Resize frames (update bounds)
- Change visibility (hide/show frames)
Clone Frame
POST /api/v1/boards/{boardId}/frames/{frameId}/clone
Content-Type: application/json
X-API-Key: your-api-key
{
"offsetX": 1300,
"offsetY": 0,
"name": "Group 1"
}What gets cloned:
- Frame properties (bounds, lock_mode, background_color)
parent_frame_idset to original frame ID
What does NOT get cloned:
- Objects inside the frame (clone only copies frame structure)
- Frame ID (new UUID generated)
Delete Frame (Soft)
DELETE /api/v1/boards/{boardId}/frames/{frameId}
X-API-Key: your-api-keyBehavior:
- Sets
visibilitytoarchived - Objects inside frame are preserved
- Can be restored by updating visibility to
active
WebSocket Events
Frame Navigation
// Teacher navigates students to frame
socket.emit('frame:navigate', {
boardId: 'board-uuid',
frameId: 'frame-uuid',
smooth: true // Smooth scroll animation
});
// All participants receive:
socket.on('frame:navigated', (data) => {
console.log(`Navigated to: ${data.frameName}`);
// Sync viewport to frame bounds
scrollToFrame(data.frame.bounds_x, data.frame.bounds_y);
});Lock Mode Changes
// Teacher locks frame (real-time)
socket.emit('frame:lock', {
boardId: 'board-uuid',
frameId: 'frame-uuid',
lockMode: 'hard'
});
// Students receive:
socket.on('frame:updated', (data) => {
if (data.lock_mode === 'hard') {
enableHardLock(data.frame.bounds);
}
});Participant Location Tracking
// Student reports their location
socket.emit('participant:location', {
boardId: 'board-uuid',
frameId: 'current-frame-id', // or null if outside
viewportBounds: {
x: 100, y: 50, width: 1920, height: 1080
}
});
// Teacher receives (for dashboard):
socket.on('participant:location', (data) => {
console.log(`${data.participantName} is in ${data.frameName}`);
updateDashboard(data);
});Best Practices
Frame Layout
Horizontal Layout (Slides):
[Frame 1] [Frame 2] [Frame 3] [Frame 4]- Easy "Next/Previous" navigation
- Natural left-to-right progression
- Standard presentation flow
Grid Layout (Topics):
[Frame 1] [Frame 2]
[Frame 3] [Frame 4]- Non-linear navigation
- Related topics grouped spatially
- Allows random access
Vertical Stack (Long Form):
[Frame 1]
[Frame 2]
[Frame 3]- Scroll-based progression
- Good for storytelling
- Mobile-friendly
Frame Sizing
Standard sizes:
- Presentation: 1200x800 (3:2 ratio)
- Widescreen: 1600x900 (16:9 ratio)
- Work area: 1400x1000 (ample space)
- Mobile-friendly: 800x600 (fits small screens)
Responsive tip: Use larger frames (1600x1000) for complex content, smaller frames (1000x700) for simple slides.
Lock Mode Guidelines
| Scenario | Recommended Lock |
|---|---|
| Guided lesson | soft - Allow exploration with gentle guidance |
| Timed exam | hard - Strict control, no escaping |
| Free work | none - No restrictions |
| Practice activity | soft - Students can ask for help outside |
| Breakout rooms | soft or none - Groups navigate freely |
Naming Conventions
Good names:
- "Slide 1: Introduction"
- "Practice: Fill in Blanks"
- "Group Discussion - Topic A"
- "Exam Question 3"
Avoid:
- "Frame 1" (not descriptive)
- "asdlkfjasdf" (meaningless)
- Very long names (truncated in UI)
Performance
- Limit: 50 frames per board (soft limit, performance tested)
- Cloning: Cloning is fast (no objects copied), safe to clone many
- Navigation: Smooth animation adds 300ms, instant navigation for exams
- Updates: Lock mode changes propagate in < 100ms via WebSocket
Examples
Complete Lesson Setup
// Setup a complete lesson with 4 frames
async function createLesson(boardId, apiKey) {
const frames = [
{ name: 'Introduction', lockMode: 'soft', bg: '#E3F2FD' },
{ name: 'Key Concepts', lockMode: 'soft', bg: '#FFF3E0' },
{ name: 'Practice', lockMode: 'soft', bg: '#F1F8E9' },
{ name: 'Summary', lockMode: 'none', bg: '#FCE4EC' }
];
const created = [];
for (let i = 0; i < frames.length; i++) {
const frame = await fetch(
`https://api.boardapi.io/api/v1/boards/${boardId}/frames`,
{
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: frames[i].name,
order: i,
bounds_x: 1300 * i,
bounds_y: 0,
bounds_width: 1200,
bounds_height: 800,
lock_mode: frames[i].lockMode,
background_color: frames[i].bg
})
}
).then(r => r.json());
created.push(frame);
}
return created;
}Breakout Room Workflow
// Complete breakout room setup with navigation
async function setupBreakoutRooms(boardId, templateFrameId, groups) {
const rooms = [];
for (let i = 0; i < groups.length; i++) {
// Clone frame
const room = await fetch(
`/api/v1/boards/${boardId}/frames/${templateFrameId}/clone`,
{
method: 'POST',
headers: {
'X-API-Key': 'your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
offsetX: 1300 * (i + 1),
offsetY: 0,
name: groups[i].name
})
}
).then(r => r.json());
rooms.push(room);
// Navigate students to their rooms
for (const studentSocket of groups[i].students) {
studentSocket.emit('frame:navigate', {
boardId: boardId,
frameId: room.id,
smooth: true
});
}
}
console.log(`Created ${rooms.length} breakout rooms`);
return rooms;
}
// Usage:
const groups = [
{ name: 'Blue Team', students: [socket1, socket2] },
{ name: 'Red Team', students: [socket3, socket4] },
{ name: 'Green Team', students: [socket5, socket6] }
];
await setupBreakoutRooms('board-uuid', 'template-frame-id', groups);Common Issues
Students not seeing frame navigation
Check:
- Students are connected to WebSocket
frame:navigateevent was broadcast (not direct message)- Students' clients are listening to
frame:navigatedevent - Frame ID is valid and visible
Lock mode not working
Check:
- Frame lock_mode is set correctly (GET frame to verify)
- Client is enforcing lock (check viewport controller)
- Lock mode change was broadcast via WebSocket
- Students received
frame:updatedevent
Cloned frames overlapping
Solution: Use consistent offset spacing:
const FRAME_SPACING = 1300; // Frame width (1200) + gap (100)
const offsetX = FRAME_SPACING * cloneIndex;Frame performance degradation
Solutions:
- Keep frame count under 50
- Archive unused frames (DELETE endpoint)
- Don't nest frames (not supported)
- Use simple background colors (not images)
Quota & Limits
| Plan | Max Frames/Board | Clone Operations/Day | Frame Navigation Events/Day |
|---|---|---|---|
| Starter | 20 | 100 | 1,000 |
| Professional | 50 | 500 | 10,000 |
| Business | 100 | 2,000 | 50,000 |
| Enterprise | 500 | Unlimited | Unlimited |
Next Steps
- Frames API Reference - Complete endpoint documentation
- Student Zones - Combine with zones for assignments
- Presenter Modes - Follow Me + Frames for presentations
- WebSocket API - Real-time frame events
Ready to use Frames? View Quick Start →