[{"data":1,"prerenderedAt":113},["ShallowReactive",2],{"products":3,"post:how-to-use-opendraw":100,"product-by-slug:opendraw":108},[4,23,38,60,73,85],{"title":5,"tagline":6,"description":7,"status":8,"kind":9,"order":10,"art":11,"artAlt":12,"platforms":13,"facts":16,"links":20,"slug":11,"path":22},"Fold","Journaling, but cool.","The journaling app for Gen Z. Photos, voice notes and quick thoughts, captured with a tap, a hold, or a swipe.","waitlist","app",1,"fold","A skeleton reading a book in a field of roses",[14,15],"iOS","Android",[17,18,19],"Free","Coming 2026","No account, no cloud",{"waitlist":21},"https:\u002F\u002Ffold.taohq.org","\u002Fproducts\u002Ffold",{"title":24,"tagline":25,"description":26,"status":27,"kind":9,"order":28,"art":29,"artAlt":30,"platforms":31,"facts":33,"links":35,"slug":29,"path":37},"Bottles","Bookmarks and docs, filed properly.","A quiet place for your links, notes and files. No feed, no noise. One space for what you keep, one for what you write.","live",2,"bottles","A skull resting in still water, surrounded by rings of ripples",[32],"Web",[34],"Live now",{"app":36},"https:\u002F\u002Fbottles.taohq.org","\u002Fproducts\u002Fbottles",{"title":39,"tagline":40,"description":41,"status":42,"kind":43,"order":44,"art":39,"artAlt":45,"platforms":46,"facts":51,"links":54,"install":58,"slug":39,"path":59},"opendraw","Hand-drawn diagrams, as one component.","A free, open-source hand-drawn whiteboard for your app. Draw hand-drawn diagrams with shapes, arrows, text and sticky notes, one component for Vue, Nuxt, React and Svelte.","open-source","library",3,"A hand-drawn diagram: a rectangle, an ellipse, an arrow, a sticky note, a diamond and a spark",[47,48,49,50],"Vue","Nuxt","React","Svelte",[52,53],"MIT license","Autosaves to localStorage",{"app":55,"github":56,"npm":57},"https:\u002F\u002Fopendraw.taohq.org","https:\u002F\u002Fgithub.com\u002FTheAlphaOnes\u002Fopendraw","https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@opendrawjs\u002Fnuxt","npm i @opendrawjs\u002Fnuxt","\u002Fproducts\u002Fopendraw",{"title":61,"tagline":62,"description":63,"status":64,"kind":9,"order":65,"art":66,"artAlt":67,"platforms":68,"facts":69,"slug":66,"path":72},"Bubbles.space","Your AI assistant's home.","A personal AI workspace OS built for the way you actually think. Bubbles, your assistant, lives and works alongside you.","in-development",4,"bubbles-space","A small figure sitting under a blossoming tree beneath a full moon",[],[70,71],"Part of the Bubbles family","AI-agent first","\u002Fproducts\u002Fbubbles-space",{"title":74,"tagline":75,"description":76,"status":64,"kind":9,"order":77,"art":78,"artAlt":79,"platforms":80,"facts":82,"slug":78,"path":84},"Bubbles.mail","It reads your inbox so you don't have to.","An intelligent mail client that turns the raw chaos of email into a short report of what matters, what can wait, and what to hand off.",5,"bubbles-mail","A skull crowned with blossoms, pierced by an arrow",[81],"macOS",[70,83],"Native macOS","\u002Fproducts\u002Fbubbles-mail",{"title":86,"tagline":87,"description":88,"status":42,"kind":43,"order":89,"art":86,"artAlt":90,"platforms":91,"facts":93,"links":95,"install":98,"slug":86,"path":99},"web-have-sounds","UI sounds, generated with math.","A zero-dependency JavaScript library for UI sound effects. No audio files, no network requests. Just the Web Audio API.",6,"A crow perched on a skull inside an arched frame",[32,92],"npm",[94],"Zero dependencies",{"github":96,"npm":97},"https:\u002F\u002Fgithub.com\u002FNormVg\u002Fweb-have-sound","https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@thenormvg\u002Fweb-have-sounds","npm i @thenormvg\u002Fweb-have-sounds","\u002Fproducts\u002Fweb-have-sounds",{"title":101,"description":102,"date":103,"author":104,"product":39,"slug":105,"path":106,"readingMinutes":44,"html":107},"How to Use opendraw","From a four-line install to headless mode: props, events, the useOpenDraw() API, custom toolbars and the React and Svelte packages.","2026-10-29","taohq","how-to-use-opendraw","\u002Fwriting\u002Fhow-to-use-opendraw","\u003Cp>opendraw meets you where you are. Four lines gets a working whiteboard; four paragraphs later you&#39;re driving it from code. This is the full path, stop wherever you like.\u003C\u002Fp>\n\u003Ch2>Step 1: the four-line start\u003C\u002Fh2>\n\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#ECE8F3\">npm \u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">install\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\"> @opendrawjs\u002Fnuxt\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#8A8494;font-style:italic\">\u002F\u002F nuxt.config.ts\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B2A0D8\">export\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> default\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> defineNuxtConfig\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">({\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#ECE8F3\">  modules\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">:\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> [\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">'\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">@opendrawjs\u002Fnuxt\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">'\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">],\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">})\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">template\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">  &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">div\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> style\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">height: 100vh\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">    &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">OpenDraw\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">  &#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">div\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">&#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">template\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>The one gotcha: \u003Cstrong>the editor fills its container\u003C\u002Fstrong>, so the container needs a height. Give it a sized div and you&#39;re done — the scene autosaves to \u003Ccode>localStorage\u003C\u002Fcode> and restores on reload.\u003C\u002Fp>\n\u003Cp>Not on Nuxt? Plain Vue 3 imports the same components from \u003Ccode>@opendrawjs\u002Fnuxt\u002Fvue\u003C\u002Fcode>. React and Svelte have their own packages (Step 6).\u003C\u002Fp>\n\u003Ch2>Step 2: learn the keys, not the menus\u003C\u002Fh2>\n\u003Cp>Press \u003Ccode>?\u003C\u002Fcode> inside the editor and it shows you every shortcut. The ones that matter daily: \u003Ccode>V\u003C\u002Fcode> select, \u003Ccode>R\u003C\u002Fcode> rectangle, \u003Ccode>D\u003C\u002Fcode> diamond, \u003Ccode>O\u003C\u002Fcode> ellipse, \u003Ccode>A\u003C\u002Fcode> arrow, \u003Ccode>L\u003C\u002Fcode> line, \u003Ccode>P\u003C\u002Fcode> freedraw, \u003Ccode>T\u003C\u002Fcode> text, \u003Ccode>N\u003C\u002Fcode> sticky note, \u003Ccode>E\u003C\u002Fcode> eraser, \u003Ccode>F\u003C\u002Fcode> frame, \u003Ccode>K\u003C\u002Fcode> laser pointer. Numbers do the same jobs (\u003Ccode>1\u003C\u002Fcode> through \u003Ccode>0\u003C\u002Fcode>) if you prefer keeping a hand on the keyboard.\u003C\u002Fp>\n\u003Cp>Two keys people miss: \u003Ccode>Q\u003C\u002Fcode> locks the active tool so you can draw a dozen rectangles without re-arming the tool each time, and an arrow drawn with the arrow tool \u003Cstrong>binds to shapes\u003C\u002Fstrong> — drag either end onto a shape and the arrow follows that shape around forever.\u003C\u002Fp>\n\u003Ch2>Step 3: tune it with props\u003C\u002Fh2>\n\u003Cp>\u003Ccode>&lt;OpenDraw&gt;\u003C\u002Fcode> is configured, not configured-around:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>theme=&quot;light | dark | system&quot;\u003C\u002Fcode> — starting theme; a persisted theme wins.\u003C\u002Fli>\n\u003Cli>\u003Ccode>ui=&quot;full | minimal | none&quot;\u003C\u002Fcode> — every island, just toolbar and zoom, or a bare canvas.\u003C\u002Fli>\n\u003Cli>\u003Ccode>grid\u003C\u002Fcode> — \u003Ccode>true\u003C\u002Fcode> shows the grid; a number sets the cell size.\u003C\u002Fli>\n\u003Cli>\u003Ccode>initialElements\u003C\u002Fcode> — a scene to open with when nothing is persisted yet.\u003C\u002Fli>\n\u003Cli>\u003Ccode>persist\u003C\u002Fcode> — autosave on by default; pass a string to make it a named key (\u003Ccode>persist=&quot;team-board&quot;\u003C\u002Fcode>), or \u003Ccode>false\u003C\u002Fcode> to turn it off. Changing the key while mounted opens the scene saved under it.\u003C\u002Fli>\n\u003Cli>\u003Ccode>menuLinks\u003C\u002Fcode> — your own links or actions in the main menu.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Events cover the rest: \u003Ccode>@change\u003C\u002Fcode> fires with \u003Ccode>{ elements, appState }\u003C\u002Fcode> on every document change (persist anywhere you like), \u003Ccode>@select\u003C\u002Fcode> with the selected ids, and \u003Ccode>@ready\u003C\u002Fcode> hands you the \u003Ccode>EditorController\u003C\u002Fcode>. There are slots at \u003Ccode>#top-left\u003C\u002Fcode>, \u003Ccode>#top-right\u003C\u002Fcode> and \u003Ccode>#footer\u003C\u002Fcode> for your own buttons.\u003C\u002Fp>\n\u003Ch2>Step 4: drive it from code\u003C\u002Fh2>\n\u003Cp>Any component inside \u003Ccode>&lt;OpenDraw&gt;\u003C\u002Fcode> can call \u003Ccode>useOpenDraw()\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#B2A0D8\">const\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> selectedElements\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">,\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> changeStyle\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">,\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> exportPNG \u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">}\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> useOpenDraw\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#ECE8F3\">changeStyle\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">({\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> strokeColor\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">:\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> '\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">#e03131\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">'\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> })\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#ECE8F3\">exportPNG\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">(\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">'\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">diagram\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">'\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>The composable exposes the whole controller as computed refs: \u003Ccode>elements\u003C\u002Fcode>, \u003Ccode>activeTool\u003C\u002Fcode>, \u003Ccode>canUndo\u003C\u002Fcode>, \u003Ccode>viewport\u003C\u002Fcode>; commands like \u003Ccode>setActiveTool\u003C\u002Fcode>, \u003Ccode>groupSelection\u003C\u002Fcode>, \u003Ccode>alignSelection\u003C\u002Fcode>, \u003Ccode>undo\u003C\u002Fcode>, \u003Ccode>zoomToFitAll\u003C\u002Fcode>; and the file group — \u003Ccode>serialize\u003C\u002Fcode>, \u003Ccode>openFile\u003C\u002Fcode>, \u003Ccode>saveToFile\u003C\u002Fcode>, \u003Ccode>exportPNG\u003C\u002Fcode>, \u003Ccode>exportSVG\u003C\u002Fcode>, \u003Ccode>clearCanvas\u003C\u002Fcode>. Every command is undoable.\u003C\u002Fp>\n\u003Ch2>Step 5: bring your own toolbar\u003C\u002Fh2>\n\u003Cp>When the built-in UI is too much, keep the canvas and draw your own chrome:\u003C\u002Fp>\n\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">script\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> setup\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> lang\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">ts\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B2A0D8\">const\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> ctx \u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> createOpenDrawContext\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#ECE8F3\">provideOpenDraw\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">(\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\">ctx\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">&#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">script\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">template\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">  &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">div\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">board\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">    &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">OpenDrawCanvas\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">    &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">nav\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">my-toolbar\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">      &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">button\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> @click\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">ctx.setActiveTool('rectangle')\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\">Rectangle\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">&#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">button\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">      &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">button\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> :disabled\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">!ctx.canUndo.value\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> @click\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">ctx.undo()\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\">Undo\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">&#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">button\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">    &#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">nav\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">  &#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">div\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">&#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">template\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>The canvas keeps every gesture, shortcut and the text editor. You only draw the buttons — \u003Ccode>ui=&quot;none&quot;\u003C\u002Fcode> plus this pattern is how you make the whiteboard feel native to your product.\u003C\u002Fp>\n\u003Ch2>Step 6: React and Svelte\u003C\u002Fh2>\n\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>\u002F\u002F React\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>import '@opendrawjs\u002Freact\u002Fstyles.css'\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>import { OpenDraw } from '@opendrawjs\u002Freact'\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>&#x3C;OpenDraw onChange={(state) => console.log(state.elements.length)} \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>&#x3C;!-- Svelte 5 -->\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>&#x3C;script lang=\"ts\">\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  import '@opendrawjs\u002Fsvelte\u002Fstyles.css'\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  import { OpenDraw } from '@opendrawjs\u002Fsvelte'\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>&#x3C;\u002Fscript>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>&#x3C;OpenDraw onchange={(state) => console.log(state.elements.length)} \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>Both also ship \u003Ccode>&lt;OpenDrawCanvas&gt;\u003C\u002Fcode> for custom UIs, and reactive state hooks: \u003Ccode>useOpenDrawEditor()\u003C\u002Fcode> and \u003Ccode>useEditorState(editor, (e) =&gt; e.activeTool)\u003C\u002Fcode> on React, \u003Ccode>editorState(editor, getter).current\u003C\u002Fcode> on Svelte.\u003C\u002Fp>\n\u003Ch2>Step 7: many drawings\u003C\u002Fh2>\n\u003Cp>Set \u003Ccode>files: true\u003C\u002Fcode> (or use \u003Ccode>&lt;OpenDrawApp&gt;\u003C\u002Fcode> with \u003Ccode>defineOpenDrawConfig\u003C\u002Fcode>) and the editor becomes a small drawings app: a sidebar to create, open, rename, duplicate, delete and search drawings, each autosaving on its own with a preview, and the open one comes back after a reload. A scene saved before you turned files on becomes the first drawing.\u003C\u002Fp>\n\u003Ch2>Step 8: other frameworks\u003C\u002Fh2>\n\u003Cp>Solid, Angular, Lit, plain JavaScript — same recipe, from \u003Ccode>@opendrawjs\u002Feditor\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#B2A0D8\">import\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> createEditor\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">,\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> mountCanvas \u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">}\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> from\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> '\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">@opendrawjs\u002Feditor\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">'\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B2A0D8\">const\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> editor \u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> createEditor\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B2A0D8\">const\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> session \u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> mountCanvas\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">(\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\">editor\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">,\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> host\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">,\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> staticCanvas\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">,\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> overlayCanvas \u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">})\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#ECE8F3\">editor\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">.\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\">subscribe\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">((\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\">ed\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">)\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> =>\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> render\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">(\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\">ed\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">.\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\">activeTool\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">,\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> ed\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">.\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\">selectedElements\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">,\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> ed\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">.\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\">canUndo\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">))\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#8A8494;font-style:italic\">\u002F\u002F session.dispose() when the host goes away\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>Mount the two stacked canvases, subscribe, render your UI from the controller&#39;s getters. The React and Svelte packages are complete references for how little this actually takes.\u003C\u002Fp>\n\u003Cp>Requirements: Nuxt 4, Vue 3.5, Node 20.19+. The \u003Ca href=\"https:\u002F\u002Fopendraw.taohq.org\">docs\u003C\u002Fa> run all of this live, the \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FTheAlphaOnes\u002Fopendraw\">GitHub\u003C\u002Fa> has the source, and if you want to know what the engine is doing under these APIs, \u003Ca href=\"\u002Fwriting\u002Finside-the-opendraw-engine\">the deep dive is here\u003C\u002Fa>.\u003C\u002Fp>\n",{"title":39,"tagline":40,"description":41,"status":42,"kind":43,"order":44,"art":39,"artAlt":45,"platforms":109,"facts":110,"links":111,"install":58,"slug":39,"path":59,"html":112},[47,48,49,50],[52,53],{"app":55,"github":56,"npm":57},"\u003Cp>\u003Cstrong>opendraw is a free, open-source hand-drawn whiteboard for your app: shapes, arrows, text and sticky notes, rendered sketchy, as one component for Vue, Nuxt, React and Svelte.\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2>What you can draw\u003C\u002Fh2>\n\u003Cp>Rectangles, diamonds and ellipses, arrows that connect, lines, freehand drawing, text, sticky notes and frames. Everything renders sketchy through rough.js, so it looks drawn by hand, because it was. There are tools and shortcuts for everything: every tool has a key, from V for select to K for the laser pointer. Press \u003Ccode>?\u003C\u002Fcode> inside the editor for the full list.\u003C\u002Fp>\n\u003Ch2>One engine, five packages\u003C\u002Fh2>\n\u003Cp>opendraw ships as five packages:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>@opendrawjs\u002Fcore\u003C\u002Fstrong>, the engine: element model, document &amp; history, geometry, hit testing, rendering and export. No framework code.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>@opendrawjs\u002Feditor\u003C\u002Fstrong>, the framework-free editor: tools, gestures, keyboard, the action registry, persistence. Mounts onto any DOM.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>@opendrawjs\u002Fnuxt\u003C\u002Fstrong>, the Vue\u002FNuxt adapter: \u003Ccode>&lt;OpenDraw \u002F&gt;\u003C\u002Fcode>, \u003Ccode>useOpenDraw()\u003C\u002Fcode> and the full editor UI.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>@opendrawjs\u002Freact\u003C\u002Fstrong> and \u003Cstrong>@opendrawjs\u002Fsvelte\u003C\u002Fstrong>, the React and Svelte 5 adapters.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>Getting started\u003C\u002Fh2>\n\u003Cp>Install, register the module, and drop the component anywhere. It fills its container, so give the container a height:\u003C\u002Fp>\n\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#ECE8F3\">npm \u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">install\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\"> @opendrawjs\u002Fnuxt\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#8A8494;font-style:italic\">\u002F\u002F nuxt.config.ts\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B2A0D8\">export\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> default\u003C\u002Fspan>\u003Cspan style=\"color:#ECE8F3\"> defineNuxtConfig\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">({\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#ECE8F3\">  modules\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">:\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> [\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">'\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">@opendrawjs\u002Fnuxt\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">'\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">],\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">})\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cpre class=\"shiki taohq\" style=\"background-color:#201e25;color:#ece8f3\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">template\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">  &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">div\u003C\u002Fspan>\u003Cspan style=\"color:#B2A0D8\"> style\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">=\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">height: 100vh\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">    &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">OpenDraw\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\"> \u002F>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">  &#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">div\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#A39DAD\">&#x3C;\u002F\u003C\u002Fspan>\u003Cspan style=\"color:#C4B5E3\">template\u003C\u002Fspan>\u003Cspan style=\"color:#A39DAD\">>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>The module auto-imports the components and composables. Plain Vue 3 without Nuxt works too: \u003Ccode>import { OpenDraw } from &#39;@opendrawjs\u002Fnuxt\u002Fvue&#39;\u003C\u002Fcode>. The scene saves itself to \u003Ccode>localStorage\u003C\u002Fcode> and restores on reload, and a drawings sidebar keeps many drawings, each saved on its own.\u003C\u002Fp>\n\u003Ch2>Where to Find It\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>Docs and demo\u003C\u002Fstrong>: \u003Ca href=\"https:\u002F\u002Fopendraw.taohq.org\">opendraw.taohq.org\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Cstrong>GitHub\u003C\u002Fstrong>: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002FTheAlphaOnes\u002Fopendraw\">github.com\u002FTheAlphaOnes\u002Fopendraw\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Cstrong>npm\u003C\u002Fstrong>: \u003Ca href=\"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@opendrawjs\u002Fnuxt\">npmjs.com\u002Fpackage\u002F@opendrawjs\u002Fnuxt\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>MIT licensed. If you draw something with it, we&#39;d genuinely love to see it.\u003C\u002Fp>\n",1793498088126]