Curations are Tessellate's multi-brand publishing contract. A curation is a complete design treatment, not only a color preset. It can carry token values, CSS variables, implementation mappings, preview paths, and page-kit metadata for one brand interpretation.
Tessellate keeps this contract configurable. Curation slugs, CSS variable prefixes, selector attributes, component package names, mapping namespaces, and preview paths belong to the connected design-system repo.
The default curation convention recognizes page prefixes 00 through 13.
Headless publish can accept any requested range, including 00..19; prefixes
beyond the built-in set are inferred from the Figma page name and written into
the curation manifest. Repos can use another convention if their design system
needs a smaller or larger kit.
| Prefix | Default surface |
|---|---|
00 |
Cover and tokens |
01 |
Storefront hero |
02 |
Storefront service detail |
03 |
Storefront booking flow |
04 |
Email booking confirmed |
05 |
Email appointment reminder |
06 |
Email receipt |
07 |
Social announcement square |
08 |
Social quote story |
09 |
Receipt microsite |
10 |
Check-in card |
11 |
Loyalty card |
12 |
Wallet pass |
13 |
Refusal notes |
The leading number is the durable parser key. The human-readable page label can vary by team.
Curations can be published from CI when a job has a Figma personal access token and a Figma file key. Token variables can come from either Figma REST or a plugin-exported variables JSON file:
Do not paste token values inline with commands or into shell prompts. Export
FIGMA_TOKEN from a secret manager, CI secret, or a local env file kept outside
the repo/release root before running Tessellate; examples below assume
FIGMA_TOKEN is already set. Use --figma-token-env <NAME> when the token is
stored under another environment variable.
npm install --global github:kfrrst/tessellate#v0.3.1
tessellate curation publish \
--file <figma-file-key> \
--slug <curation-slug> \
--pages "00..19" \
--variables-file ./variables.reference.json \
--surfaces-file ./tessellate-surfaces.json \
--preview-host "https://cdn.example.com/tessellate" \
--mappings-file ./component-mappings.json \
--component-import-root "@example/design-system" \
--out ./public-mirrorThe command is noninteractive and uses Figma REST APIs to read the file tree and
export page PNG previews. It reads local Variables through REST unless
--variables-file is supplied. Optional component-mapping registries are carried
into the generated mapping artifacts. It reads FIGMA_TOKEN by default; use
--figma-token-env <NAME> when CI stores the token under another secret name.
Use --variables-file <path> when the Figma account cannot use the Variables
REST API. The full Tessellate plugin emits variables.{curationSlug}.json from
Figma's in-editor local variables API. When that file is provided, the CLI skips
GET /v1/files/:file_key/variables/local entirely; file tree reads and page PNG
exports still use Figma REST.
CI modes:
--dry-runresolves Figma data and computes artifacts without writing files.--checkexits non-zero when any output artifact is missing or different.--no-figma-previewswrites preview manifest entries while deferring PNG generation totessellate curation render-previews.
Variables file shape:
{
"version": 1,
"source": { "adapter": "figma-plugin" },
"meta": {
"variableCollections": {
"collection-id": {
"id": "collection-id",
"name": "Reference",
"defaultModeId": "mode-light",
"modes": [{ "modeId": "mode-light", "name": "Light" }]
}
},
"variables": {
"variable-id": {
"id": "variable-id",
"name": "color/core",
"variableCollectionId": "collection-id",
"resolvedType": "COLOR",
"valuesByMode": {
"mode-light": { "r": 0.1098, "g": 0.4863, "b": 0.3294, "a": 1 }
}
}
}
}
}Use --surfaces-file <path> when your numbered kit uses names that differ from
the built-in default surface labels. The file is a JSON array keyed by page
prefix. Matching prefixes override the default surface name, manifest label,
required flag, and preview filename; omitted prefixes continue to use the
built-in convention or the Figma page name fallback.
[
{
"pagePrefix": "00",
"surface": "cover",
"label": "Cover + tokens",
"required": true
},
{
"pagePrefix": "01",
"surface": "storefront_hero",
"label": "Storefront - hero",
"required": true
},
{
"pagePrefix": "02",
"surface": "storefront_team_roster",
"label": "Storefront - team roster",
"required": true
}
]Use tessellate copy-check before publishing a curation kit when design copy
contains availability, quantity, review, fee, deposit, or vertical-specific
language that must stay truthful:
tessellate copy-check <figma-file-key> \
--surfaces-file ./tessellate-surfaces.json \
--terminology ./terminology.json \
--claims ./claims.json \
--pages "00..13" \
--threshold strict \
--out ./copy-report.jsonThe command reads GET /v1/files/:fileKey through Figma REST, scans text layers
on each configured curation surface, and writes deterministic JSON for CI,
reviewers, MCP hosts, and agents. It reads FIGMA_TOKEN by default; pass
--figma-token-env <NAME> when the token is stored under another environment
variable. Tests and private API proxies can point the command at a different
Figma-compatible endpoint with --figma-api-base-url <url>.
The check is generic. It does not hardcode a sponsor, customer, or vertical. Repos decide which surface names, nouns, and claim patterns are acceptable.
Flag classes:
| Class | Meaning |
|---|---|
fabricated-availability |
Copy asserts availability or booking certainty that the repo has not allowed |
fabricated-count |
Copy asserts exact counts, inventory, people, locations, or number words that are not allowed |
fabricated-review-source |
Copy cites reviews, ratings, publications, or social proof sources that are not allowed |
fabricated-fee-claim |
Copy asserts fee, deposit, cancellation, or no-fee claims that are not allowed |
vertical-locked-noun |
Copy uses a vertical-specific noun where the kit should stay generic or tokenized |
Terminology can be a noun replacement map:
{
"nouns": {
"chair": "{seat}",
"stylist": "{provider}"
}
}Repos that already maintain term dictionaries can also use a terms-style shape:
{
"terms": [
{ "noun": "chair", "token": "{seat}" },
{ "term": "stylist", "replacement": "{provider}" }
]
}claims.json should describe the deterministic patterns the repo wants to
gate:
{
"availability": {
"allow": ["available by appointment"],
"patterns": ["\\bavailable today\\b", "\\bbook now\\b"]
},
"counts": {
"nouns": ["locations", "providers", "appointments"],
"numberWords": ["dozens", "hundreds", "thousands"]
},
"reviewSources": {
"allowed": ["internal testimonials"],
"patterns": ["\\bGoogle reviews\\b", "\\bYelp\\b", "\\b5-star\\b"]
},
"fees": {
"patterns": ["\\bno deposit\\b", "\\bno hidden fees\\b", "\\bfree cancellation\\b"]
}
}Use --threshold strict to exit non-zero when any fail-severity finding is
reported. Use --threshold warn when CI should attach copy-report.json
without blocking the run. MCP parity for this feature means stable CLI
invocation plus JSON input and output; no special host runtime is required.
Use tessellate materiality before publishing or reviewing a curation surface
when the kit must avoid banned material treatments while still allowing
explicit, profile-approved tactile cues:
tessellate materiality <fileKey:nodeId> \
--profile ./materiality-profile.json \
--threshold strict \
--out ./materiality-report.jsonThe command reads GET /v1/files/:fileKey through Figma REST and inspects the
requested node subtree. It reads FIGMA_TOKEN by default; pass
--figma-token-env <NAME> when the token is stored under another environment
variable. Tests and private API proxies can point the command at a different
Figma-compatible endpoint with --figma-api-base-url <url>.
The check is deterministic and generic. Repos decide which bans are enabled and
which cue roles are allowed in their profile. Repos can use the built-in
detectors below or add custom banned IDs with a detect object over node type,
name/text predicates, fill opacity, gradient-stop counts, image scale modes,
and simple has tokens. Unsupported banned IDs without a supported detector
are reported as materiality-unsupported-detector warnings.
Banned classes:
| Class | Meaning |
|---|---|
glassmorphism |
Detects frosted glass, blur-heavy translucent panels, and glass-card treatments when the profile enables the ban. |
neumorphism |
Detects soft extruded light/dark shadow pairings and recessed controls when the profile enables the ban. |
gradient-mesh |
Detects mesh-like multi-stop gradient material treatments when the profile enables the ban. |
fake-paper |
Detects artificial paper/card texture treatments when the profile enables the ban. |
Permitted cues are profile-defined candidates, not automatic writes:
| Cue | Meaning |
|---|---|
groundTint |
A profile-approved surface tint targeted by role names and appliesTo rules. |
grain |
A profile-approved grain cue targeted by role names and appliesTo rules. |
paperDepthShadow |
A profile-approved depth shadow targeted by role names and appliesTo rules. |
Use --threshold strict to exit non-zero when any fail-severity banned
material finding is reported. Use --threshold warn when CI should attach
materiality-report.json without blocking the run.
Passing --apply true marks permitted-cue candidates as apply-requested in the
JSON report:
tessellate materiality <fileKey:nodeId> \
--profile ./materiality-profile.json \
--apply true \
--threshold warn \
--out ./materiality-report.jsonThe CLI remains read-only and does not mutate Figma through REST. Plugin, MCP, or write-capable bridge consumers can read the report and perform write-back under their own permissions. MCP parity means stable CLI invocation plus JSON input and output; write-back requires that separate write-capable bridge.
Curations publish standard token JSON per curation. Token leaves may use the
W3C-style $value and $type fields. Existing token tools that use value
and type can be bridged without changing the curation contract.
When the plugin exports a variables snapshot, the default path is:
variables.{curationSlug}.json
Default token path:
tokens/curations/{curationSlug}.tokens.json
Example token leaf:
{
"color": {
"core": {
"$value": "#1c7c54",
"$type": "color"
}
}
}Each curation also publishes a CSS variable layer. The selector attribute and custom-property prefix are configurable.
Default CSS path:
tokens/curations/{curationSlug}.vars.css
Example output:
[data-curation="reference"] {
--curation-color-core: #1c7c54;
--curation-typography-headline-font-family: "Example Serif", serif;
--curation-typography-headline-font-size: 80px;
}Teams that already use a different attribute or prefix can configure their own
values, such as data-brand and --brand-*.
Tessellate supports two curation mapping shapes:
| Mode | Output |
|---|---|
| Aware | One mapping file that accepts a runtime curation context |
| Compiled | One mapping file per curation slug |
| Both | Both shapes, useful during migration |
Default paths:
mappings/curation-aware-mappings.json
mappings/curations/{curationSlug}/component-mappings.json
The mapping entries keep component imports and props configurable. Tessellate does not assume a specific component package or tenant runtime.
Preview manifests describe per-surface preview images for a curation. Preview assets can be generated by the connected pipeline or referenced by public URLs owned by the repo.
Default preview paths:
previews/{curationSlug}/manifest.json
previews/{curationSlug}/{NN}-{surface}.png
Preview manifests are suitable for curation galleries, side-by-side review, and release validation.
Host-route previews can load customer-owned licensed fonts before screenshots are captured. This avoids judging brand surfaces in fallback fonts while keeping font binaries outside Tessellate.
tessellate render-previews \
--curation-dir ./public-mirror \
--slug reference \
--url-template "https://preview.example.com/{surface}?curation={slug}" \
--fontset ./fontset.json \
--font-coverage-source ./figma-tree.json \
--coverage-report ./font-coverage.json \
--font-policy stricttessellate curation render-previews remains supported as a compatibility
alias. When --fontset is provided, Tessellate injects generated @font-face
CSS into the browser page and waits for document.fonts.ready before taking
the screenshot. Teams can still pass --screenshot-command for repo-owned
render tooling; the command template can consume {fontCssPath},
{fontsetPath}, {coverageReport}, and {fontPolicy}.
fontset.json is a JSON array:
[
{
"family": "Example Serif",
"style": "Regular",
"weight": 400,
"woff2Url": "https://cdn.example.com/fonts/example-serif-regular.woff2",
"licenseNote": "Customer-hosted licensed font"
}
]At least one browser-loadable URL is required per entry: woff2Url, woffUrl,
otfUrl, or ttfUrl. Local desktop paths such as otfPath belong to
Figma/Desktop companion flows and are not used by the headless host-route
renderer.
--font-coverage-source should point to a Figma-like JSON tree containing text
nodes with style.fontFamily and style.fontStyle metadata. The generated
coverage report flags every text node whose family/style is absent from the
fontset. --font-policy strict fails on missing coverage; --font-policy warn
keeps rendering and emits warnings for review.
Use --targets <targets.json> when a kit contains surfaces that should not all
render through the same host URL template. Target maps are keyed by curation
manifest surface name.
tessellate render-previews \
--curation-dir ./public-mirror \
--slug reference \
--targets ./targets.json \
--report ./render-coverage.jsonExample target map:
{
"storefront_hero": {
"type": "host-route",
"urlTemplate": "https://preview.example.com/{surface}?curation={slug}"
},
"wallet_pass": {
"type": "artifact",
"render": "image",
"command": "node ./scripts/render-pass.mjs --out {output}"
},
"social_card": {
"type": "image-composition",
"sourcePath": "./artifacts/social-card.png",
"aspect": "1:1"
}
}Supported target types:
| Type | Required fields | Behavior |
|---|---|---|
host-route |
urlTemplate |
Captures a browser screenshot through Playwright or --screenshot-command. |
artifact |
command |
Runs a customer-owned command, useful for wallet/pass/PDF renderers. |
image-composition |
sourcePath or command |
Copies a prepared image or runs a composition command for fixed-aspect outputs. |
--report writes render coverage using the shared quality-report envelope plus
covered, uncovered, and perSurface. Missing required targets fail the run.
Missing optional targets stay visible as warnings. Failed renders include
surface, path, exit code, and stderr evidence.
Use tessellate render-previews --email when a curation kit includes email
surfaces that need proof across multiple configured email clients. Tessellate
classifies email surfaces from the curation manifest, reads the HTML source map,
and dispatches each source to repo-owned client adapters.
tessellate render-previews \
--email \
--curation-dir ./public-mirror \
--clients ./clients.json \
--email-source ./email-source.json \
--slug reference \
--dark-mode true \
--report ./email-render-report.jsonclients.json contains adapter commands. The command template receives the
HTML source path, output path, and warnings path:
{
"clients": [
{
"id": "desktop-a",
"command": "node render-client.mjs {htmlPath} {output} {warningsPath}"
}
]
}Tessellate shell-quotes placeholder values before executing adapter commands, so
paths with spaces are safe when passed through {htmlPath}, {output}, or
{warningsPath}. Quote any literal executable or script paths you write directly
inside the command string.
A bare array of client objects is also accepted by implementations that expose that compatibility shape.
email-source.json maps curation surface ids or names to HTML source files:
{
"email_booking_confirmed": "./booking-confirmed.html"
}With --dark-mode true, Tessellate asks adapters to produce both light and dark
renders when the adapter supports that mode. The report includes per-client
artifacts, warnings, and failures. Any failed configured client render exits
non-zero so CI and MCP hosts can treat the JSON report as the stable contract.
Use tessellate parity to compare two PNG renders of the same surface:
tessellate parity \
--a ./reference/01-storefront-hero.png \
--b ./previews/reference/01-storefront-hero.png \
--mode design-vs-impl \
--threshold 0.02 \
--out ./parity-report.jsonModes:
| Mode | Use |
|---|---|
design-vs-impl |
Figma/reference export vs host-route implementation render. |
cascade-on-vs-off |
Two current renders in different cascade or feature-flag states. |
The report uses the shared quality-report envelope and includes
overallDivergence, regions, changedPixels, comparedPixels, and
ignoredPixels. Divergent regions are classified as fallback-palette-leak in
cascade mode, or as color-shift / position-shift in design-vs-implementation
mode. --ignore-regions <regions.json> excludes intentionally dynamic content:
[
{ "bbox": [0, 0, 320, 80], "reason": "rotating announcement" }
]The command exits non-zero when divergence exceeds --threshold.
Use tessellate curations diff to compare two or more numbered curation kits
and detect when a new kit is structurally too close to another kit. The command
reads Figma file trees through REST, applies the configured surface map, builds a
color-blind skeleton for each surface, and emits a deterministic JSON report.
tessellate curations diff \
<file-key-a> <file-key-b> <file-key-c> \
--surfaces-file ./tessellate-surfaces.json \
--registry-file ./component-mappings.json \
--pages "00..13" \
--threshold 0.85 \
--out ./curation-structure-report.jsonThe skeleton intentionally ignores color, fills, strokes, token values, text content, image URLs, and other content-specific values. It compares node type, child topology, layout mode, nesting depth, approximate size buckets, surface prefix, and mapped component names when a registry is provided.
Similarity is scored per surface:
similarity =
0.40 * nodeTreeEditDistance +
0.35 * layoutTopology +
0.25 * sharedComponentSet
Verdicts:
| Verdict | Meaning |
|---|---|
near-identical |
Surface score is at or above --threshold |
similar |
Surface score is at or above max(0.65, threshold - 0.2) |
distinct |
Surface score is below the similar threshold |
The command exits non-zero when any surface is near-identical, so CI can fail a
kit that appears to be a recolored copy of an existing structure. Use
--baseline <fileKey> to compare every candidate against a known kit while
still producing pairwise comparisons among the provided candidates.
Report shape:
{
"version": 1,
"command": "curations diff",
"status": "failed",
"threshold": 0.85,
"pairs": [
{
"a": { "fileKey": "reference", "name": "Reference Kit" },
"b": { "fileKey": "candidate", "name": "Candidate Kit" },
"overallSimilarity": 0.91,
"verdict": "near-identical",
"surfaces": [
{
"pagePrefix": "01",
"surface": "storefront_hero",
"similarity": 0.94,
"signals": {
"sharedComponentSet": 1,
"nodeTreeEditDistance": 0.92,
"layoutTopology": 0.93
},
"verdict": "near-identical"
}
]
}
],
"flags": [
{
"a": "reference",
"b": "candidate",
"surface": "storefront_hero",
"similarity": 0.94,
"verdict": "near-identical"
}
]
}MCP and agent hosts can call this command directly and read the --out JSON
file. The command does not require a plugin runtime and uses the same
unauthenticated local inputs (--surfaces-file, optional --registry-file) plus
the configured Figma token environment.
Use tessellate curations lint before publish to fail fast when a numbered kit
is incomplete or when a required surface has no mapped component match.
tessellate curations lint \
<file-key> \
--surfaces-file ./tessellate-surfaces.json \
--registry-file ./component-mappings.json \
--out ./curation-lint-report.jsonThe lint report includes:
status:passedorfailedmissingSurfaces: required surface prefixes not found in the Figma fileunmappedSurfaces: required surfaces that do not contain a registry matchdiagnostics: parse and validation findings
If --pages is omitted, both analysis commands derive the checked prefixes from
--surfaces-file. Use --pages to narrow the analysis to a subset of the map.
Repos that use curations should publish:
- A source curation JSON per curation or a build job that produces one.
- Per-curation token output.
- Per-curation CSS variable output.
- Curation-aware or compiled mapping output.
- Preview manifests and assets.
- A status or release note that tells consuming apps which curation slugs are ready for runtime use.
The curation contract is additive. Existing single-brand Tessellate users can continue using normal mapping registries, Surface context, and Inlay profiles.