Markasso is a fast, minimal, keyboard-first whiteboard engine that runs entirely in the browser.
Built with vanilla TypeScript and the Canvas 2D API — no framework dependencies. Just you, a canvas, and JavaScript doing exactly what it was invented to do.
This repository is the source of truth for the production deployment.
While Excalidraw excels at freehand sketching and Draw.io offers comprehensive diagramming, Markasso occupies a different niche: smaller, faster, and requiring no sign-in, cookies, or workspace creation.
Markasso was born from a simple frustration: wanting to sketch a quick diagram shouldn't require:
- Waiting for a heavy application to load
- Dismissing cookie consent banners
- Creating an account for "free" features
- Risking lost work due to forgotten exports
Markasso is the alternative. Open, draw, export, done.
| Tool |
Shortcut |
| Hand |
H / Space |
| Select |
V / 1 |
| Rectangle |
R / 2 |
| Rhombus (Diamond) |
D / 3 |
| Ellipse |
E / 4 |
| Line + arrowheads |
A / L / 5 |
| Curve (bezier) |
C |
| Polygon / Polyline |
O |
| Pen (freehand) |
P / 7 |
| Text |
T / 8 |
| Eraser |
0 |
| Feature |
Description |
| Infinite canvas |
Pan with middle-click or Alt+drag; zoom with Ctrl+scroll |
| Millimeter grid |
Dot · Line · Graph-paper modes (real mm at 96 DPI) |
| Navigation recovery |
F fits all content into view · Shift+0 resets to origin |
| Dark / Light theme |
Switchable themes with system preference support |
| Feature |
Description |
| Resize handles |
8-handle bounding box on every element type |
| Rotation |
Rotate any element via a handle above the selection box (fully undoable) |
| Endpoint editing |
Drag individual endpoints of lines and arrows independently |
| Shift+click multi-select |
Add or remove elements from selection without a marquee |
| Arrow key nudge |
Move selected elements 1px (or 10px with Shift) |
| Lock elements |
Lock any element to prevent selection, movement, or deletion |
| Feature |
Description |
| Smart arrow links |
Connect arrows to shapes — arrows attach to the border (not center), facing each other |
| Hover preview |
Hover to preview connection points before drawing |
| Cascade delete |
Deleting a shape removes all connected arrows and lines |
| Feature |
Description |
| Groups |
Ctrl+G to group · click selects all members · click again enters group for individual editing |
| Shape labels |
Double-click any rectangle or ellipse to type a label, clipped inside the shape |
| Connector labels |
Add labels to lines with arrowheads and keep the gap aligned to the bezier bend |
| Text scaling |
Dragging a text handle scales the font size, not just the box |
| Double-click to edit |
Open any existing text element for inline editing |
| Feature |
Description |
| Shift constraints |
Rectangle/Ellipse → square/circle · Line/Arrow → 45° snap · Resize → keep aspect ratio |
| Hover highlight |
Elements highlight on hover — always know what you're about to select |
| Eraser hover |
Elements highlight as you drag the eraser over them |
| Feature |
Description |
| Floating glass UI |
Excalidraw-style islands: lock · hand · tools · eraser, top-right import + export; mobile gets a compact bottom-right action bar |
| Properties panel |
Stroke color, fill color, stroke width, stroke style, linecap icons, opacity, roughness, shadow, rounded corners; font size + family + bold/italic/underline/strikethrough for text |
| Tool lock |
Lock button keeps the active drawing tool after placing a shape instead of reverting to Select |
| Panel toggle |
\ (backslash) shows/hides all UI panels for a distraction-free canvas |
| Command palette |
Ctrl+K — fuzzy-search and run any action without touching the mouse |
| Element search |
Ctrl+F — search elements by label/content, click to pan and select |
| Minimap |
Collapsible canvas overview; click or drag to pan the viewport |
| Share link |
Encode the full scene into a URL hash for instant one-click sharing |
| Keyboard navigation |
Full Tab-based keyboard navigation through all panels |
| About modal |
Info panel with version, links, and credits |
| Feature |
Description |
| Session persistence |
Auto-saved to localStorage — your work survives page refreshes |
.markasso format |
Save and reload your full scene as a .markasso file (JSON) — images included |
| Image import |
Drag-and-drop, file picker, or Ctrl+V paste; .markasso files can also be dropped directly |
| Mermaid import |
Import .mmd / .mermaid files via drag-and-drop or the toolbar button; paste Mermaid text from the clipboard — graph, flowchart, and sequenceDiagram are converted to native elements |
| Export PNG / SVG / HTML |
Download as 2× PNG, clean SVG, or standalone HTML — all respect the current canvas background color |
| Share link |
One-click URL encoding of the full scene — no server, no account |
| Feature |
Description |
| Undo / Redo |
Full command history with Ctrl+Z / Ctrl+Y or Ctrl+Shift+Z |
| Feature |
Description |
| Zero dependencies |
Browser Canvas 2D API only — no React, no framework, no bundled megabytes |
pnpm dev # Start dev server at http://localhost:5173
pnpm build # Type-check and bundle → dist/
pnpm typecheck # TypeScript validation
pnpm test # Run Vitest unit tests
No .env files. No API keys. No containerization required.
| Key |
Action |
H / Space |
Hand (pan) |
V / 1 |
Select |
R / 2 |
Rectangle |
D / 3 |
Rhombus (Diamond) |
E / 4 |
Ellipse |
A / L / 5 |
Line + arrowheads |
C |
Curve |
O |
Polygon |
P / 7 |
Pen (freehand) |
T / 8 |
Text |
N |
Sticky note |
0 |
Eraser |
| Key |
Action |
G |
Toggle grid |
F |
Fit all content into view |
Shift+0 |
Reset viewport to origin |
\ |
Toggle all UI panels |
Scroll |
Pan |
Middle-click drag / Alt+drag |
Pan |
Ctrl+scroll |
Zoom to cursor |
| Key |
Action |
Ctrl+K |
Open command palette |
Ctrl+F |
Open element search |
| Key |
Action |
Esc |
Cancel / exit group / deselect |
Delete / Backspace |
Delete selected elements |
Arrow keys |
Nudge selection 1px |
Shift+Arrow |
Nudge selection 10px |
Ctrl+A |
Select all |
Ctrl+D |
Duplicate selection |
Ctrl+G |
Group selected elements |
Ctrl+Shift+G |
Ungroup |
Ctrl+Shift+] |
Bring to front |
Ctrl+Shift+[ |
Send to back |
Ctrl+Z |
Undo |
Ctrl+Y / Ctrl+Shift+Z |
Redo |
Shift+click |
Add / remove from selection |
| Key |
Action |
Shift (while drawing) |
Constrain proportions / snap angle |
Shift (while resizing) |
Lock aspect ratio |
| Double-click on text |
Edit text in place |
| Double-click on rect / ellipse |
Edit shape label |
| Text tool |
Click to place · drag to define area · Enter = commit · Shift+Enter = newline · Esc = cancel |
Markasso follows a Redux-style unidirectional data flow — no mutable state, no event spaghetti, no surprises.
User event
│
▼
Tool (onMouseDown / onMouseMove / onMouseUp)
│ dispatches Command
▼
History.dispatch(command)
│ calls
▼
reducer(Scene, Command) → Scene ← pure function, no side effects
│ notifies
▼
Subscribers (toolbar, properties panel, canvas view)
│
▼
render(ctx, scene, canvas) ← called every requestAnimationFrame
- All coordinates in CSS pixels. The canvas buffer is
clientWidth × devicePixelRatio for sharpness, but all viewport offsetX/Y, element positions, and mouse events live in CSS pixel space.
- Immutable scene. Every
Scene object is never mutated. The reducer returns a new reference or the same reference if nothing changed (enabling cheap equality checks).
- Ephemeral commands (pan, zoom, set-viewport, select, tool change) are excluded from the undo stack.
APPLY_STYLE is the single undoable command that updates both appState defaults and all currently selected elements in one atomic operation.
src/
├── core/
│ ├── scene.ts # Scene interface + factory
│ ├── viewport.ts # Pan/zoom math (screenToWorld, worldToScreen)
│ └── app_state.ts # AppState (activeTool, colors, grid, …)
├── elements/
│ └── element.ts # Discriminated union for all element types
├── commands/
│ └── commands.ts # Full Command discriminated union
├── engine/
│ ├── reducer.ts # Pure (Scene, Command) → Scene
│ └── history.ts # Undo/redo stack + pub/sub
├── io/
│ ├── markasso.ts # .markasso save / load (exportMarkasso, importMarkasso)
│ ├── mermaid.ts # Mermaid parser + layout engine → Markasso elements
│ ├── session.ts # localStorage auto-save / restore + quota warning toast
│ └── share.ts # URL-hash scene encoding / decoding for share links
├── rendering/
│ ├── renderer.ts # rAF render loop entry point
│ ├── draw_element.ts # Per-type draw dispatch (with rotation + shape labels)
│ ├── draw_grid.ts # Dot / line / mm graph-paper grids
│ ├── draw_selection.ts # Selection box, resize/rotation/endpoint handles + hit testing
│ └── export.ts # exportPNG / exportSVG / exportHTML (bounding-box auto-fit)
├── tools/
│ ├── tool.ts # Tool interface
│ ├── select_tool.ts # Hit test, marquee, drag-move, resize
│ ├── rectangle_tool.ts
│ ├── ellipse_tool.ts
│ ├── rombo_tool.ts # Rhombus tool
│ ├── line_tool.ts # Line tool; arrowheads are a style on line elements
│ ├── pen_tool.ts # Freehand with Catmull-Rom smoothing + stylus pressure
│ ├── text_tool.ts # Invisible overlay textarea + in-place editing
│ └── sticky_tool.ts # Sticky note with editable text and color picker
└── ui/
├── canvas_view.ts # DOM event hub → world coords → tool
├── toolbar.ts # Floating islands: tools · undo/zoom · import · export · share
├── context_panel.ts # Properties panel (style, formatting, shadow, alignment)
├── command_palette.ts # Ctrl+K fuzzy-search command launcher
├── element_search.ts # Ctrl+F canvas element search overlay
├── minimap.ts # Collapsible canvas overview + viewport panning
├── image_import.ts # File picker, drag-and-drop, paste — images + .markasso
├── settings.ts # Hamburger panel: grid, accent color, theme, language
├── mobile_action_bar.ts # Compact bottom-right action bar for touch devices
└── shortcuts.ts # Global keyboard map
| Mode |
Description |
| Dot |
Subtle dots at configurable world-unit spacing |
| Line |
Horizontal + vertical lines |
| mm |
Three-tier graph paper (1 mm / 5 mm / 10 mm) at physical scale assuming 96 DPI |
| Concern |
Choice |
Rationale |
| Language |
TypeScript 5 |
Exhaustive switch on discriminated unions catches unhandled commands at compile time |
| Build |
Vite |
Zero-config, instant HMR, single-file output |
| Rendering |
Canvas 2D API |
Direct pixel control without virtual DOM overhead |
| Testing |
Vitest |
Runs in Node — no browser needed for reducer/viewport math |
| Dependencies |
None |
crypto.randomUUID(), requestAnimationFrame, ResizeObserver — all native |
See CONTRIBUTORS.md for guidelines.
MIT — © 2026 Alberto Barrago
Draw things. Ship things. Touch grass.