Paints, strokes and effects
On this page3 sections
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).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).@kimo/core-schema (packages/core-schema). Where this document and the implementation disagree, that is a bug in one of them; file an issue.docs/teardown.md §10. Every place where this spec resolves an ambiguity in, or deviates from, that section is listed in §20.7.1 Paint
A Paint is a Color (hex, OKLCH, P3 or Ref — a solid paint) or one of:
kind | Fields | Defaults |
|---|---|---|
"solid" | color req, blend, visible | blend: "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; visible | center [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; visible | fit "cover", opacity 1, blend "normal", visible true |
"mesh" | cols req, rows req (integers ≥ 1); colors req: Color[]; points req: array | 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" | src req: relative path; uniforms: record name → any | 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). |
"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: arrayfill/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 → anyfill/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.
7.2 Stroke
| Field | Type | Default |
|---|---|---|
paint | Paint | Paint[] — required | |
width | Num ≥ 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" |
miterLimit | number ≥ 1 | 4 (SVG stroke-miterlimit; a miter longer than miterLimit × width is beveled) |
dash | number[] (≥ 0) | absent = solid. Alternating dash and gap lengths in px, as SVG stroke-dasharray (an odd-length list repeats). cap applies to every dash. |
paintwidth{ t?, r?, b?, l? } (per side; an absent side is 0)1align"inside" | "center" | "outside""inside"cap"butt" | "round" | "square""butt"join"miter" | "bevel" | "round""miter"miterLimit4 (SVG stroke-miterlimit; a miter longer than miterLimit × width is beveled)dashstroke-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
kind | Fields | Defaults |
|---|---|---|
"shadow" | x req, y req: Num; blur req: Num ≥ 0; color req; inner: boolean; spread: Num; blend; visible: boolean | inner false, spread 0, blend "normal", visible true |
"blur" | radius req: Num ≥ 0 (layer blur); visible: boolean | visible true |
"backdrop-blur" | radius req: Num ≥ 0 (background blur); visible: boolean | visible true |
"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 trueEffects 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.