Build Canva
KINDAreplaces $15/mosaves $180/yrback to the verdict
A browser editor for social graphics: fixed-size artboards, text with real typography, images with focal-point cropping, your own templates as data, a brand kit of colours and fonts, and PNG and PDF export at exactly the pixel dimensions each destination expects.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the build and the export pipeline.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - have readyfree with open licences; commercial families vary widely
Why Export embeds or outlines fonts, which is redistribution. A font licensed for web display is not automatically licensed for that.
Get it Use openly licensed families such as Inter, Source Sans or Public Sans, and keep each licence file alongside the font. If you must use a commercial face, read its embedding clause before Phase 4.
Verify
Each bundled font has its licence file present and permits embedding - have readyfree
Why Phase 3's cropping and downscaling behaviour cannot be judged without them, and these are exactly the cases that produce bad output.
Get it Any photo library. You need one tall portrait with a face near the top and one photograph over ten megapixels.
Verify
Both are saved as fixtures - decidefree
Why Export presets are only useful if they match real destinations, and platform requirements change.
Get it Check the current specification for each destination you publish to, and write the numbers down with the date you checked.
Verify
Each preset's dimensions are recorded with a source and a date
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- artboards(id, doc_id, name, width, height, background) · width and height are pixels, fixed, chosen from a preset - layers(id, artboard_id, kind, z, x, y, w, h, rotation, opacity) · kind is text, image, shape or group - text_props(layer_id, content, font_family, size, weight, line_height, letter_spacing, align, color) - image_props(layer_id, asset_id, fit, focal_x, focal_y) · focal point drives cropping when the frame changes shape - brand(id, name, palette_json, fonts_json, logo_asset_id) - templates(id, name, thumbnail, document_json) · a template is a document with placeholder slots, nothing more
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
VITE_MAX_IMAGE_PX | required | 4000 | Longest edge kept on import. Larger images are downscaled. |
VITE_AUTOFIT_MIN_PT | required | 12 | The smallest font size auto-fit will produce, so text never shrinks into illegibility. |
VITE_FONT_DIR | required | /fonts | Where self-hosted font files are served from. The licence files live beside them. |
VITE_SHARE_ENDPOINT | optional | https://share.example.com | Product phase only. Leave unset to keep everything local, which is a legitimate final state. |
The build, in order
Artboard, layers and one layout engine
You can place and move things on a fixed-size artboard, and the geometry is exact.
One transform from artboard coordinates to screen. Everything else works in artboard space.
Files
src/artboard.tsterminalnpm create vite@latest . -- --template vanilla-ts
Plain serialisable objects. Selection and hover are view state and never stored.
Files
src/document.tsDocument in, positioned boxes out, pure and testable. Both the editor and the exporter call it.
Files
src/layout.ts
done when · tick each as it passeswatch out- Storing positions in screen pixels rather than artboard pixels means every export at a different scale is subtly wrong. Store artboard coordinates only, and treat the display scale as a view concern that never reaches the document.
Typography that behaves
Text wraps, fits and renders the same in the editor as it will in the export.
Wait for the font to load before measuring. Measuring against a fallback and rendering with the real face is a classic source of one-word overflow.
Files
src/text-measure.tsBreak into lines there and hand the editor positioned lines to draw. The editor never calls the browser's own wrapping.
Files
src/layout.tsBinary search the font size until the wrapped height fits the box, with a floor so text never becomes unreadable.
Files
src/autofit.ts
done when · tick each as it passeswatch out- Letting the browser wrap the text in the editor and letting the exporter wrap it separately guarantees they disagree, usually by one word on the last line, and always on the graphic you already published. Wrap once, in the shared layout function, and have the editor render the result rather than compute its own.
Images, cropping and a brand kit
Swapping an image or a brand does not break a design.
Downscale on import to a sensible maximum. A twelve megapixel photo in a social graphic is wasted bytes and a slow editor.
Files
src/assets.tsFit modes of cover and contain, with a draggable focal point used by cover. Default centre, and make it visible.
Files
src/image.tsA colour is either a literal or a reference into the brand palette. References are what make rebranding one action.
Files
src/brand.ts
done when · tick each as it passeswatch out- Centre-cropping a portrait into a wide frame decapitates people with great reliability. Store a focal point per image, default it to the centre, and let the user move it. This is a small feature that prevents most of the bad outputs.
Export at exact dimensions
The exported file is exactly the pixel size the destination requires, and it matches the screen.
Feed the layout result to satori as element structure, then rasterise. Both run identically wherever they run, which is the property you are buying.
Files
src/export.tsterminalnpm install satori @resvg/resvg-js
Named sizes with a test that asserts the output dimensions rather than trusting the arithmetic.
Files
src/presets.tssrc/export.test.tsLoad the font files into the renderer. Record each font's licence and whether embedding is permitted.
Files
docs/fonts.md
done when · tick each as it passeswatch out- Exporting by screenshotting the editor DOM inherits the browser's font rendering, device pixel ratio and any scrollbar, and produces a different file on a different machine. Render from the document through the shared layout function instead, which is why the layout function exists.
Templates as data
A design becomes a reusable template, and filling it takes seconds.
A layer can be declared a slot with a name and a kind. Everything else is fixed design.
Files
src/template.tsApplying a template shows only its slots. This is the difference between a template and a starting point.
Files
src/template.tsA template plus its assets in one file. No server, and shareable by any means you already have.
Files
src/template-io.ts
done when · tick each as it passeswatch out- A template that hard-codes its own text and image assets is a copy, not a template, and updating the original never propagates. Mark slots explicitly and keep the rest as the design, so applying a template is filling in blanks rather than editing someone else's document.
Assets, licensing and sharingproduct builder
Anything you distribute is something you are allowed to distribute.
Source, licence and whether redistribution is permitted, stored with the asset rather than in a separate document that drifts.
Files
src/assets.tsShown at the point of use, not buried. This is the only place it changes behaviour.
Files
src/assets.tsIf you host templates or exports, put a login in front of it.
Files
src/auth.ts
done when · tick each as it passeswatch out- Stock imagery and fonts are the substantial part of what the subscription buys, and their licences are specific about redistribution. A template shipped with a licensed photo inside it is redistribution even when your application is free.
That is the whole plan for Canva. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- The template library, which is the product.
- Stock photo, video and audio libraries.
- Generative image tools and background removal.
- Real-time collaboration and team brand governance.
- template library
- stock/licensed assets
- Magic Studio depth
- collaboration
- brand kits
- print/social integrations
- mobile apps
- A batch mode that fills one template from a spreadsheet row per graphic
- Artboard variants so one design exports at several sizes at once
- A contrast check against the brand palette before export
Need the files? The project pack on the verdict page hands your agent the whole brief · more design.