Build Canva

KINDAreplaces $15/mosaves $180/yrback to the verdict

0%0 of 19 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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.

estimated effort multi-daythe files for this build are in the project pack

RuntimeVite with TypeScript, no frameworkRenderingDOM and CSS for the editor, with one layout engine shared by editor and exportExportsatori to turn the layout into SVG, then resvg to rasteriseFontsSelf-hosted font files with the licence recordedStorageIndexedDB for documents, with JSON import and export

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. 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

  2. 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

  3. 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

  4. 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.

VariableNeededExampleWhere the value comes from
VITE_MAX_IMAGE_PXrequired4000Longest edge kept on import. Larger images are downscaled.
VITE_AUTOFIT_MIN_PTrequired12The smallest font size auto-fit will produce, so text never shrinks into illegibility.
VITE_FONT_DIRrequired/fontsWhere self-hosted font files are served from. The licence files live beside them.
VITE_SHARE_ENDPOINToptionalhttps://share.example.comProduct phase only. Leave unset to keep everything local, which is a legitimate final state.

The build, in order

  1. Artboard, layers and one layout engine

    You can place and move things on a fixed-size artboard, and the geometry is exact.

    1. One transform from artboard coordinates to screen. Everything else works in artboard space.

      Files src/artboard.ts

      terminal
      npm create vite@latest . -- --template vanilla-ts
    2. Plain serialisable objects. Selection and hover are view state and never stored.

      Files src/document.ts

    3. Document 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 passes
    watch 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.
  2. Typography that behaves

    Text wraps, fits and renders the same in the editor as it will in the export.

    1. 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.ts

    2. Break into lines there and hand the editor positioned lines to draw. The editor never calls the browser's own wrapping.

      Files src/layout.ts

    3. Binary 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 passes
    watch 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.
  3. Images, cropping and a brand kit

    Swapping an image or a brand does not break a design.

    1. 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.ts

    2. Fit modes of cover and contain, with a draggable focal point used by cover. Default centre, and make it visible.

      Files src/image.ts

    3. A 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 passes
    watch 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.
  4. Export at exact dimensions

    The exported file is exactly the pixel size the destination requires, and it matches the screen.

    1. 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.ts

      terminal
      npm install satori @resvg/resvg-js
    2. Named sizes with a test that asserts the output dimensions rather than trusting the arithmetic.

      Files src/presets.tssrc/export.test.ts

    3. Load 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 passes
    watch 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.
  5. Templates as data

    A design becomes a reusable template, and filling it takes seconds.

    1. A layer can be declared a slot with a name and a kind. Everything else is fixed design.

      Files src/template.ts

    2. Applying a template shows only its slots. This is the difference between a template and a starting point.

      Files src/template.ts

    3. A 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 passes
    watch 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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more design.