Figma to Vaadin Implementation
Scope
This skill produces Vaadin Flow (Java) code that reproduces the layout and component
structure of a Figma design. It does not configure global theme tokens, brand colors, or
typography — that belongs to a separate theme configuration skill.
The main failure mode this skill guards against is jumping straight to code from a guess.
Gather enough context — the design, its annotations, and the real Vaadin API — before writing
anything.
Project overrides
This is a local copy, adapted to this repo. Upstream is silent on all of the following;
here they are hard rules. They override anything else in this skill, and they sit on top of
docs/theming-layouts.md and
CLAUDE.md, which are the binding authority.
- No
LumoUtility, no Tailwind. layout-approach is vaadin-css, permanently. Vaadin
layout Java APIs for structure; scoped, role-named CSS classes for the rest.
- No
LUMO_* theme variants. This app runs Aura, where the LUMO_* constants are the
legacy naming. Use the theme-agnostic ones — ButtonVariant.PRIMARY, TERTIARY, ERROR,
SUCCESS, WARNING, SMALL, LARGE. The tertiary-inline, contrast and icon
variants are Lumo-only and do nothing under Aura; use plain TERTIARY (findings F-013,
F-017).
- No
--lumo-* CSS custom properties. They are undefined under Aura, so they render
nothing — silently, with no error. Use --aura-* and --vaadin-* tokens; look the exact
names up with get_theme_css_properties theme=aura rather than guessing.
- Aura has no
Npct opacity scale. There is no --aura-red-10pct equivalent to Lumo's
--lumo-error-color-10pct. Derive a tint with
color-mix(in srgb, var(--aura-red) 15%, transparent).
- Custom styling lands in one place:
src/main/resources/META-INF/resources/styles.css,
as scoped, kebab-case, role-named classes (order-summary-card, not blue-background).
Not in getStyle().set(...), and not in a new stylesheet per view.
- Vaadin docs tools come from the
vaadin-skills plugin, not a Vaadin MCP server entry
— this repo deliberately has none. search_vaadin_docs, get_full_document,
get_component_java_api and get_theme_css_properties are available under the plugin's
tool prefix; that a server named Vaadin is missing is not a setup failure.
Workflow
Create TODOs from these steps and follow them in order.
1. Fetch design context
get_design_context on the given node is the primary source — it has the most detailed
component information; check data-name for component type, and note theme/variant hints and
text styles. If the response is truncated (very large or deeply nested frames), fall back to
get_metadata for the layer hierarchy, then call get_design_context on the specific child
nodes you need.
2. Check component annotations
For each component instance, apply these in order: recommended Vaadin component, theme
variants, accessibility requirements, implementation notes, documentation links. Annotations
override guesses from layer names.
If a Figma component still doesn't map clearly to one Vaadin component after checking
annotations, ask: "Should this be a [ComponentA] or [ComponentB]? The Figma shows
[description]." Don't guess.
3. Research each component (mandatory)
Never rely on memorized Vaadin knowledge — API surfaces and feature-flag status change between
versions.
search_vaadin_docs to find candidates, record file_path
get_full_document for every component before implementing — search results are
previews, not enough on their own
get_component_java_api for the exact Java method signatures — use this whenever you
need to know which methods a component exposes (slot setters, theme variants, sizing)
If a compile error suggests a method doesn't exist, re-read the component's Java API docs
before guessing at a fix. Don't search local .m2 jars for source, and don't run anything to
"just try it" — the docs are the authoritative source.
4. Resolve project preferences, once
This project has already resolved all four preferences in .agent-context
at the repo root. Read that file and use its values — do not ask the user, and do not
re-derive them by auto-detection. Only ask if a key you need is genuinely absent from it.
layout-approach: vaadin-css
architecture: composed-components
sample-data: use-existing-data
verification: verify
| Preference |
Values |
Auto-detect |
Otherwise |
layout-approach |
vaadin-css only |
Fixed by project standard — lumo-utility and tailwind are forbidden here. |
Never ask; the value is pinned in .agent-context. |
architecture |
single-view / composed-components |
No reliable signal |
"Should I build this as one view class with private helper methods, or split it into reusable components (e.g. a separate detail/edit form that fires its own save/cancel events)?" |
sample-data |
generate-sample / use-existing-data |
Check whether the project already has a repository, service, or entity matching the data shown in the design |
"Should I generate small sample data for this view, or is there existing data/service in the project I should wire it to instead?" |
verification |
skip / verify / verify-and-fix |
No reliable signal |
"After implementing, should I skip testing, run visual verification against the Figma design, or run visual verification and automatically apply one round of fixes based on the findings?" |
Read these two project documents before writing any layout code — they are the binding
authority on layout and styling in this repo, and they override anything in this skill:
docs/theming-layouts.md — the layout & spacing
standard: which Vaadin layout Java API covers which need, the --vaadin-gap-* /
--vaadin-padding-* token scale, when to fall back to a scoped CSS class, CSS class
naming, Scroller instead of overflow: auto.
CLAUDE.md — the Aura-not-Lumo theming rules and the project's
overall orientation.
Where this skill and those documents disagree, those documents win. This skill carries no
bundled layout references; the upstream references/layouts-*.md files were deliberately not
copied into this repo (see Provenance).
5. Implement
- Use Vaadin components, not generic HTML; prefer the component API over the element/style API
(e.g.
textField.setReadOnly(true), not .getElement().setAttribute("readonly", ""))
- Apply theme variants via Java API (
addThemeVariants)
- Use the layout patterns from
docs/theming-layouts.md (Vaadin layout Java APIs first;
scoped, role-named CSS classes with --vaadin-* / --aura-* tokens for the rest)
- Pick correct heading levels from text styles
- Add accessibility attributes where needed (e.g.
setAriaLabel on icon-only buttons)
If architecture: composed-components — split the view into a container plus reusable
sub-components (e.g. a details/edit form as its own class). Sub-components fire custom
ComponentEvents (e.g. SaveEvent, CancelEvent) that the container listens for and acts on,
rather than the container reaching into the sub-component's fields directly.
If sample-data: generate-sample:
- Define it in a
private helper method (e.g. createSampleOrders())
- 3–5 items max, or enough to match what the design visually shows (e.g. a scrolling grid) if
that density is core to the layout
- Realistic values (
"Alice Johnson", not "Item 1")
- Add
// Sample data — replace with real service call comment
- Prefer
List.of(...) for immutable collections
If sample-data: use-existing-data, wire the view to the existing repository/service/entity
instead of inventing new sample data.
5b. Conform to the design spec
Where the project keeps a design spec, it is the contract for anything this step styles:
take tokens and states from the component's file rather than choosing values. A difference
is a bug in this code. If a component you are building has no spec, or the design has
moved, run figma-survey — it owns the spec. Do not write or edit a spec file to match
what you just built.
Done when every component this step styled matches its spec, or the mismatch is
reported.
6. Test
This skill's own job — writing code — is done by the end of Step 5. Don't run terminal
commands, open a browser, or take screenshots yourself; what happens next depends on the
verification preference resolved in Step 4:
skip — stop here.
verify — invoke the figma-visual-verification skill, passing it the Figma URL (or
fileKey/nodeId) used for this view and the route it was implemented at. Present its
prioritized findings to the user as-is; don't act on them yet.
verify-and-fix — invoke figma-visual-verification the same way, then apply exactly
one round of fixes addressing its findings, highest severity first. Tell the user what was
changed and why. Don't loop back into a second verification pass automatically — if the user
wants to confirm the fixes, that's a new verification run.
.agent-context pins verification: verify for this project — report only, never auto-fix.
Universal component patterns
These apply regardless of the styling approach.
// ✅ Component API over element/style API
textField.setReadOnly(true);
button.addThemeVariants(ButtonVariant.TERTIARY); // not LUMO_TERTIARY — see Project overrides
iconButton.setAriaLabel("Close");
input.setLabel("Label"); // HasLabel API, not a separate Span
// ✅ Sizing via component API
layout.setSizeFull();
layout.setWidth("600px");
// ❌ Never use the style API for things the component API handles
textField.getElement().setAttribute("readonly", "");
button.getElement().getStyle().set("background", "transparent");
layout.getStyle().set("width", "600px");
avatar.getStyle().set("--vaadin-avatar-size", "48px");
Gotchas
VerticalLayout defaults:
- Padding ON — call
setPadding(false) if not wanted
- Width 100% of parent
alignItems START — children do not stretch horizontally; call setAlignItems(STRETCH) or setWidthFull() per child to fill the width
justifyContentMode controls the vertical (main) axis
HorizontalLayout defaults:
Padding OFF
Width shrinks to content — call setWidthFull() if it should fill the parent
alignItems STRETCH — children stretch vertically to fill the layout height (a Button next to a TextField will silently grow)
justifyContentMode controls the horizontal (main) axis
A layout child's minimum size defaults to its content size; this causes unexpected scrollbars in Scroller / TabSheet; fix with component.setMinWidth("0") or setMinHeight("0")
For purely visual containers prefer FlexLayout — it avoids all of the above defaults
flex-shrink is on by default — a fixed-size child shrinks when placed next to a setWidthFull() sibling; call layout.setFlexShrink(component, 0) to prevent it, or use layout.setFlexGrow(fullSizeComponent, 1) instead of setWidthFull() to avoid the conflict altogether
setWidthFull() on a child in a content-hugging HorizontalLayout expands the layout rather than fitting it; use setAlignItems(STRETCH) instead
A layout child's minimum size defaults to its content size; this causes unexpected scrollbars in Scroller / TabSheet; fix with component.setMinWidth("0") or setMinHeight("0"). The same default also applies one level up: a component like MasterDetailLayout or Scroller placed as the expand()ed child of a VerticalLayout (or a CSS Grid area) can resist shrinking below its content's natural height even with setSizeFull(). If a view overflows the page instead of scrolling internally, add setMinHeight("0") to that expanded child itself, not just to a Scroller nested further inside it
RadioButtonGroup / CheckboxGroup default orientation is theme-dependent: horizontal in Lumo, vertical in Aura. If the Figma layer is named/laid out horizontally and the project uses Aura, add addThemeVariants(RadioGroupVariant.AURA_HORIZONTAL) / CheckboxGroupVariant.AURA_HORIZONTAL — otherwise the group silently renders as a vertical stack
Feature-flag status changes between versions — don't assume a component needs one from memory; check search_vaadin_docs("feature flags") then get_full_document on the result
Never use CSS margin to space out a Vaadin layout component from its container — margin sits outside the component's measured box, which breaks setSizeFull()/expand() height math (a component can measure "correct" while still visually overflowing its parent). Add spacing instead via padding on a wrapping layout, or by targeting the component's own shadow-DOM part with ::part(...) (e.g. vaadin-master-detail-layout::part(detail) { padding: ...; })
When writing custom CSS, use real theme CSS custom properties — look them up with the Vaadin MCP (get_theme_css_properties) rather than inventing a plausible-sounding variable name with a hardcoded var(--name, fallback) fallback. If the name doesn't actually exist, the fallback silently becomes the real value and never tracks the theme (e.g. var(--vaadin-background-color-secondary, #f9fafb) — that property doesn't exist; the real one is --vaadin-background-container)
VerticalLayout/HorizontalLayout/FlexLayout already set box-sizing: border-box themselves, so padding on them is safe by default. Only plain elements — a custom CSS rule targeting a Div, another non-layout component, or a shadow-DOM ::part(...) — need box-sizing: border-box added explicitly when the rule also sets padding; without it, padding adds to the element's declared width/height instead of being carved out of it, so a component sized with setWidth()/setSizeFull() ends up visually larger than intended
Quick reference: Figma → Vaadin
| Figma |
Vaadin |
| Vertical auto layout |
VerticalLayout |
| Horizontal auto layout |
HorizontalLayout |
| Free / absolute layout |
FlexLayout |
| Form / labelled fields |
FormLayout |
| Master-detail |
MasterDetailLayout |
| Button |
Button |
| Text Field |
TextField |
| Grid / Table |
Grid |
| Avatar |
Avatar |
| Card |
Card (v24.8+) |
| Badge / status label |
Badge |
| Text layer |
com.vaadin.flow.component.html.Span |
| Heading 3 |
com.vaadin.flow.component.html.H3 |
Provenance
- Upstream: https://github.com/juuso-vaadin/figma-to-vaadin-skill
- Source path:
skills/figma-to-vaadin/SKILL.md
- Commit:
3a9289c (3a9289c15df9e7a7659f0d92fee204ad1dc65c14)
- Copied: 2026-08-26 — by hand, as a project-owned file. Not managed by
skills.sh / skills-lock.json; that lock file is CLI-managed against
mattpocock/skills with per-entry hashes, and this skill is locally modified.
- Locally modified: yes
- The three
references/layouts-*.md files were not copied.
layouts-lumo-utility.md and layouts-tailwind.md describe approaches
docs/theming-layouts.md forbids outright; layouts-vaadin-css.md is a near-duplicate
of that document, and a near-duplicate is where divergence hides.
- Step 4's layout-approach mapping now reads
docs/theming-layouts.md and CLAUDE.md as
the binding authority, and treats the four preferences as already resolved in
.agent-context.
- Added the Project overrides section (no
LumoUtility, no LUMO_* variants, no
--lumo-* properties, no Aura Npct scale, one styles.css, Vaadin docs tools from
the vaadin-skills plugin).
ButtonVariant.LUMO_TERTIARY in the universal-patterns example replaced with
ButtonVariant.TERTIARY; the AvatarVariant.LUMO_LARGE line dropped (no verifiable
theme-agnostic equivalent in the 25.2 docs).
- Step 6 now invokes
figma-visual-verification (this repo's renamed copy of upstream's
vaadin-visual-verification).
compatibility: no longer claims a Vaadin MCP server is required.
- Added Step 5b — Conform to the design spec, since upstream has no design-spec
concept at all: it ends at code plus verification, which is why the same "is this a
card?" question can be answered differently by every view. This project authors the
spec in
figma-survey and treats it as a contract, so implementation conforms and
never edits it.
- Not copied at all: upstream's
figma-to-lumo-theme — this app is Aura, and
CLAUDE.md forbids --lumo-*.
1---2name: figma-to-vaadin3description: Translate Figma designs into Vaadin Flow (Java) UI code using the Figma MCP and the Vaadin docs tools. Use this skill whenever the user wants to implement a Figma frame, screen, or component as Vaadin Java code — even if they just say "implement this design", "generate Vaadin code from Figma", "convert this frame to Java", or paste a Figma URL. Does NOT apply to React, HTML, web components, or other frontend frameworks — only Vaadin Flow (Java). Does NOT apply to design-only tasks such as editing Figma files or generating Figma components. Does NOT configure themes or visual design tokens — that is a separate skill.4---56# Figma to Vaadin Implementation78## Scope910This skill produces Vaadin Flow (Java) code that reproduces the **layout and component11structure** of a Figma design. It does not configure global theme tokens, brand colors, or12typography — that belongs to a separate theme configuration skill.1314The main failure mode this skill guards against is jumping straight to code from a guess.15Gather enough context — the design, its annotations, and the real Vaadin API — before writing16anything.1718## Project overrides1920This is a **local copy**, adapted to this repo. Upstream is silent on all of the following;21here they are hard rules. They override anything else in this skill, and they sit on top of22[`docs/theming-layouts.md`](../../../docs/theming-layouts.md) and23[`CLAUDE.md`](../../../CLAUDE.md), which are the binding authority.2425- **No `LumoUtility`, no Tailwind.** `layout-approach` is `vaadin-css`, permanently. Vaadin26 layout Java APIs for structure; scoped, role-named CSS classes for the rest.27- **No `LUMO_*` theme variants.** This app runs Aura, where the `LUMO_*` constants are the28 legacy naming. Use the theme-agnostic ones — `ButtonVariant.PRIMARY`, `TERTIARY`, `ERROR`,29 `SUCCESS`, `WARNING`, `SMALL`, `LARGE`. The `tertiary-inline`, `contrast` and `icon`30 variants are Lumo-only and do nothing under Aura; use plain `TERTIARY` (findings F-013,31 F-017).32- **No `--lumo-*` CSS custom properties.** They are undefined under Aura, so they render33 nothing — silently, with no error. Use `--aura-*` and `--vaadin-*` tokens; look the exact34 names up with `get_theme_css_properties theme=aura` rather than guessing.35- **Aura has no `Npct` opacity scale.** There is no `--aura-red-10pct` equivalent to Lumo's36 `--lumo-error-color-10pct`. Derive a tint with37 `color-mix(in srgb, var(--aura-red) 15%, transparent)`.38- **Custom styling lands in one place:** `src/main/resources/META-INF/resources/styles.css`,39 as scoped, kebab-case, role-named classes (`order-summary-card`, not `blue-background`).40 Not in `getStyle().set(...)`, and not in a new stylesheet per view.41- **Vaadin docs tools come from the `vaadin-skills` plugin**, not a `Vaadin` MCP server entry42 — this repo deliberately has none. `search_vaadin_docs`, `get_full_document`,43 `get_component_java_api` and `get_theme_css_properties` are available under the plugin's44 tool prefix; that a server named `Vaadin` is missing is not a setup failure.4546## Workflow4748Create TODOs from these steps and follow them in order.4950### 1. Fetch design context5152`get_design_context` on the given node is the primary source — it has the most detailed53component information; check `data-name` for component type, and note theme/variant hints and54text styles. If the response is truncated (very large or deeply nested frames), fall back to55`get_metadata` for the layer hierarchy, then call `get_design_context` on the specific child56nodes you need.5758### 2. Check component annotations5960For each component instance, apply these in order: recommended Vaadin component, theme61variants, accessibility requirements, implementation notes, documentation links. Annotations62override guesses from layer names.6364If a Figma component still doesn't map clearly to one Vaadin component after checking65annotations, ask: "Should this be a [ComponentA] or [ComponentB]? The Figma shows66[description]." Don't guess.6768### 3. Research each component (mandatory)6970Never rely on memorized Vaadin knowledge — API surfaces and feature-flag status change between71versions.7273- `search_vaadin_docs` to find candidates, record `file_path`74- `get_full_document` for **every** component before implementing — search results are75 previews, not enough on their own76- `get_component_java_api` for the exact Java method signatures — use this whenever you77 need to know which methods a component exposes (slot setters, theme variants, sizing)7879If a compile error suggests a method doesn't exist, re-read the component's Java API docs80before guessing at a fix. Don't search local `.m2` jars for source, and don't run anything to81"just try it" — the docs are the authoritative source.8283### 4. Resolve project preferences, once8485This project has already resolved all four preferences in [`.agent-context`](../../../.agent-context)86at the repo root. **Read that file and use its values — do not ask the user, and do not87re-derive them by auto-detection.** Only ask if a key you need is genuinely absent from it.8889```90layout-approach: vaadin-css91architecture: composed-components92sample-data: use-existing-data93verification: verify94```9596| Preference | Values | Auto-detect | Otherwise |97|---|---|---|---|98| `layout-approach` | `vaadin-css` **only** | Fixed by project standard — `lumo-utility` and `tailwind` are forbidden here. | Never ask; the value is pinned in `.agent-context`. |99| `architecture` | `single-view` / `composed-components` | No reliable signal | "Should I build this as one view class with private helper methods, or split it into reusable components (e.g. a separate detail/edit form that fires its own save/cancel events)?" |100| `sample-data` | `generate-sample` / `use-existing-data` | Check whether the project already has a repository, service, or entity matching the data shown in the design | "Should I generate small sample data for this view, or is there existing data/service in the project I should wire it to instead?" |101| `verification` | `skip` / `verify` / `verify-and-fix` | No reliable signal | "After implementing, should I skip testing, run visual verification against the Figma design, or run visual verification and automatically apply one round of fixes based on the findings?" |102103**Read these two project documents before writing any layout code — they are the binding104authority on layout and styling in this repo, and they override anything in this skill:**1051061. [`docs/theming-layouts.md`](../../../docs/theming-layouts.md) — the layout & spacing107 standard: which Vaadin layout Java API covers which need, the `--vaadin-gap-*` /108 `--vaadin-padding-*` token scale, when to fall back to a scoped CSS class, CSS class109 naming, `Scroller` instead of `overflow: auto`.1102. [`CLAUDE.md`](../../../CLAUDE.md) — the Aura-not-Lumo theming rules and the project's111 overall orientation.112113Where this skill and those documents disagree, **those documents win**. This skill carries no114bundled layout references; the upstream `references/layouts-*.md` files were deliberately not115copied into this repo (see Provenance).116117### 5. Implement118119- Use Vaadin components, not generic HTML; prefer the component API over the element/style API120 (e.g. `textField.setReadOnly(true)`, not `.getElement().setAttribute("readonly", "")`)121- Apply theme variants via Java API (`addThemeVariants`)122- Use the layout patterns from `docs/theming-layouts.md` (Vaadin layout Java APIs first;123 scoped, role-named CSS classes with `--vaadin-*` / `--aura-*` tokens for the rest)124- Pick correct heading levels from text styles125- Add accessibility attributes where needed (e.g. `setAriaLabel` on icon-only buttons)126127If `architecture: composed-components` — split the view into a container plus reusable128sub-components (e.g. a details/edit form as its own class). Sub-components fire custom129`ComponentEvent`s (e.g. `SaveEvent`, `CancelEvent`) that the container listens for and acts on,130rather than the container reaching into the sub-component's fields directly.131132If `sample-data: generate-sample`:133- Define it in a `private` helper method (e.g. `createSampleOrders()`)134- 3–5 items max, or enough to match what the design visually shows (e.g. a scrolling grid) if135 that density is core to the layout136- Realistic values (`"Alice Johnson"`, not `"Item 1"`)137- Add `// Sample data — replace with real service call` comment138- Prefer `List.of(...)` for immutable collections139140If `sample-data: use-existing-data`, wire the view to the existing repository/service/entity141instead of inventing new sample data.142143### 5b. Conform to the design spec144145Where the project keeps a design spec, it is the contract for anything this step styles:146take tokens and states from the component's file rather than choosing values. A difference147is a bug in this code. If a component you are building has no spec, or the design has148moved, run `figma-survey` — it owns the spec. Do not write or edit a spec file to match149what you just built.150151**Done when** every component this step styled matches its spec, or the mismatch is152reported.153154### 6. Test155156This skill's own job — writing code — is done by the end of Step 5. Don't run terminal157commands, open a browser, or take screenshots yourself; what happens next depends on the158`verification` preference resolved in Step 4:159160- **`skip`** — stop here.161- **`verify`** — invoke the `figma-visual-verification` skill, passing it the Figma URL (or162 `fileKey`/`nodeId`) used for this view and the route it was implemented at. Present its163 prioritized findings to the user as-is; don't act on them yet.164- **`verify-and-fix`** — invoke `figma-visual-verification` the same way, then apply exactly165 **one** round of fixes addressing its findings, highest severity first. Tell the user what was166 changed and why. Don't loop back into a second verification pass automatically — if the user167 wants to confirm the fixes, that's a new verification run.168169`.agent-context` pins `verification: verify` for this project — report only, never auto-fix.170171## Universal component patterns172173These apply regardless of the styling approach.174175```java176// ✅ Component API over element/style API177textField.setReadOnly(true);178button.addThemeVariants(ButtonVariant.TERTIARY); // not LUMO_TERTIARY — see Project overrides179iconButton.setAriaLabel("Close");180input.setLabel("Label"); // HasLabel API, not a separate Span181182// ✅ Sizing via component API183layout.setSizeFull();184layout.setWidth("600px");185186// ❌ Never use the style API for things the component API handles187textField.getElement().setAttribute("readonly", "");188button.getElement().getStyle().set("background", "transparent");189layout.getStyle().set("width", "600px");190avatar.getStyle().set("--vaadin-avatar-size", "48px");191```192193## Gotchas194195`VerticalLayout` defaults:196- Padding ON — call `setPadding(false)` if not wanted197- Width 100% of parent198- `alignItems` START — children do not stretch horizontally; call `setAlignItems(STRETCH)` or `setWidthFull()` per child to fill the width199- `justifyContentMode` controls the vertical (main) axis200201`HorizontalLayout` defaults:202- Padding OFF203- Width shrinks to content — call `setWidthFull()` if it should fill the parent204- `alignItems` STRETCH — children stretch vertically to fill the layout height (a `Button` next to a `TextField` will silently grow)205- `justifyContentMode` controls the horizontal (main) axis206- A layout child's minimum size defaults to its content size; this causes unexpected scrollbars in `Scroller` / `TabSheet`; fix with `component.setMinWidth("0")` or `setMinHeight("0")`207208- For purely visual containers prefer `FlexLayout` — it avoids all of the above defaults209- `flex-shrink` is on by default — a fixed-size child shrinks when placed next to a `setWidthFull()` sibling; call `layout.setFlexShrink(component, 0)` to prevent it, or use `layout.setFlexGrow(fullSizeComponent, 1)` instead of `setWidthFull()` to avoid the conflict altogether210- `setWidthFull()` on a child in a content-hugging `HorizontalLayout` expands the layout rather than fitting it; use `setAlignItems(STRETCH)` instead211- A layout child's minimum size defaults to its content size; this causes unexpected scrollbars in `Scroller` / `TabSheet`; fix with `component.setMinWidth("0")` or `setMinHeight("0")`. The same default also applies one level up: a component like `MasterDetailLayout` or `Scroller` placed as the `expand()`ed child of a `VerticalLayout` (or a CSS Grid area) can resist shrinking below its content's natural height even with `setSizeFull()`. If a view overflows the page instead of scrolling internally, add `setMinHeight("0")` to that expanded child itself, not just to a `Scroller` nested further inside it212- `RadioButtonGroup` / `CheckboxGroup` default orientation is theme-dependent: horizontal in Lumo, **vertical in Aura**. If the Figma layer is named/laid out horizontally and the project uses Aura, add `addThemeVariants(RadioGroupVariant.AURA_HORIZONTAL)` / `CheckboxGroupVariant.AURA_HORIZONTAL` — otherwise the group silently renders as a vertical stack213- Feature-flag status changes between versions — don't assume a component needs one from memory; check `search_vaadin_docs("feature flags")` then `get_full_document` on the result214- Never use CSS `margin` to space out a Vaadin layout component from its container — margin sits outside the component's measured box, which breaks `setSizeFull()`/`expand()` height math (a component can measure "correct" while still visually overflowing its parent). Add spacing instead via padding on a wrapping layout, or by targeting the component's own shadow-DOM part with `::part(...)` (e.g. `vaadin-master-detail-layout::part(detail) { padding: ...; }`)215- When writing custom CSS, use real theme CSS custom properties — look them up with the Vaadin MCP (`get_theme_css_properties`) rather than inventing a plausible-sounding variable name with a hardcoded `var(--name, fallback)` fallback. If the name doesn't actually exist, the fallback silently becomes the real value and never tracks the theme (e.g. `var(--vaadin-background-color-secondary, #f9fafb)` — that property doesn't exist; the real one is `--vaadin-background-container`)216- `VerticalLayout`/`HorizontalLayout`/`FlexLayout` already set `box-sizing: border-box` themselves, so padding on them is safe by default. Only plain elements — a custom CSS rule targeting a `Div`, another non-layout component, or a shadow-DOM `::part(...)` — need `box-sizing: border-box` added explicitly when the rule also sets `padding`; without it, padding adds to the element's declared width/height instead of being carved out of it, so a component sized with `setWidth()`/`setSizeFull()` ends up visually larger than intended217218## Quick reference: Figma → Vaadin219220| Figma | Vaadin |221|---|---|222| Vertical auto layout | `VerticalLayout` |223| Horizontal auto layout | `HorizontalLayout` |224| Free / absolute layout | `FlexLayout` |225| Form / labelled fields | `FormLayout` |226| Master-detail | `MasterDetailLayout` |227| Button | `Button` |228| Text Field | `TextField` |229| Grid / Table | `Grid` |230| Avatar | `Avatar` |231| Card | `Card` (v24.8+) |232| Badge / status label | `Badge` |233| Text layer | `com.vaadin.flow.component.html.Span` |234| Heading 3 | `com.vaadin.flow.component.html.H3` |235---236237## Provenance238239- **Upstream:** [https://github.com/juuso-vaadin/figma-to-vaadin-skill](https://github.com/juuso-vaadin/figma-to-vaadin-skill)240- **Source path:** `skills/figma-to-vaadin/SKILL.md`241- **Commit:** `3a9289c` (`3a9289c15df9e7a7659f0d92fee204ad1dc65c14`)242- **Copied:** 2026-08-26 — by hand, as a project-owned file. **Not** managed by243 `skills.sh` / `skills-lock.json`; that lock file is CLI-managed against244 `mattpocock/skills` with per-entry hashes, and this skill is locally modified.245- **Locally modified:** yes246 - The three `references/layouts-*.md` files were **not** copied.247 `layouts-lumo-utility.md` and `layouts-tailwind.md` describe approaches248 `docs/theming-layouts.md` forbids outright; `layouts-vaadin-css.md` is a near-duplicate249 of that document, and a near-duplicate is where divergence hides.250 - Step 4's layout-approach mapping now reads `docs/theming-layouts.md` and `CLAUDE.md` as251 the binding authority, and treats the four preferences as already resolved in252 `.agent-context`.253 - Added the **Project overrides** section (no `LumoUtility`, no `LUMO_*` variants, no254 `--lumo-*` properties, no Aura `Npct` scale, one `styles.css`, Vaadin docs tools from255 the `vaadin-skills` plugin).256 - `ButtonVariant.LUMO_TERTIARY` in the universal-patterns example replaced with257 `ButtonVariant.TERTIARY`; the `AvatarVariant.LUMO_LARGE` line dropped (no verifiable258 theme-agnostic equivalent in the 25.2 docs).259 - Step 6 now invokes `figma-visual-verification` (this repo's renamed copy of upstream's260 `vaadin-visual-verification`).261 - `compatibility:` no longer claims a Vaadin MCP server is required.262 - Added **Step 5b — Conform to the design spec**, since upstream has no design-spec263 concept at all: it ends at code plus verification, which is why the same "is this a264 card?" question can be answered differently by every view. This project authors the265 spec in `figma-survey` and treats it as a contract, so implementation conforms and266 never edits it.267- **Not copied at all:** upstream's `figma-to-lumo-theme` — this app is Aura, and268 `CLAUDE.md` forbids `--lumo-*`.