napkin-sketchdocs 1.0.0 GitHub

The graphic design API: cheatsheet#

On this page

API hub · Reference · Quickstart · Cheatsheet

Reminders for compositions: the page, every element, paint, masks, rendering, and the Node and brand helpers.

The page#

createComposition({ ... })DefaultMeaning
width, height360Page size, in units
units'px'px, in, mm or pt, at 96 pixels to the inch
backgroundnullA CSS color, or null for transparent
id, titlenoneThe root <svg id>, and a <title> for screen readers
useGuiCanvasfalsetrue only inside the app, where paintComposition draws into its canvas

Elements#

MethodNeedsExample
rectx, y, width, height; rx, rydesign.rect({ x: 20, y: 20, width: 120, height: 60, rx: 12, fill: '#326478' })
circlecx, cy, rdesign.circle({ cx: 80, cy: 80, r: 40, fill: '#4cae50' })
ellipsecx, cy, rx, rydesign.ellipse({ cx: 200, cy: 80, rx: 60, ry: 30, fill: '#fdc83a' })
trianglex, y, width, height, variant, or pointsdesign.triangle({ x: 20, y: 20, width: 80, height: 70, variant: 'up' })
polygonpoints, closeddesign.polygon({ points: [{ x: 0, y: 0 }, { x: 40, y: 0 }, { x: 20, y: 30 }] })
polylinepoints, opendesign.polyline({ points, fill: null, stroke: '#1f2328' })
linex1, y1, x2, y2design.line({ x1: 16, y1: 100, x2: 344, y2: 100, stroke: '#4cae50' })
pathd: M L H V C S Q T Zdesign.path({ d: 'M 20 130 C 60 110, 100 170, 140 130', fill: null, stroke: '#dc143c' })
textx, y, textdesign.text({ x: 180, y: 78, text: 'Acme Corp', align: 'center', fontSize: 28 })
imagesrc, x, y, width, height; fitdesign.image({ src: logo, x: 24, y: 24, width: 64, height: 64, fit: 'contain' })
image, linkedlink: true; src is a pathdesign.image({ src: 'assets/logo.svg', link: true, x: 24, y: 24, width: 120, height: 60 })
groupa builder callback, or childrendesign.group({ translate: { x: 40, y: 0 } }, (g) => g.circle({ cx: 40, cy: 40, r: 32 }))

Every element also takes#

PropertyValues
fillA CSS color, a gradient, or null
stroke, strokeWidthA CSS color or null; a width, default 1
fillOpacity, strokeOpacity, opacity0 to 1
fillRulenonzero or evenodd
lineCap, lineJoinbutt, round, square; miter, round, bevel
dash, dashOffset[6, 3]; a number
rotate, scale, translate, originDegrees clockwise; a number or { x, y }; { x, y }; the pivot, default the centre
clipA mask id, or an inline shape
effects[{ type: 'blur', radius: 4 }, { type: 'sepia', amount: 0.8 }], drawn in order
blend'stack' (Multiply), 'sheer' (Screen) or another of the sixteen napkin names
isolateA group's: true mixes its children's blends only with each other
id, name, visibleAn SVG id; data-name; false keeps it out of every render

Text#

PropertyDefaultValues
fontFamily, fontSizea sans stack, 16A CSS family list; a size
fontWeight, fontStylenormalbold or a weight; italic
alignleftleft, center, right, justify
baselinealphabeticalphabetic, top, middle, bottom
maxWidth, lineHeightnone, 1.2The wrap width; the line advance in font sizes
letterSpacing, wordSpacing, indent, paragraphSpacing0Extra space
decoration, transformnoneunderline, line-through; uppercase, lowercase, capitalize

Paint, masks and holes#

WantWrite
A linear gradientfill: { type: 'linear', angle: 90, stops: [{ offset: 0, color: '#ffe08a' }, { offset: 1, color: '#ff8a65' }] }
A radial gradientfill: { type: 'radial', stops: [...] }
A shared maskdesign.defineClip('badge', { type: 'circle', cx: 60, cy: 60, r: 40 }), then clip: 'badge'
A one-off maskclip: { type: 'rect', x: 30, y: 30, width: 60, height: 60 }
A hole in a groupdesign.group({ erase: [{ type: 'circle', cx: 60, cy: 40, r: 14 }] }, (g) => { ... })
A shadoweffects: [{ type: 'drop-shadow', dx: 4, dy: 4, blur: 8, color: 'rgba(0, 0, 0, 0.4)' }]
One shadow for a groupdesign.group({ effects: [{ type: 'drop-shadow', dx: 0, dy: 6, blur: 6, color: '#0006' }] }, (g) => { ... })
A bevel on the fill, a glow round the strokeeffects: [{ type: 'bevel', size: 8, target: 'fill' }, { type: 'outer-glow', target: 'stroke' }]

Composition Styles#

CallGives
validateCompositionStyle(json){ ok, style, errors, notes }: every reason a style is not one, a sentence each
applyCompositionStyle(design, style){ composition, report }: a new composition, restyled by its elements' names
applyCompositionStyle(page, style, { configs }){ sketch, strokes, layers, configs, report }: a page, by its layers' names
captureCompositionStyle(page, 'Day')A page's named layers as a style that, applied again, changes nothing
compositionReportLines(report)What did not match, a line each, with the nearest name

Rendering#

CallGives
design.toSVG({ pretty, precision })SVG text
design.toPNG({ scale })PNG bytes, a Uint8Array
design.rasterize({ scale, decodeImage, resolveLink }){ width, height, data, warnings }
renderComposition(design, { format })Either, by format
renderPng(doc, options){ data, width, height, warnings }
compositionToSvg(doc)SVG text, from a document

Node and brand helpers#

CallDoes
imageDataUrl('assets/logo.png')A file as a data URL, for src
writeComposition(design, 'out', 'card')out/card.svg and out/card.png, from one document
resolveLinkFromDir('./brand')A resolver that reads links inside one folder
parseResources(markdown)A brand's resources.md: { paths, directories, text }
placeBrand(design, box, asset, { fit })A logo into a slot; a vector is inlined
inlineSvg(markup)An SVG's shapes as elements: { viewBox, elements, notes }
brandMark(design, box, { label })A monogram where no asset is configured

Common mistakes#

WrongRightWhy
An SVG logo placed with imageplaceBrand or inlineSvgThe PNG decodes only PNG itself: an SVG image is a hole in it.
Runs of text advanced by measureTextThe font's own metric: 0.6 em a character for monospaceRuns overlap in the SVG otherwise.
Small text at scale: 1toPNG({ scale: 2 }) or 3A stroke under a device pixel is grey mush.
fill: '#32647'fill: '#326478'The PNG render throws on a color it cannot read; the SVG writes it as given.