Kimo
Docs
v1.0
Changeloggetkimo.design
Docs
v1.0
File format spec
§7

Paints, strokes and effects

On this page3 sections
  1. 7.1 Paint
  2. 7.2 Stroke
  3. 7.3 Effects
Status
Stable (format version 1.0.0, frozen 2026-10). Backward compatible within 1.x (§16.4). Supersedes the draft v0.1, kept as a historical record; 0.1 files open through the 0.1 → 1.0 migration (§16.2).
Schema
https://getkimo.design/schema/kimo-1.0.json (JSON Schema draft 2020-12), generated into packages/core-schema/schema/kimo-1.0.schema.json. The 0.1 schema stays published at …/kimo-0.1.json (schema/kimo-0.1.schema.json, frozen).
Reference
@kimo/core-schema (packages/core-schema). Where this document and the implementation disagree, that is a bug in one of them; file an issue.
Plan of record
docs/teardown.md §10. Every place where this spec resolves an ambiguity in, or deviates from, that section is listed in §20.
License
MIT

7.1 Paint

A Paint is a Color (hex, OKLCH, P3 or Ref — a solid paint) or one of:

kindFieldsDefaults
"solid"color req, blend, visibleblend: "normal", visible: true
"linear" | "radial" | "angular"stops req: ≥ 2 × { color, at ∈ [0,1] }; center: [x, y] normalized; size: [w, h] normalized (for linear, h is the length); rotation: degrees CCW, 0 = up; interpolation: "srgb" | "oklab"; opacity: Num [0,1]; blend; visiblecenter [0.5,0.5], size [1,1], rotation 0, interpolation "srgb", opacity 1, blend "normal", visible true
"image"src req: asset reference; fit: cover contain stretch tile crop; crop: affine [a, b, c, d, tx, ty] in normalized image space; opacity; blend; visiblefit "cover", opacity 1, blend "normal", visible true
"mesh"cols req, rows req (integers ≥ 1); colors req: Color[]; points req: arrayFeature fill/mesh. colors and points are row-major with exactly cols × rows entries; each points entry is opaque in 1.0: readers store and round-trip it and are not required to render mesh paints (§20 row 29). Informative (§20 row 33): Kimo renders entries [x, y] or {position: [x, y], leftHandle?, rightHandle?, topHandle?, bottomHandle?} (normalized to the node box; handles are offsets from the position, a missing one mirrors its opposite or is automatic) as pen.dev mesh_gradient: Coons patches with Catmull-Rom auto handles, corner colors blended with smoothstep weights in sRGB.
"shader"src req: relative path; uniforms: record name → anyFeature fill/shader. The shader protocol is not defined by 1.0: readers store and round-trip src and uniforms and are not required to render shader paints (§20 row 29). Informative (§20 row 33): Kimo runs src as a pen.dev WebGL 1.0 (GLSL ES 1.00) fragment shader with annotated uniforms (@resolution, @time, @mouse, @color, @default, @range/@min/@max, @label); uniforms override them by name (number, boolean, #RRGGBB[AA], 2–4 numbers, or a binding).
"solid"
Fields color req, blend, visible
Defaults blend: "normal", visible: true
"linear" | "radial" | "angular"
Fields stops req: ≥ 2 × { color, at ∈ [0,1] }; center: [x, y] normalized; size: [w, h] normalized (for linear, h is the length); rotation: degrees CCW, 0 = up; interpolation: "srgb" | "oklab"; opacity: Num [0,1]; blend; visible
Defaults center [0.5,0.5], size [1,1], rotation 0, interpolation "srgb", opacity 1, blend "normal", visible true
"image"
Fields src req: asset reference; fit: cover contain stretch tile crop; crop: affine [a, b, c, d, tx, ty] in normalized image space; opacity; blend; visible
Defaults fit "cover", opacity 1, blend "normal", visible true
"mesh"
Fields cols req, rows req (integers ≥ 1); colors req: Color[]; points req: array
Defaults Feature fill/mesh. colors and points are row-major with exactly cols × rows entries; each points entry is opaque in 1.0: readers store and round-trip it and are not required to render mesh paints (§20 row 29). Informative (§20 row 33): Kimo renders entries [x, y] or {position: [x, y], leftHandle?, rightHandle?, topHandle?, bottomHandle?} (normalized to the node box; handles are offsets from the position, a missing one mirrors its opposite or is automatic) as pen.dev mesh_gradient: Coons patches with Catmull-Rom auto handles, corner colors blended with smoothstep weights in sRGB.
"shader"
Fields src req: relative path; uniforms: record name → any
Defaults Feature fill/shader. The shader protocol is not defined by 1.0: readers store and round-trip src and uniforms and are not required to render shader paints (§20 row 29). Informative (§20 row 33): Kimo runs src as a pen.dev WebGL 1.0 (GLSL ES 1.00) fragment shader with annotated uniforms (@resolution, @time, @mouse, @color, @default, @range/@min/@max, @label); uniforms override them by name (number, boolean, #RRGGBB[AA], 2–4 numbers, or a binding).

Image crop. crop maps the node box, normalized to [0,1]², into normalized image space ([a, b, c, d, tx, ty] as in SVG/CSS matrix()): a box point (u, v) samples the image at (a·u + c·v + tx, b·u + d·v + ty). With fit: "crop" the image is drawn through that transform, so the crop region (the transform's image of the unit square) is stretched to the node box; an absent crop is the identity (same as stretch). An axis-aligned crop of the image rectangle (x, y, w, h), normalized, is [w, 0, 0, h, x, y]. Readers that do not support crop with another fit ignore it there.

Keywords:
MUST
MUST NOT
SHOULD
MAY
set in small mono caps wherever they are normative

7.2 Stroke

FieldTypeDefault
paintPaint | Paint[] — required
widthNum ≥ 0, or { t?, r?, b?, l? } (per side; an absent side is 0)1
align"inside" | "center" | "outside""inside"
cap"butt" | "round" | "square""butt"
join"miter" | "bevel" | "round""miter"
miterLimitnumber ≥ 14 (SVG stroke-miterlimit; a miter longer than miterLimit × width is beveled)
dashnumber[] (≥ 0)absent = solid. Alternating dash and gap lengths in px, as SVG stroke-dasharray (an odd-length list repeats). cap applies to every dash.
paint
Type Paint | Paint[] — required
Default
width
Type Num ≥ 0, or { t?, r?, b?, l? } (per side; an absent side is 0)
Default 1
align
Type "inside" | "center" | "outside"
Default "inside"
cap
Type "butt" | "round" | "square"
Default "butt"
join
Type "miter" | "bevel" | "round"
Default "miter"
miterLimit
Type number ≥ 1
Default 4 (SVG stroke-miterlimit; a miter longer than miterLimit × width is beveled)
dash
Type number[] (≥ 0)
Default absent = solid. Alternating dash and gap lengths in px, as SVG stroke-dasharray (an odd-length list repeats). cap applies to every dash.

Per-side widths ({ t, r, b, l }) draw each side as its own segment, with the given align, cap and dash; corners are joined only where both adjacent sides are non-zero.

7.3 Effects

kindFieldsDefaults
"shadow"x req, y req: Num; blur req: Num ≥ 0; color req; inner: boolean; spread: Num; blend; visible: booleaninner false, spread 0, blend "normal", visible true
"blur"radius req: Num ≥ 0 (layer blur); visible: booleanvisible true
"backdrop-blur"radius req: Num ≥ 0 (background blur); visible: booleanvisible true
"shadow"
Fields x req, y req: Num; blur req: Num ≥ 0; color req; inner: boolean; spread: Num; blend; visible: boolean
Defaults inner false, spread 0, blend "normal", visible true
"blur"
Fields radius req: Num ≥ 0 (layer blur); visible: boolean
Defaults visible true
"backdrop-blur"
Fields radius req: Num ≥ 0 (background blur); visible: boolean
Defaults visible true

Effects apply in list order. An effect with visible: false is kept but not rendered or exported (the eye toggle). blur is the CSS blur radius (filter: blur(r), standard deviation r / 2); a shadow's blur is the CSS shadow blur radius (standard deviation blur / 2), and spread grows (inner: shrinks the hole of) the shadow shape before blurring.