THE MEDIA FOR AI THAT ACTUALLY HELPS.French edition · Understand & take action
The interface is in English. Use cases, skills and articles remain in French.
Adobe

Transformer une maquette Figma en page AEM Edge Delivery

figma-to-content

En bref

Sert à transformer une maquette Figma en page de contenu AEM Edge Delivery Services (EDS / Franklin / Helix) dans Document Authoring (DA, da.live). L’agent lit le frame (et d’éventuelles annotations) via un MCP Figma, puis rattache chaque section à un bloc existant ou à un nouveau bloc isolé. Le cas typique, d’après le SKILL.md, est un client déjà sur EDS, avec ses propres blocs, qui reçoit une nouvelle maquette. Il ne faut pas s’en servir pour restyler un site EDS existant, ni pour convertir du HTML statique généré (Mobirise, Relume, v0, HTML exporté de Figma) : le texte renvoie alors vers la skill snowflake, ni pour Universal Editor ou AEM Cloud Service.

Ce que ça fait

  • Lit le frame Figma via MCP
  • Le mappe vers des blocs EDS dans DA
  • Écarte restylage, HTML exporté et Universal Editor

Pour qui, et un exemple

Les équipes déjà sur AEM Edge Delivery Services qui veulent partir d’une maquette Figma pour une nouvelle page dans Document Authoring.

Exemple de demande : « Voici l’URL Figma de la nouvelle page : transforme-la en page EDS dans da.live, en réutilisant nos blocs existants. »

On l’utilise lorsqu’un frame Figma doit devenir une page EDS dans DA, par exemple « build this Figma frame in EDS » ou « publish this design to da.live ». Exemple rédigé par iadujour.fr à partir du SKILL.md, non testé.

Installer

Claude Code : /plugin marketplace add adobe/skills puis /plugin install <plugin>@adobe-skills

/plugin marketplace add adobe/skills
/plugin install <plugin>@adobe-skills

Niveau : Intermédiaire

Suppose un projet déjà en place chez l’éditeur et les outils décrits dans le SKILL.md.

À savoir

Dépôt officiel de l’organisation GitHub adobe.

Extrait du SKILL.md

La description d’origine, celle que l’agent lit pour décider de charger la skill (en anglais) :

Use this to turn a Figma design into an AEM Edge Delivery Services (EDS / AEM / Franklin / Helix) content page in Document Authoring (DA, da.live). Triggers: "build this Figma frame in EDS", "turn this Figma design into a DA page", "publish this design to da.live", or providing a figma.com URL for a page. Reads the frame (and any annotations) via a Figma MCP, resolves each section to an existing […]

Citée depuis le SKILL.md au commit 8c23ac2 . Le dossier contient aussi 5 fichiers annexes (references/).

Le SKILL.md compte 970 lignes et 22 sections principales :

  • Two paths
  • When to use
  • When NOT to use
  • Related skills — orchestrated by this one
  • Inputs (gather before Phase 1; ask if missing — never guess)
  • Phase 0 — Preflight (fail fast, before any read or write)
  • Phase 1 — Read the Figma design (Figma MCP)
  • Phase 2 — Resolve each section
  • 2.0 — Know the project's block palette (always)
  • 2.1 — Resolve each section (annotation-first, else infer)
  • 2.2 — Confirm the plan before deploying (never deploy a guess)
  • 2.3 — Lock the orchestration manifest (which sub-skills this plan requires)
  • Phase 3A — Map content into an EXISTING block
  • Phase 3B — Create a NEW block (content + code)
  • Phase 3C — Author DEFAULT CONTENT (no block)
  • Phase 4 — Generate DA body-fragment HTML (da-content)
  • Phase 5 — Deploy to DA
  • Pre-publish gate — the page is not "done" until every box is checked
  • Publish to the live host — the FINAL step, gated on the checklist
  • Phase 6 — Report
  • Guardrails
  • Open questions

Licence : Apache 2.0

Vérifiée le 10 novembre 2026 sur : fichier LICENSE du dépôt . Licence déclarée dans le frontmatter : « Apache-2.0 ».

La licence autorise la reprise : le texte intégral du SKILL.md est reproduit ci-dessous, sans modification, avec sa mention de licence.

Texte intégral du SKILL.md

Reproduit sans modification depuis adobe/skills (commit 8c23ac2) , © Adobe, sous licence Apache 2.0 (fichier de licence d’origine ).

Afficher le SKILL.md original (970 lignes, en anglais)
---
name: figma-to-content
description: "Use this to turn a Figma design into an AEM Edge Delivery Services (EDS / AEM / Franklin / Helix) content page in Document Authoring (DA, da.live). Triggers: \"build this Figma frame in EDS\", \"turn this Figma design into a DA page\", \"publish this design to da.live\", or providing a figma.com URL for a page. Reads the frame (and any annotations) via a Figma MCP, resolves each section to an existing block, a new isolated block, or default content (inferred against the project's existing blocks and confirmed with you, or read from annotations when the frame happens to have them), generates DA-compliant body-fragment HTML, and deploys via the DA Source API + preview."
license: Apache-2.0
metadata:
  version: "1.0.0"
---

# figma-to-content — Figma design → EDS content page in DA

Read a Figma frame, assemble a page from EDS blocks and default content, and
publish it to Document Authoring. Runs with a **Figma MCP** (to read the
design) and a **DA IMS token** (to write content) — no proprietary tooling
required.

This skill **orchestrates existing skills**; it does not reimplement DA rules,
block knowledge, or block-building. **Invoke those skills — do not inline them.**
The condensed rules quoted in this file are *pointers* to jog the right skill,
never a substitute for loading it: when a phase names a skill, invoke it and work
from its actual guidance. Running this file as a self-contained procedure —
hand-writing blocks, authoring DA HTML from memory, skipping the browser/visual
check — is the single most common way a run goes wrong. Phase 2.3 turns the
confirmed plan into an explicit manifest of the sub-skills you must invoke.

## Two paths

Classify each section of the design, then follow the matching path:

- **Content only** — every section maps to a block that **already exists** in
  the target project, or to **default content** (plain headings/paragraphs/
  images/buttons — no block). Author content and deploy. No code changes.
- **Content + code** — a section needs a block the project **does not have**,
  or an existing block matches structurally but its **styling diverges** (the
  look lives in block-specific CSS you'd have to edit). Create it as a **new,
  isolated block** (via the block-building skills), push the code, then author
  content and deploy. **Never skin an existing block or add per-section rules
  to global CSS** — new, additive blocks only. (Retargeting the project's
  global design tokens is a separate, allowed theming step; see Guardrails.)

A single design usually mixes all three (known blocks + default content + one
or two new blocks).

## When to use

- The user has a Figma frame representing a page and wants it as an EDS page in DA.
  The common case: a customer **already on EDS**, with their own blocks, gets a
  new design for a new page — some sections reuse existing blocks, some need new
  ones.
- **The default path is infer-and-confirm.** Usually the frame is **not**
  annotated (e.g. the user just says "migrate this page"): the skill **infers**
  each section's mapping against the project's existing block palette and
  **confirms the plan** before building, asking whenever a section is ambiguous
  (Phase 2). This path needs nothing but the design itself.
- **Annotations are an optional accelerator — never required.** If a frame
  happens to declare each section's block / default content / new block (see
  [references/annotation-contract.md](./references/annotation-contract.md)),
  those declarations are taken as authoritative and skip the inference for that
  section. Absent them, nothing is lost — the skill infers and confirms.

### When NOT to use

- **Redesign / restyle an existing EDS site**, or convert arbitrary generated
  static HTML (Mobirise, Relume, v0, exported Figma HTML). Use **snowflake**.
- **Universal Editor or AEM Cloud Service (Java/OSGi/JCR).** Out of scope.

## Related skills — orchestrated by this one

| For | Use skill |
|---|---|
| DA IMS token (`DA_TOKEN`) | **da-auth** |
| DA body-fragment HTML rules, Source API, preview/publish, media | **da-content** |
| Whether a block exists + its authoring model & examples | **block-collection-and-party** |
| Surveying the whole available block palette | **block-inventory** |
| Designing a content model for a **new** block | **content-modeling** |
| Building a **new** block (full dev workflow) | **content-driven-development** (invokes **building-blocks**, **testing-blocks**) |
| Rendering a block + **visual comparison to the design** (the reuse gate) | **testing-blocks** (browser/Playwright screenshot + "compare implementation to design") |

The DA-write contract in Phase 5 is the same one **da-content** documents
(see its `references/html-content.md` and `references/platform.md`).

---

## Inputs (gather before Phase 1; ask if missing — never guess)

- **Figma reference** — file key + node id of the page frame (from the
  figma.com URL or the current Figma MCP selection). A file usually holds
  **many frames** — desktop/mobile variants, A/B versions, work-in-progress
  copies of the same page. Confirm **exactly which frame** to build; don't
  assume the first or largest. Two frames that are variants of the *same* page
  are one page, not two — ask which is canonical rather than deploying both.
- **Target project** — a local checkout of the EDS project repo (needed to see
  existing blocks under `blocks/`, and required for the content+code path to
  add block code). Its GitHub `{owner}`/`{repo}` and the deploy `{branch}`.
- **DA location** — `daOrg`, `daRepo` (the DA namespace), page `PATH` (no
  extension, lowercase/dash only — see da-content platform rules). In the
  standard EDS+DA setup `daOrg`/`daRepo` **equal** the GitHub `{owner}`/`{repo}`;
  confirm, because Phase 5 writes to `daOrg`/`daRepo` but previews/renders on
  the GitHub `{owner}`/`{repo}`/`{branch}`.
- **`DA_TOKEN`** — via **da-auth**, which exports `$DA_TOKEN` and caches it at
  `~/.aem/da-token.json` (valid ~1h). Prefer the `$DA_TOKEN` da-auth already set
  in this session; read the cache file only if that's unset. Two distinct
  failures: a `401` with an empty body means the token **expired** → re-auth; a
  cache file that **can't be read because the execution sandbox has no `$HOME`
  access** means the token is *unreachable*, not expired (see Phase 0 step 3) —
  don't conflate them.

---

## Phase 0 — Preflight (fail fast, before any read or write)

Verify the run can actually complete **before** reading the design or writing to
DA — a missing prerequisite caught here is one actionable message; caught mid-run
it is a confusing, half-built page. Run these checks in order and, on the first
that fails, **stop with the specific remediation below** — do not proceed on a
guess or a partial capability.

1. **Figma MCP reachable.** Confirm a Figma MCP is connected and responds via a
   cheap call (e.g. `whoami`, or listing its tools). If **no Figma MCP tool is
   available at all**, stop: *"No Figma MCP is connected. Connect one (Claude
   desktop Dev Mode, an IDE Figma integration, or a remote Figma MCP) and
   re-run."* Record the authenticated identity (`whoami`) for the next check.
2. **Access to the specific file.** Make one lightweight call against the target
   `fileKey` (e.g. `get_metadata` scoped to the frame, or `get_design_context`
   on the node). A **permission / not-found** error (`403`/`404`/"no access")
   means the file is not shared with the authenticated account → stop: *"Figma
   reports no access to `<fileKey>` as `<whoami>`. Share the file with that
   account, switch accounts, or provide a file you can open."* **Distinguish this
   from a transport cap** — a truncated, garbled, or JSON-parse-error response is
   the size cap (see Phase 1), **not** an access failure: retry narrower, do not
   report it as no access.
3. **DA write path available.** Confirm a `DA_TOKEN` is obtainable via **da-auth**
   — prefer the `$DA_TOKEN` it exports into the session, else its cache at
   `~/.aem/da-token.json`, else freshly minted. **If the cache exists but can't be
   read because this execution sandbox has no `$HOME` access**, the token is not
   missing — it is *unreachable*; do **not** loop re-minting. Stop with that
   distinction spelled out: *"A DA token exists but this sandbox can't read
   `~/.aem/da-token.json` — run where the cache is readable, or provide the token
   as `$DA_TOKEN` (or a readable path)."* If no token can be obtained at all,
   stop: *"Can't obtain a DA token (da-auth) — authenticate to DA and re-run."*
   Either way, don't spend a full Figma read only to fail at the deploy step.
4. **Project checkout + orchestrated skills present.** The target repo is checked
   out locally (needed to see `blocks/` and to add new-block code) and the skills
   this one orchestrates (**da-auth**, **da-content**, the block skills) are
   available. If the checkout path is unknown, ask for it.

On all-pass, print a one-line preflight summary — Figma identity, the file/frame,
and the DA `org/repo` + `branch` you will write to — then proceed to Phase 1.

---

## Phase 1 — Read the Figma design (Figma MCP)

Use a Figma MCP (Claude desktop / IDE / external). **Introspect the actual tool
schemas** — signatures differ between MCP implementations (the local Dev Mode
server often works off the current selection and may not take a `fileKey`; the
remote/desktop server takes `fileKey` + optional `nodeId`). The tools you need,
by capability:

- **Structure** (e.g. `get_metadata`) — the frame's section/layer tree; node
  ids, names, positions, sizes. Derive the section list from the **content
  groups** in visual order (sort by `y`) — **not** the raw child list: full-
  bleed background rectangles, overlays, and decorative shapes are *part of* a
  section (its background), not sections of their own, and a single visual
  section is often split across sibling nodes (e.g. a background rect + a tab
  strip + a text group). Ignore the decorative layers and group the rest into
  sections by position. Usually `fileKey` required, `nodeId` optional. **Some
  MCP servers cap response size — even a single frame's structure dump can
  exceed it; scope the call to the frame or, if that still fails, one section
  at a time. A truncated, garbled, or JSON parse-error response *is* the cap
  being hit — retry narrower; do not read it as "no structure."**
- **Visual** (e.g. `get_screenshot`) — a per-section reference image to
  sanity-check the block/content mapping.
- **Content & assets** (e.g. `get_design_context`) — text, links, and image
  asset download URLs for a node. For the content+code path this also provides
  the layout/structure a new block must reproduce. **Request the lean form** —
  exclude the screenshot from the context call (fetch visuals separately with
  the screenshot tool) and disable any Code Connect lookup (e.g.
  `excludeScreenshot` / `disableCodeConnect`-style options) unless you are
  mapping to a real component library; both add payload and round-trips and can
  push a large response over the transport cap. Icons are usually **component
  instances**, not raster fills — obtain their **SVG** (export/copy as SVG),
  never a PNG, for the `/icons` or DA `/media` reference in Phase 4.
- **Design tokens** (e.g. `get_variable_defs`) — colors, spacing, type. Read
  annotation values and, for new blocks, source token values.

**Call budget & order — Figma MCP calls are rate-limited and payload-capped, so
spend them deliberately rather than re-fetching:**

1. **`get_metadata` first** (scoped to the frame) — the structure/section tree.
   The cheapest orienting call; every later call keys off the node ids it returns.
2. **`get_screenshot` of the whole frame early** — one full-frame reference image
   up front is the anchor you reconcile the section count against (segmentation
   heuristic) and, later, compare the rendered page to (Phase 5 Stage B). Take
   per-section crops afterwards, only for the sections you actually build.
3. **`get_design_context` targeted and lean, per section** — request the lean
   form (exclude the screenshot, disable Code Connect) and scope it to **one
   section's node at a time**. A whole-frame context dump is the single call most
   likely to blow the transport cap.
4. **Asset download last** (`download_assets` / export-as-SVG) — only for the
   assets the **confirmed** plan references, after Phase 2. Don't pull binaries
   for sections that end up reusing an existing block or being cut.

A `429`/rate-limit or a truncated/garbled response is a **budget/cap signal, not
"no data"**: back off, narrow the scope (frame → section), and retry — never read
it as an empty design or as missing access (Phase 0 step 2 draws the same line).

Produce an ordered **section inventory**: `{ sectionNodeId, annotation,
screenshot, content, background }` — capture each section's **background /
theme** (e.g. alternating light and dark sections), because the global token
retheme (Guardrails) recolors blocks but does **not** switch a section's
background: that carries via a `section-metadata` `Style` class or a block's
own defined dark/light variant (Phase 4). Read annotations per
[references/annotation-contract.md](./references/annotation-contract.md).

> **Segmentation heuristic** — when the frame has no explicit grouping, derive
> the section list like this, not from the raw child order:
> 1. Sort the frame's direct children by `y` (top to bottom).
> 2. **Drop pure-decoration layers** from the section list — full-bleed
>    background rectangles, gradients, blurs, absolutely-positioned shapes with
>    no text or interactive child. Record each as the *background* of the
>    content it sits behind (→ Phase 4 `section-metadata`); don't emit it as a
>    section of its own.
> 3. **Merge siblings that form one visual band** — nodes whose vertical
>    extents overlap or sit within ~one line-height of each other (a background
>    rect + a heading group + a button row are *one* section, not three).
> 4. **Reconcile the count against the screenshot** before resolving: the eye
>    sees the real sections; a mismatch means you over- or under-split — fix it
>    before Phase 2.

> **Placeholder content is common — don't ship it.** Designs routinely contain
> dummy copy (`Lorem ipsum`, a CTA literally labelled "Button" or "Lorem
> Ipsum", the same card title repeated across every card) and unfilled slots
> (empty or transparent image cells, blank stat boxes). Author from the **real
> text and media in the design context** — not from the placeholder and not from
> invented filler. Where it's clearly placeholder, **flag it in the plan and
> confirm the real copy/media with the user** rather than publishing "Lorem
> Ipsum" to a live page. Distinct items (cards, tabs, news entries) need
> **distinct** copy and images — repeated-identical content is itself a
> placeholder smell. If the design *itself* carries only placeholder, you cannot
> manufacture the real content: stop and get it from the user before publish.

> Site chrome (nav bar, footer) is usually **not page body** — in EDS it is
> sourced from separate `/nav` and `/footer` documents via the header/footer
> blocks. Don't author it into the page unless the user asks.

---

## Phase 2 — Resolve each section

Every section resolves to exactly one of: **existing block** (→ 3A),
**default content** (→ 3C), or **new block** (→ 3B). How that decision is
reached depends on whether the section is annotated.

### 2.0 — Know the project's block palette (always)

Before resolving anything, enumerate what the project **already has**:
`ls -d blocks/*/` plus **block-inventory** / **block-collection-and-party** for
each block's **authoring model** (row/cell structure, variants) **and a
rendered example** — the block's `liveExampleUrl` when it comes from the Block
Collection, or the project's own block rendered at `localhost:3000`. That
rendered example is the "block side" of the 2.1 / Phase 3A visual check. This
is the reuse-candidate set — essential when the customer is already on EDS with
their own blocks.

### 2.1 — Resolve each section (annotation-first, else infer)

**If the section is annotated** (see
[references/annotation-contract.md](./references/annotation-contract.md)), the
annotation is **authoritative**: named block that exists → existing block (3A);
marked `new` (or absent-and-user-confirmed) → new block (3B); plain prose/media
→ default content (3C).

**If it is not annotated** (e.g. "just migrate this page"), **infer** the
mapping — do not dump it as unresolved:

1. Plain prose/media (headings, paragraphs, images, a standalone link) with no
   repeating structure → **default content** (3C).
2. Otherwise match it against the 2.0 palette using the **reuse gate (structure
   AND visual, Phase 3A)**: does its content model fit an existing block *and*
   does that block's rendered example — under the project theme — look like the
   section, allowing only token differences and variants the block defines?
   - **Both fit → existing block** (3A).
   - **Structure fits but the look diverges** (bespoke card/layout/decoration
     the block's CSS can't produce without editing it), **or nothing fits →
     new block** (3B).
   - **A section carrying an interactive control** — tabs / segmented switch,
     accordion, carousel or slider, toggle — is structural divergence no static
     block reproduces: route it to a **new block** (3B), or, if the control is
     non-essential chrome, **confirm with the user** whether to keep it or
     flatten it to static content. Don't silently drop the interaction or fake
     it with a look-alike static block.
3. Attach a **confidence** to every inference: `high` (clear reuse match, or
   clearly novel) or `low` (structure fits but styling is borderline; two
   blocks plausibly fit; new-variant-vs-new-block; content model ambiguous).

### 2.2 — Confirm the plan before deploying (never deploy a guess)

Present a **resolution plan** — one line per section: decision (reuse `X` /
default content / new block `Y`), confidence, a one-clause rationale, and a
**content flag** on any section whose copy or media is placeholder (Phase 1)
and needs real content before publish.

- **High-confidence sections auto-proceed through building** (Phases 3–4) —
  don't block on them.
- **Stop and ask before building** any `low`-confidence section or genuine
  ambiguity, offering the concrete choice (reuse this block vs. new block;
  which block; new variant vs. new block). Wait for the answer.
- **Pause once before deploying (Phase 5)** whenever the plan contains any
  **inferred** (unannotated) mapping: show the final plan and get a single
  confirmation before the da.live write/preview — deploy is outward-facing and
  hard to reverse. Skip this pause only if the user pre-authorized an
  unattended run. A **fully annotated** plan needs no pause — the annotations
  are the authorization.
- **Flag an existing target page.** Before confirming, check whether the target
  `content/<PATH>.html` already exists in DA (a cheap Source-API `GET`, Phase 5);
  if it does, deploying **overwrites** it — say so in the plan and get explicit
  overwrite confirmation. Never silently clobber a page you didn't create, even
  on an otherwise pre-authorized unattended run. **Record two facts per path** for
  Phase 5 to enforce: `PLANNED_STATE` (`new` if the check returned 404, `exists`
  if 200) and `OVERWRITE_OK` (`yes` only when the user confirmed overwriting an
  existing page). Phase 5 re-checks existence right before writing and **refuses**
  if the state changed since planning (a page appeared in the gap) or overwrite
  was never confirmed — the plan-time check alone is not a license to clobber.
- The user can override any line.

Never silently drop a section, and never deploy an **inferred** mapping the
user has not seen.

**Worked example** — an unannotated 4-section frame; this is the plan you
present in 2.2 (one line per section):

| # | Section | Decision | Conf. | Why | Content |
|---|---|---|---|---|---|
| 1 | Hero band — heading + 2 CTAs over a photo | reuse `hero` | high | model fits; heading **and** CTAs stay legible on the media under the theme | ok |
| 2 | 3 feature blurbs — icon + title + text | reuse `cards` | high | content model and rendered look both fit | ok |
| 3 | Metric strip — 3 big numbers + labels | **new block** `stat-cards` | high | bespoke panel look no existing block produces (3B) | ok |
| 4 | Newsletter row — heading + email field + button | **new block** / confirm | low | carries an interactive control (input) — ask keep vs. flatten (G5) | ⚠ placeholder copy |

Then act on it: sections 1–2 build without blocking; #3 builds (high-confidence
new block); **#4 stops for a decision** (low-confidence + interactive control);
and because the plan contains inferred mappings, the whole thing gets **one
pre-deploy confirmation** before the da.live write. Section #4's ⚠ flag means
its real copy must be supplied before publish, not shipped as placeholder.

### 2.3 — Lock the orchestration manifest (which sub-skills this plan requires)

Turn the confirmed plan into an explicit **manifest** of the sub-skills it
requires and **invoke each one** — this is where the intro's *orchestrate, don't
inline* rule becomes a concrete, ticked list. This file's summaries never
substitute for loading the named skill.

Derive the manifest from the plan:

| The plan contains… | You MUST invoke |
|---|---|
| **Any** section (always) | **da-auth** (token) and **da-content** — load its real `references/html-content.md`, `platform.md`, and `media.md`, *not* the condensed rules in this file — before authoring (Phase 4) and deploying (Phase 5). |
| An **existing-block reuse** (3A) | **block-collection-and-party** (authoring model + a rendered example) **and testing-blocks** for the visual reuse gate (rendered block vs. the Figma section screenshot). |
| A **new block** (3B) | **content-modeling** (design the authoring model), then **content-driven-development** (which runs **building-blocks** and **testing-blocks**). Do **not** hand-write block JS/CSS from this file. |
| **Default content** (3C) | **da-content** only (no block skills). |

Record the manifest as an evidence-bearing checklist and tick each item **only
after you actually invoked the skill** — "I know what it does" is not invocation,
and an un-invoked required skill means this phase is **not complete**:

- [ ] **da-content** reference docs loaded (`html-content.md` / `platform.md` / `media.md`)
- [ ] **block-collection-and-party** invoked for every reused block *(if any 3A)*
- [ ] **content-modeling** + **content-driven-development** invoked for every new block *(if any 3B)*
- [ ] **Default-content** sections authored via **da-content** alone — **no** block-building skills invoked for them *(if any 3C)*
- [ ] **testing-blocks** invoked — its browser render + visual comparison **is** the
      Stage B pre-publish check (Phase 5); a run with **no** browser available
      reports the page **preview-only, UNVERIFIED**, never "done".

If the environment genuinely cannot run a required skill (e.g. no browser for
testing-blocks), **say so explicitly in the report and mark the affected checks
unverified** — never silently substitute this file's summary and call it passed.

---

## Phase 3A — Map content into an EXISTING block

**Reuse gate — structure AND visual.** An existing block is a valid target
only when the section both (a) **fits the block's authoring model** (its
row/cell structure and field types) *and* (b) **matches the block's rendered
appearance** under the project theme, using only tokens and variants the block
already defines. Structural fit alone is **not** enough: if the section's
visual identity — bespoke layout, corner radius, shadow, decorative treatment —
lives in that block's own CSS, you cannot reproduce it without editing the
block (forbidden), so route the section to **Phase 3B** (new block). Global,
token-level differences (palette, fonts, type scale) do **not** break reuse —
they are absorbed once by retargeting the project's design tokens (see
Guardrails). **How to run the visual check — reuse testing-blocks, don't invent
one:** get a rendered example of the candidate block — its `liveExampleUrl`
(block-collection-and-party / block-inventory) or the project's own block
rendered at `localhost:3000` with the section's **actual** content — including
secondary text, captions, and CTAs over whatever background or media the block
places them on, not just placeholder cells — then follow **testing-blocks**'
browser/Playwright-MCP screenshot pass (mobile/tablet/desktop) and its "compare
implementation to design" step, comparing that screenshot against the Figma
**section screenshot** from Phase 1. Watch for treatments a block applies to
only its primary element: one that (say) whitens a heading over dark media but
leaves the supporting text and buttons at body color passes a structural check
yet renders that text illegibly — a divergence the token retheme cannot fix.
Divergence beyond what the token retheme explains ⇒ new block (or a new
variant), not reuse. This outcome is **blocking**: the section is not resolved
until its rendered look — that text included — is faithful, and the fix is a new
isolated block/variant, never an edit to the shared block. Recording the gap in
the plan and reusing the block anyway is a **plan note, not a fix** — the Phase 5
pre-publish gate treats such a box as failed.

Once the gate passes, **invoke block-collection-and-party** to learn the block's
authoring model (its examples show the row/cell structure and variants) — read
the block from the skill, don't guess its model from its CSS source. Then pour
the Figma content into that structure:

- **Text** → matching cells; preserve heading levels from the design.
- **Variants** → extra class tokens on the block (e.g. `cards highlight`).
  Only apply a variant the block actually defines. (Adding a *new* variant =
  modifying an existing block = Phase 3B, not 3A.)
- **Links/buttons** → a **standalone link** (the only content of its
  paragraph) auto-promotes to a button; wrap in `<strong>` for a primary
  button, `<em>` for secondary. Do not add `target="_blank"` (decoration
  handles external links). Validate the href's URL scheme and escape the
  link text/attributes before emitting — see Phase 4, *Sanitize everything
  derived from the design*. *(da-content html-content.md §8)*
- **Images** → Phase 4 (they need real URLs).

---

## Phase 3B — Create a NEW block (content + code)

Only for sections Phase 2 routed here (a needed block is missing, or an existing
block's look diverges) — the **3B** case. **Guardrails (strict):**

- Create **new, isolated block folders** only (`blocks/<new-name>/`).
- **Do NOT** skin this block by editing an existing block, `scripts.js`, or
  `head.html`, or by adding block-specific rules to global CSS — keep it
  self-contained under `blocks/<new-name>/`. *(Retargeting the project's
  global design tokens in `styles/styles.css` — the `:root` custom properties
  and base typography — is a separate, allowed project-theming step, not part
  of building this block; see Guardrails.)*
- New block **names and variant tokens** must obey EDS block-name rules
  (da-content html-content.md §3.3): lowercase alphanumeric + single hyphens,
  **no underscores, no double dashes, must not start with a digit**
  (`pricing-table` ✓, `pricing_table` / `2col` / `promo--wide` ✗). Names must
  be unique and not collide with existing blocks.

**Build route — invoke content-driven-development (don't hand-write the block).**
Build every new block by **invoking content-driven-development** — not by writing
block JS/CSS from scratch off this file's summary. It invokes **content-modeling**
(design the authoring model from the Figma structure/tokens) then
**building-blocks** and **testing-blocks**, and produces a self-contained
`blocks/<name>/` — no source URL, no installed substrate, no page chrome, and no
global styles. That is the route that honors the 3B guardrails above, and its
testing-blocks pass is the block's Stage B verification (Phase 5). Build a **bespoke, one-off** section
the same way — it is still an ordinary isolated block, and "one-off" changes
nothing about how it is generated.

> **Do not use snowflake here.** Snowflake converts an *already-rendered* page:
> it requires a reachable **Source URL**, **installs an overlay substrate** into
> the repo, and in block mode emits **header/footer fragments and global
> styles/tokens** — each of which violates this skill's constraints (isolated new
> block, don't touch globals, work from the **Figma frame**, not a live URL).
> Snowflake is the right tool for a *different* entry point — converting an
> existing static/rendered site — as noted under "When NOT to use".

Use the Figma design context/tokens from Phase 1 as the source of truth for
layout and styling. New-block **CSS must target structure, not authored
classes** — inline wrappers like `<span class="…">` are stripped inside block
cells at delivery (da-content html-content.md §3.9), so a class you emit in a
cell will not survive.

**Make the block responsive.** A Figma page frame is almost always a single
**desktop** width, but EDS pages are responsive. Author the block mobile-first
(or with explicit breakpoints) so a multi-column layout collapses to one column
on narrow viewports, and verify at mobile / tablet / desktop via
**testing-blocks** — don't ship a fixed desktop-width block. If the design
provides a **separate mobile frame**, use it to derive the breakpoint behavior
(what stacks, what hides, how type scales) — it's the *same page*, so it feeds
one responsive block, **not** a second page (see Inputs on frame variants).

The new block's code must be **committed and pushed to the deploy branch on
GitHub and built by Code Sync** before the page can render it — see Phase 5
(content+code).

---

## Phase 3C — Author DEFAULT CONTENT (no block)

For sections Phase 2 routed to default content — the **3C** case — emit standard
document elements directly inside the section `<div>` (see Phase 4 skeleton) — no
block wrapper:

- Headings `<h1>`–`<h6>` (preserve levels), paragraphs, lists, images.
- A **standalone link** in its own `<p>` becomes a button (`<strong>`/`<em>`
  for primary/secondary) — same rule as 3A.
- Do **not** add `class`, `id`, or `style` — decoration adds them at delivery.

*(da-content html-content.md §6)*

---

## Phase 4 — Generate DA body-fragment HTML (da-content)

Emit a **body fragment** (not a full HTML document) per **da-content**. **Invoke
da-content and load its `references/html-content.md`, `platform.md`, and
`media.md` now** — the rules quoted throughout this phase are reminders to jog the
right skill, not the source of truth. Subtle authoring rules (block-cell inline-
tag normalization, media MIME/extension derivation, metadata keys) live in those
docs; authoring from this summary alone is how they get missed. Write one file per
page to `content/<PATH>.html`.

**Mandatory skeleton** (da-content html-content.md §1–§2): wrap everything in
`<body>` with an (empty) `<header>`/`<footer>` and a `<main>`; **each section
is exactly one `<div>` directly inside `<main>`** — the `<div>` *is* the
section boundary (no `<hr>`). Do NOT emit `<!DOCTYPE>`, `<html>`, `<head>`,
`<script>`, `<style>`, `style=`, or `class=` on default content.

```html
<body>
  <header></header>
  <main>
    <div>
      <!-- section: default content and/or a block, in visual order -->
      <h1>Heading</h1>
      <p>Intro paragraph.</p>
      <div class="block-name variant">
        <div><div>cell</div><div>cell</div></div>
      </div>
    </div>
    <div>
      <!-- next section -->
    </div>
  </main>
  <footer></footer>
</body>
```

- **Sanitize everything derived from the design — text, attributes, links.**
  Figma text and layer names are untrusted input to the HTML you emit; treat
  them as data, never as markup:
  - **HTML-escape** every design-derived string before it lands in the document
    — `&`→`&amp;`, `<`→`&lt;`, `>`→`&gt;`, and inside attribute values also
    `"`→`&quot;` and `'`→`&#39;`. A heading `Tips & Tricks <Beta>` must serialize
    as `Tips &amp; Tricks &lt;Beta&gt;`, never as raw markup that can break the
    document or inject an element.
  - **Validate every link's URL scheme** against an allowlist — `http`, `https`,
    `mailto`, `tel`, or a root-relative (`/…`) path. **Reject `javascript:`,
    `data:`, `vbscript:`, and any other scheme** (a prototype link can carry
    anything): drop the href or ask the user — never emit it.
  - **Admit a Figma-derived class token only after block-name validation** — a
    layer/frame name becomes a block or variant class *only* once it passes the
    EDS name rules in Phase 3B (lowercase alphanumeric + single hyphens, no
    underscores/double-dashes, not digit-initial); never pass a raw layer name
    through as a class.
- **Blocks — canonical div form:** `<div class="block-name variant">`, each
  direct child `<div>` a row, each grandchild `<div>` a cell. The first class
  token is the block name (resolves to `blocks/<name>/<name>.{js,css}`).
  Multi-word variants hyphenate; multiple variants are separate class tokens.
  Max 4 cells per row; blocks cannot nest. *(html-content.md §3)*
- **Default content:** headings/paragraphs/lists/images/buttons live directly
  in the section `<div>`, outside any block. *(html-content.md §6)*
- **Icons — two non-interchangeable paths; never a stand-in glyph.** The
  `<span class="icon icon-<name>"></span>` convention resolves **only** to the
  project's Code Bus `/icons/<name>.svg`, so that SVG must be **committed to the
  repo `/icons/` folder and pushed on the deploy branch** (content+code path,
  same as block code) and return `200` on the branch host — uploading it to DA
  `/media` does **not** satisfy the span (it 404s and the icon silently vanishes).
  A DA-`/media` SVG must instead be referenced by **full URL on an `<img>`**, not
  an icon span. Get the real **SVG** in Phase 1; **never emit an emoji or Unicode
  glyph in place of a designed icon.** *(html-content.md §7)*
- **Images — MUST be full, fetchable URLs.** Figma render URLs expire, and
  **repo-relative paths (`/img/…`) render as `about:error`.** So: download the
  image bytes from Figma (Phase 1 asset URLs), **upload each binary to DA**
  (`PUT admin.da.live/source/{daOrg}/{daRepo}/<media-path>`), and reference
  `https://content.da.live/{daOrg}/{daRepo}/<media-path>`. External image URLs
  are also accepted (the preview sideloads them). Author a bare `<img alt="…">`
  and let the pipeline build the `<picture>`.
  - **Normalize format, extension, and MIME together — from the bytes, never the
    URL suffix.** Detect the real format from the image's magic bytes (or the
    asset's reported `format`), then make **all three agree**: the multipart
    `type=` MIME, the `<media-path>` file extension you PUT to, and the extension
    in the `content.da.live` URL you author. Design tools routinely export JPEG
    bytes under a `.png`-named asset; trusting the suffix gives you a `.png` path
    served as `image/jpeg` (or the reverse) — a latent corruption bug. A layer
    that *looks* vector (an icon, a logo, a shape) often comes back **rasterized**
    — `download_assets` returns it under `rawImages` with `svgAssets` empty — so a
    design that implies `.svg` can hand you PNG/JPEG bytes. Author each `<img>`
    extension from the bytes you actually downloaded, never from the layer's
    apparent type or name. Canonical
    mapping: JPEG→`.jpg`/`image/jpeg`, PNG→`.png`/`image/png`, WebP→`.webp`/
    `image/webp`, GIF→`.gif`/`image/gif`, SVG→`.svg`/`image/svg+xml`. If bytes
    and asset-reported format disagree, trust the bytes.
  *(html-content.md §9 + media.md)*
- **Section styling** → a `section-metadata` block **inside** the section
  (`Style` → CSS classes; other rows → `data-*`). *(html-content.md §4)*
- **Page metadata** → a single `metadata` block (exact class), placed as the
  **last element of the last section inside `<main>`** (never after `</main>`
  or in `<footer>`); keys like `title`, `description`, `image`, `template`,
  `theme`. **Author it from the design — don't leave it empty or a bare
  comment.** Derive `title` from the frame name or the page `<h1>`, `description`
  from the hero/intro copy (a concise real sentence, never lorem), and `image`
  from the primary/hero image's uploaded DA URL when the design has one. This
  block is **required** — the Phase 5 pre-publish gate blocks on its absence — so
  populate it rather than deferring it. If the design offers no usable
  title/description text, ask the user rather than inventing marketing copy.
  *(html-content.md §5)*

Inside block cells the pipeline runs a stricter inline-tag normalization than
for default content — `<span class>` is unwrapped (class lost), `<b>`→`<strong>`,
`<mark>`→`<em>`, etc. Restrict cell content to the html-content.md §3.9 preserve
list. A wrong metadata **key** or block **field** silently corrupts output;
when unsure, read da-content.

---

## Phase 5 — Deploy to DA

**If a DA MCP server is available in the session, use its tools** for auth and
source writes (da-auth and da-content both defer to it when present).
Otherwise use the Source API directly, below.

```bash
# Two identities — keep them separate. DA (Document Authoring) and GitHub are the
# same org/repo in the standard EDS setup, but nothing guarantees it, so never
# assume one from the other. DA endpoints (admin.da.live/source, content.da.live,
# da.live/edit) use the DA pair; the render host and admin.hlx.page (code, preview,
# live) use the GitHub pair.
DA_ORG=<da-org>        # Document Authoring org
DA_REPO=<da-repo>      # Document Authoring repo/site
GH_OWNER=<gh-owner>    # GitHub owner
GH_REPO=<gh-repo>      # GitHub repo
# In the standard setup all four match: DA_ORG=GH_OWNER=<owner>, DA_REPO=GH_REPO=<repo>.
BRANCH=<branch>        # git deploy ref (usually main). For content+code this MUST be
                       # the branch the new-block code was pushed to and Code Sync built.
BRANCH_HOST=${BRANCH//\//-}   # host label: slashes → dashes ('feature/x' → 'feature-x').
                              # Used BOTH for the aem.page/aem.live hostname AND as the ref
                              # segment in every admin.hlx.page path (code/preview/live): that
                              # ref is a SINGLE path segment, so a slashed branch ('figma/x')
                              # splits it and 404s — pass the dashed label ('figma-x'), which is
                              # what AEM actually resolves. Only git itself (push/checkout) uses
                              # the literal slashed $BRANCH. For a slash-free branch the two
                              # forms are identical, so $BRANCH_HOST is always the safe choice
                              # for admin.hlx.page.
P=<path-without-extension>
TOKEN="$DA_TOKEN"      # from da-auth; 401 w/ empty body ⇒ expired, re-auth

# Fail fast if the branch host would be unresolvable (>63 chars won't resolve).
host="$BRANCH_HOST--$GH_REPO--$GH_OWNER"
[ "${#host}" -le 63 ] || { echo "❌ branch host '$host' is ${#host} chars (>63) — won't resolve; use a shorter branch/repo/org"; exit 1; }

# --- checked-request helper: every call asserts its status; a bare `curl -sS`
#     exits 0 on 401/403/409/5xx, so an unchecked curl silently "succeeds" on a
#     failed write. req <expected-codes> <curl-args…>: prints the body, retries a
#     few times on network/429/5xx, and aborts (non-zero) on any other mismatch.
#     Use it for every PUT/POST below; if a DA MCP server is used instead, apply
#     the same rule — assert the returned status, don't assume success.
Source. Fiche rédigée par iadujour.fr à partir de : Frontmatter, sections et corps du SKILL.md, README du dépôt. Consultée le 10 novembre 2026. Dépôt : adobe/skills .

Dans l’annuaire

Cas d’usage et guides qui partagent un thème, un métier ou un outil avec cette skill.

ÉCHANGES

Commentaires.

Un retour d’expérience, une question, une précision sur cette skill ? Les commentaires sont relus avant publication.

Commentaires publiés

Chargement des commentaires…

    Laisser un commentaire

    Charte de modération. Chaque commentaire est relu avant publication. Sont refusés : l’incitation à la haine, les propos injurieux ou discriminatoires, la publicité, le spam et les liens promotionnels, le hors-sujet et les données personnelles d’autrui.