Getting started
Build your first Waraq editor in minutes.
This guide walks you through assembling a minimal but fully functional design editor.
Minimal editor
"use client"
import {
Waraq,
WaraqBackground,
WaraqStage,
WaraqFrame,
WaraqPanel,
WaraqPane,
WaraqPaneTitle,
WaraqPaneContent,
WaraqToolbar,
WaraqToolbarGroup,
PaneAddLayer,
PaneLayerTree,
ActionToolbarTool,
ActionToolbarHistory,
ActionToolbarZoomGroup,
WaraqKeyboardShortcuts,
ActionPosition,
ActionSize,
ActionFill,
ActionBorder,
ActionCorner,
} from "@codecanon/waraq"
export default function Editor() {
return (
<Waraq>
{/* Canvas background pattern */}
<WaraqBackground variant="dots" />
{/* Top toolbar */}
<WaraqToolbar position="top">
<WaraqToolbarGroup>
<ActionToolbarTool />
</WaraqToolbarGroup>
<WaraqToolbarGroup>
<ActionToolbarHistory />
</WaraqToolbarGroup>
<ActionToolbarZoomGroup />
<WaraqToolbarGroup>
<WaraqKeyboardShortcuts />
</WaraqToolbarGroup>
</WaraqToolbar>
{/* Left panel — layers */}
<WaraqPanel position="left-start">
<WaraqPane>
<WaraqPaneTitle>Add layer</WaraqPaneTitle>
<WaraqPaneContent>
<PaneAddLayer />
</WaraqPaneContent>
</WaraqPane>
<WaraqPane>
<WaraqPaneTitle>Layers</WaraqPaneTitle>
<WaraqPaneContent>
<PaneLayerTree />
</WaraqPaneContent>
</WaraqPane>
</WaraqPanel>
{/* Canvas */}
<WaraqStage>
<WaraqFrame />
</WaraqStage>
{/* Right panel — properties */}
<WaraqPanel position="right-start">
<WaraqPane showFor="layer">
<WaraqPaneTitle>Position & size</WaraqPaneTitle>
<WaraqPaneContent>
<ActionPosition />
<ActionSize />
</WaraqPaneContent>
</WaraqPane>
<WaraqPane showFor="layer">
<WaraqPaneTitle>Appearance</WaraqPaneTitle>
<WaraqPaneContent>
<ActionFill />
<ActionCorner />
<ActionBorder />
</WaraqPaneContent>
</WaraqPane>
</WaraqPanel>
</Waraq>
)
}Persisting state
Pass data and onDataChange to sync the canvas with your own state or storage:
import { createWaraqData, type WaraqData } from "@codecanon/waraq/lib"
import { useState } from "react"
export default function Editor() {
const [data, setData] = useState<WaraqData>(() => createWaraqData())
return (
<Waraq data={data} onDataChange={setData}>
{/* … */}
</Waraq>
)
}WaraqData is plain JSON, so you can serialize it to localStorage, a database, or a URL param.
Loading data from an API
Pass null while your data is in-flight. The editor defers history initialisation until the real value arrives, so undo/redo starts from the correct baseline instead of an empty state:
import { type WaraqData } from "@codecanon/waraq/lib"
import { useEffect, useState } from "react"
export default function Editor() {
const [data, setData] = useState<WaraqData | null>(null)
useEffect(() => {
fetchDesign().then(setData)
}, [])
return (
<Waraq data={data} onDataChange={setData}>
{/* … */}
</Waraq>
)
}
nullvsundefined—nullmeans "controlled but loading".undefined(i.e. omitting the prop) means uncontrolled, and the editor treatsinitialLayersas the permanent starting state. Always usenullwhile waiting for data, neverundefined.
Controlling the editor programmatically
Use the focused hooks inside any component that is a child of <Waraq>. Each hook subscribes only to the state it needs, so components re-render only when relevant state changes:
"use client"
import { useWaraqDocument, useWaraqZoom } from "@codecanon/waraq"
import { Button } from "@codecanon/waraq/ui"
function MyControls() {
const { addLayer, deleteSelected, layers } = useWaraqDocument()
const { zoomFit } = useWaraqZoom()
return (
<div className="flex gap-2">
<Button onClick={() => addLayer("text")}>Add text</Button>
<Button variant="destructive" onClick={deleteSelected}>Delete</Button>
<Button variant="outline" onClick={zoomFit}>Fit canvas</Button>
<span>{layers.length} layers</span>
</div>
)
}Adding custom layer types
See Custom layers for a full guide.
import { Waraq } from "@codecanon/waraq"
import { Type } from "lucide-react"
const myLayerTypes = [
{
id: "badge",
name: "Badge",
icon: <Type size={16} />,
defaultValues: {
value: "New badge",
cssVars: { "--width": "120px", "--height": "40px" },
},
Component: ({ layer }) => (
<div className="flex h-full items-center justify-center rounded-full bg-blue-500 px-4 text-white text-sm font-semibold">
{layer.value}
</div>
),
},
]
export default function Editor() {
return (
<Waraq layerTypes={myLayerTypes}>
{/* … */}
</Waraq>
)
}