What this skill does
A canvas.md is a markdown file that describes a project's visual taste
— palette, typography, surfaces, motion, default effects, icons — and
from which Malevich generates the foundations and semantic token
tiers. Change the canvas, and every visual surface re-flows with no
component edits.
This skill is about importing taste from a reference into a
canvas.md. The input is a thing that already has a look — a live
site, screenshots, a Figma file, a brand guideline PDF — and the output
is a canvas that, when parsed by @malevich/canvas, reproduces that
look as closely as the system allows. Read llm-wiki/canvas-spec.md
in full before starting; it is authoritative for the file format,
section layout, parsing rules, and validation. This skill is the
procedure; the spec is the contract.
The mindset — describe taste, not CSS
A canvas is a brand book a machine can act on: opinionated, declarative,
short — typically 50–200 lines. Your job when importing is to read the
reference and express its decisions at the highest level that still
captures them. Resist transcribing pixel values. If the reference uses
a 1.5 type ratio, say hierarchy: dramatic, don't list every heading
size. The whole point of the system is that you describe intent and the
parser derives the scale.
Things a canvas must NOT do (anti-patterns from the spec): define raw component CSS, list every color shade, set a component-specific token for every component, duplicate the default canvas, or encode application logic. When you catch yourself doing any of these, step back up a level.
Step 0 — Read the reference deliberately
Before writing anything, extract the reference's decisions in each of these dimensions. Note what is intentional vs. incidental:
- Color — the ink (text) color, the canvas (background), the one accent, the status hues. Does it have a distinct dark mode?
- Type — the display face, the body face, the mono face. How tight is the leading? How much letter-spacing? How dramatic is the size jump from body to hero?
- Shape & depth — sharp or rounded corners? Flat, subtle, or dramatic shadows? Dense or generous spacing?
- Motion — snappy, smooth, or expressive? Any signature easing?
- Effects — recurring decorative treatments (a grain texture, a glow on primary actions, a gradient on feature cards)?
- Icons — line or filled? A specific weight or stroke?
If the reference is screenshots or a PDF, sample real values (use a color picker for hex, measure type sizes). If it's a live site, read the computed styles. Capture decisions, not every declaration.
Step 1 — Scaffold the sections
A canvas.md uses headings as section boundaries. Lay out the skeleton,
then fill it. Section order aids readability but doesn't affect parsing;
omit any section to inherit the default canvas.
# <Project name>
<one-line description>
## Identity
## Palette
## Typography
## Surfaces
## Motion
## Effects
## Icons
## Overrides
Inside sections, use key: value lines for flat fields and fenced
```yaml blocks where structure is needed. Both are valid.
Step 2 — Palette (required)
At minimum a canvas defines ink, canvas, and accent; everything
else derives from these three (the parser auto-generates a tonal scale
via OKLCH). Override status and surface keys only where the reference is
specific.
## Palette
### Core
ink: #0A0A0A
canvas: #FAFAFA
### Accent
accent: #D32F1E
accent-strong: #B02817
accent-muted: #F5C4BE
### Status
danger: #D32F1E
warning: #E8B547
success: #2A8A3E
info: #1A4FB5
If the reference has a real dark mode, declare both under ### Light and
### Dark subsections; otherwise give a single set. Prefer oklch() for
new values — it is perceptually uniform. The parser validates contrast
(ink-strong on canvas must hit AAA, ink-regular AA, etc.) and warns
without blocking; honor those warnings rather than overriding blindly.
Step 3 — Typography and the three axes (required)
This is where "the same fonts arranged differently" is controlled. Declare the three font families, then set the three voice axes — these are the import's most important judgement calls.
## Typography
### Families
display: NAMU 1930
regular: NAMU Pro
mono: JuliaMono
### Voice
tone: tight
character: standard
hierarchy: dramatic
### Cases
heading-case: sentence
overline-case: uppercase
action-case: sentence
The three axes:
- tone — line-height multipliers.
tight(×0.95 headings / ×0.92 body),regular(×1.0),airy(×1.10 / ×1.15). Read the reference's leading: dense editorial →tight; spacious marketing →airy. - character — letter-spacing.
compact,standard,expansive. Tighten for big condensed display type; expand for an airy, premium feel. - hierarchy — the scale ratio.
subtle(1.200, minor third),balanced(1.333, perfect fourth),dramatic(1.500, perfect fifth). A small jump from body to H1 →subtle; a poster-like contrast →dramatic. Body anchors the scale at 16px; change withbody-size.
Font names must resolve to an @font-face in @malevich/core/fonts/ —
the parser does not fetch fonts from the internet. If the reference uses
a font Malevich doesn't ship, either map to the closest available family
or flag it for the human; don't invent a web-font load.
Step 4 — Surfaces (optional)
Radii, shadows, and spacing, expressed as named styles rather than per-token values:
## Surfaces
### Radii
style: sharp # sharp | soft | round
### Shadows
style: subtle # none | subtle | pronounced | dramatic
### Spacing
density: regular # compact | regular | comfortable
Reach for a per-token override (radius-card: 6px) only when a named
style gets one specific surface wrong — not as the default mode of
working.
Step 5 — Motion (optional)
## Motion
style: subtle # none | subtle | standard | expressive
respects-reduced-motion: true
style is shorthand for a fast/base/slow/easing set; individual fields
override it. Keep respects-reduced-motion: true unless the reference
has a deliberate reason otherwise.
Step 6 — Effects (optional)
Declares default decorative treatments applied to components or variants.
In the v1.0 model these are modifiers from the seven categories
(background, surface, effect, shader) — so an effects default is
really "apply this modifier by default to this target." Express the
reference's recurring treatments here, sparingly:
## Effects
### Defaults
- target: button.-accent
apply: effect=glow
- target: card.-feature
apply: background=gradient
Each entry names a target (component + optional variant) and the
modifier to apply by default. Only declare a default when the treatment
is genuinely systemic in the reference; one-off flourishes belong in the
markup, not the canvas. Confirm the target component accepts that
modifier category in
packages/components/modifiers/applicability.json — a default that the
matrix forbids will warn.
Step 7 — Icons (optional)
## Icons
weight: regular
stroke-width: 2
fallback: static
Map the reference's icon feel to weight and stroke. Malevich icons are a pluggable pack (ADR-0011); the canvas expresses preference, the pack supplies the glyphs.
Step 8 — Overrides (optional, last resort)
Explicit token-level overrides for the rare case where higher-level configuration can't express a decision. They are the last word, applied after generation.
## Overrides
### Semantic
radius-card: 12px
### Component-specific
button-background-accent: linear-gradient(135deg, #D32F1E, #B02817)
Use sparingly. A canvas heavy with overrides is a signal the taste wasn't captured at the right level — go back to the axes and styles first.
Step 9 — Validate and report
Run the canvas through @malevich/cli (apply-canvas) and read both
lists it returns:
- Errors block generation — missing required field, type mismatch
(
accent: "tight"), unknown enum value, cyclic inheritance, unresolvable font. - Warnings don't block — contrast below threshold, unknown section, an override targeting a nonexistent token, an effect target matching no component.
Resolve every error. For each warning, decide consciously: fix it, or record why you're accepting it. Then produce a short import report: which reference decisions mapped cleanly, which were approximated (and why), and which couldn't be expressed and need a human call.
Output checklist
- Reference decisions captured per dimension (color, type, shape, motion, effects, icons) before writing
-
ink,canvas,accentdefined; other palette keys only where the reference is specific - Typography expressed via the three axes (tone / character / hierarchy), not transcribed sizes
- Font families resolve to shipped faces, or unmapped ones flagged
- Surfaces / motion expressed as named styles, per-token overrides only for genuine exceptions
- Effects defaults are real modifiers, matrix-checked, and only for systemic treatments
- Overrides used sparingly, if at all
- Canvas validates with zero errors; warnings triaged
- Import report lists clean maps, approximations, and open questions