# Figma Theme

> Apply a settled design spec to a Vaadin Aura app's global theme: write only what differs from the theme's defaults, prove it in the browser, and refresh the resolved token values the spec records.

- Skill: `vaadin/figma-theme` (Agent Skill)
- Install (CLI): `npx skillmds@latest add vaadin/figma-theme`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vaadin/figma-theme/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: vaadin (https://skillmd.com/u/vaadin)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vaadin/figma-theme

---


# Figma theme

This skill **applies** the global theme. It does not decide it.

The deciding happened in a theme-scoped `figma-survey`: that read the design's variables,
put every divergence to the user, and wrote the answers into the project's design spec.
This skill reads that spec and turns it into CSS — a single bounded artifact whose
correctness is checkable in a browser.

Splitting the two matters because the expensive part is the decision, not the CSS, and a
decision made while writing the stylesheet is a decision nobody reviewed. If the spec does
not exist, or its rows are still **open**, stop and say so: applying an unsettled spec
just moves the guessing into a file that looks authoritative.

Applies to Vaadin Flow (Java) on the Aura theme. Aura is the precondition —
`@StyleSheet(Aura.STYLESHEET)` marks it, and an app on the classic Lumo theme is out of
scope.

## Steps

### 1. Read the spec, and refuse an unsettled one

Read the design spec at the path the project's agent instructions name: the foundations
files for the decided values and the off-scale decisions, and the token reference for the
inputs and formulas.

Check three things before writing anything:

- **Every row that this run would apply is marked settled.** An **open** row is an
  undecided question; implementing it silently converts a deferral into a fact. List the
  open rows and stop.
- **The spec's framework version matches the app's.** The formulas are internals with no
  compatibility promise, so a spec resolved against another version may be describing a
  scale the app no longer has.
- **The spec is not empty.** No spec means no theme survey has run. Say so and stop —
  the survey is the prerequisite, not an optional first draft.

**Done when** the spec has been read, its version checked against the app's, and every row
in scope confirmed settled — or the run has stopped with the open rows named.

### 2. Fix the app end

Find the stylesheet the app's theme entry point imports, and list every property it
currently sets. That list is what step 3 diffs against: some declarations will be replaced,
some deleted outright, and knowing which is which is the difference between a rewrite and
an edit.

Note anything in the file that is **not** a theme value — component rules, selector
overrides. Those are not this skill's business unless the spec decides them, and a rewrite
that quietly drops them is how a nav ends up unstyled.

**Done when** every property the app's theme sets is listed, and non-token rules in the
file are identified as out of scope or explicitly in it.

### 3. Write the CSS

**Set only what differs from the theme's own default.** Look each default up rather than
recalling it — a default recalled from memory produces a declaration that looks
theme-driven while only re-asserting what was already true, or quietly misses the real
default. A file full of restated defaults hides the handful of properties that actually
differ.

So a value the spec settled *in favour of the framework default* becomes a **deletion**,
not a declaration. Expect the file to shrink.

Edit the app's existing stylesheet in place; a second file competing with the first is how
two sources of truth start. A webfont needs its `@import` at the top of that file — but
check whether the theme already bundles the family before adding one, because an
unnecessary import is a network request for a font that was already there.

Three things to carry across rather than lose:

- **A declaration another component depends on.** Where a Java component's behaviour reads
  a theme property, dropping that property breaks the component silently. Find out before
  deleting; a comment in the file saying why a line exists is a warning, not decoration.
- **Rules for a part of the UI the spec has not reached yet.** Comment-mark them as pending
  their own issue rather than deleting them, so the app is not left unstyled in between.
- **A comment explaining a non-obvious line.** If you cannot restate why it is there, that
  is a reason to keep it and ask.

**Done when** every settled value from the spec is applied, no property is set to its own
default, and nothing was deleted whose purpose you could not name.

### 4. Prove it, then refresh the resolved values

Reload the running app and read the tokens back. **The resolved numbers must equal what the
decided inputs predict through the spec's formulas.** That check is cheap, and it is the
difference between CSS that was written and CSS that works.

Then exercise what the theme cannot prove by inspection:

- **Both colour schemes**, on more than one screen. A theme is a claim about every screen,
  and the scheme nobody looks at is where a frozen literal surfaces.
- **Any live scheme switch the app offers.** Where a control resets to the OS preference,
  that usually works by *clearing* an inline property so the stylesheet's own declaration
  applies again — so the test is that the reset resolves to the stylesheet's value, not
  that the declaration is present in the file.
- **The states the spec records but a static frame cannot show** — hover, disabled, focus.
  Read what the theme actually changes: it may express hover as a pseudo-element overlay
  and disabled as `opacity` on the host, leaving `background-color` and `color` identical
  in both. An audit reading those two properties reports no difference having measured
  nothing. Diff the full computed style between two states when unsure which property
  moves.

**Then write the measurements back.** The spec's *resolved values* table is this skill's to
own, because only a running app produces it: record the numbers at the decided inputs, the
framework version they hold for, and the refresh trigger. Where the spec quotes a measured
figure elsewhere — a contrast ratio in a component file — this run either confirms it or
corrects it.

**Capture the formulas too, whenever the spec lacks them** — a first run, or a framework
upgrade since the last one. A survey never boots the app, so it defers this here and
records the gap rather than guessing; leave the gap unfilled and the next survey cannot
judge an off-scale value at all. `getComputedStyle(document.documentElement)` returns each
token's expression with its `var()`s substituted but not evaluated: that is the formula.
For the number, apply the token to a probe element and read the used value back. Do both
for every step of every scale, and mark any step whose expression you could not read as
unverified rather than inferring a multiplier from one sample.

**The probe trap:** `element.style.setProperty()` takes a **CSS** property name, so
`setProperty('borderTopLeftRadius', …)` fails silently and every token reads back as `0px`
or `normal`. Use `'border-top-left-radius'`. A whole measurement pass can look successful
and be entirely zeros, and the zeros are indistinguishable from a real answer if you are
not expecting them.

Finally, say what this run **staled**. Changing an input moves every derived value, so any
component spec quoting a resolved number is now suspect even though its token names are
still correct. Recommend a component-spec refresh; do not perform it here.

**Done when** the re-read tokens match the predicted values, both schemes and every state
the spec records have been exercised, the resolved-values table is written back, and the
staled figures are named.

