automattic/generating-patterns
Guidelines and examples for generating WordPress block patterns — load this when creating patterns for themes
npx skills add https://github.com/Automattic/studio --skill generating-patterns
Use this skill when generating block patterns for a theme.
Load this skill alongside creating-themes or editing-themes when patterns are part of the work.
Page-builder storefronts emit div-soup markup (no semantic <section> tags) and rich, repeated components that are easy to flatten into generic static/columns blocks. Don't. The section spec's Interaction model carries the real shape — honor it and pick the matching template in references/section-mapping.md:
project-card-grid/columns. Keep prices verbatim; link to the local product page or WooCommerce.★ glyph run so it survives without a JS widget; render the captured reviews as a static, mobile-safe grid. Review/testimonial text is NEVER synthesized — source-captured verbatim or placeholdered, full stop. It is sourced (in order) from spec.reviews (the deterministic review-extract.ts extractor populates it from the served HTML), the inline carousel markup in the captured HTML, embedded JSON (schema.org Review, warmup blobs), or the widget's plain-GET JSON API. "The reviews are a JS widget" is usually false — page-builder carousels render every slide inline. Only when ALL of those fail is the band placeholdered + flagged. See the review-grid template and *Missing-content fallback* in references/section-mapping.md.core/search input+button (forms have no backend in the replica) and stub the real integration in the framework-widget table.The extractor now captures page-builder CDN imagery (Replo assets.replocdn.com, Shogun, image proxies) regardless of host or file extension. The faithful imagery is in the media library — use it. Do not fall back to unrelated product photos for hero/lifestyle/app slots.
Every word of visible text a pattern emits — headings, subheads, body paragraphs, list items, button/label text, review quotes — MUST be the source's captured text, reproduced verbatim, OR a clearly-marked missing-content placeholder + run-report flag. Never synthesize, paraphrase, reword, "tighten," "improve," or invent copy to fill a slot. This is not limited to reviews; it governs *all* prose. Reproducing copy verbatim is the whole point of a replica — paraphrase is a fidelity lie that silently rewrites the source's own words.
Where the source text lives, in order:
spec.headings, spec.buttonLabels.spec.bodyText (every captured <p>/<li> text node, verbatim — see SectionSpec.bodyText). If a template slot wants body copy that is not in spec.bodyText, do NOT write your own — re-read the section's captured html/<slug>.html and pull the real line. If the source genuinely has no text for that slot, leave the slot out or use the missing-content placeholder; never fabricate.spec.reviews → inline carousel HTML → embedded JSON → widget GET API (see review-grid).Allowed differences from the raw source byte stream are only mechanical renderings: HTML-entity encoding (’, , &), whitespace collapse, and typographic-glyph folding (smart quotes ↔ straight, en/em dash ↔ hyphen, ellipsis char ↔ ...). Anything beyond that — a different word, a reordered clause, a "punchier" rewrite — is paraphrase and is rejected.
This is enforced deterministically: liberate_validate_artifacts checks emitted text against the captured source. Headings must each be contained in a single spec.expectedText entry; body paragraphs must be substantially contained in spec.expectedText ∪ spec.bodyText — a reworded paragraph HARD-FAILS the gate (not a warning). You may not bypass that gate. An earlier getsnooz build paraphrased section body copy ("Real fan-powered sound — no loops…", "Your best shut-eye, guaranteed.") and invented three testimonials while the real lines sat in the captured HTML the whole time — exactly the failure this rule and gate exist to stop.
If a section spec references an image that could not be captured (cross-origin, 403/expired CDN, rejected content-type), emit a sized placeholder that preserves the layout slot, flag it in run-report.json (details.provenanceFlags + bump summary.provenanceFlags), and never substitute an unrelated image as if it were the source. A confident wrong image hides the extraction failure and produces a plausible-but-wrong replica — the exact mistake behind the first getsnooz build.
The same rule governs all text (per the cardinal rule above), most acutely review/testimonial copy. Never synthesize, paraphrase, or invent review quotes, author names, ratings, category labels, headings, or body paragraphs. They are source-captured verbatim or rendered as a clearly-marked placeholder ([review text not captured], or for body copy [copy not captured]) plus a run-report flag — never fabricated, and never bypass the liberate_validate_artifacts provenance gate (text ⊆ captured source). An earlier getsnooz build invented three testimonials AND paraphrased section body copy, and manually skipped that gate; the real text was in the captured HTML the entire time. See *Missing-media fallback*, *Missing-content fallback (review/testimonial text)*, and the review-grid sourcing order in references/section-mapping.md.
patterns/ directoryhero-split.php, faq-accordion.php)spec.headings / spec.bodyText / spec.buttonLabels / spec.reviews (or placeholdered + flagged). Do not write your own headlines, body, or CTAs.<inner-blocks> or assume external contextEvery pattern file must begin with this PHP comment block:
<?php
/**
* Title: Hero Split
* Slug: theme-slug/hero-split
* Categories: featured, banner
* Keywords: hero, split, cta
* Block Types: core/template-part/content
*/
?>
Title: Human-readable name shown in the inserterSlug: Must be theme-slug/pattern-name using the theme's text domainCategories: Comma-separated list (use WordPress defaults: featured, banner, text, gallery, call-to-action, about, team, testimonials, contact, footer, header)Keywords: Comma-separated search terms for discoverabilityBlock Types: Optional — restricts where the pattern appearsWhen generating patterns for a landing page theme, include a varied set that covers the full page flow. Choose patterns that match the site type and audience — not every site needs the same set.
Recommended baseline (adapt per site type):
If a pattern reconstructs the site header (or a header part), it MUST mirror the SOURCE header:
core/image / core/site-logo), not core/site-title text and not a product image.core/navigation-links for the source's top-level primary menu only (label + href). NEVER use core/page-list — it dumps every published WP page (Sample Page, Checkout, account, recall pages) as junk that does not reflect the source's menu.@font-face → assets/fonts/ → theme.json fontFamilies).wp:buttons with clear, action-oriented text ("Get Started", "Book a Demo", "View Portfolio")Two-column split: heading + paragraph + CTA button on the left, full-height image on the right. Use wp:columns with a 55/45 or 60/40 split. The text column gets vertical centering; the image column uses object-fit: cover at full height.
Columns (align: full)
└── Column (width: 55%, verticalAlignment: center)
│ └── Heading (h1)
│ └── Paragraph
│ └── Buttons
└── Column (width: 45%)
└── Image (style: height 100%, object-fit: cover)
Alternate the position of text and image across consecutive sections to create a natural Z reading flow. Odd sections place text left / image right; even sections flip to image left / text right.
Section 1 — Columns (align: wide)
└── Column (text) | Column (image)
Section 2 — Columns (align: wide)
└── Column (image) | Column (text)
Section 3 — Columns (align: wide)
└── Column (text) | Column (image)
Use alternating background colors (light/dark) between sections to reinforce separation.
Three equal-width columns, each containing an icon or image, a heading, a paragraph, and an optional CTA. Use the equal-cards pattern from the card-layouts reference.
Columns (className: "equal-cards", align: wide)
└── Column (width: 33.33%, verticalAlignment: stretch)
│ └── Group
│ └── Image (icon or illustration)
│ └── Heading (h3)
│ └── Paragraph
│ └── Buttons (className: "cta-bottom") [optional]
└── Column (width: 33.33%, verticalAlignment: stretch)
│ └── Group
│ └── ...
└── Column (width: 33.33%, verticalAlignment: stretch)
└── Group
└── ...
Wrap each section in a wp:group with a contrasting background color. Pull colors from the theme palette — use has-{color}-background-color classes or inline backgroundColor attributes. Alternate between the theme's base and contrast colors.
Group (align: full, backgroundColor: base)
└── [section content]
Group (align: full, backgroundColor: contrast)
└── [section content with inverted text color]
Group (align: full, backgroundColor: base)
└── [section content]
A fixed-position bar at the bottom of the viewport with a single CTA. Use a full-width Group with custom CSS class for sticky positioning.
Group (align: full, className: "sticky-cta")
└── Buttons (layout: center)
└── Button ("Get Started Now")
Required CSS (style.css):
.sticky-cta {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 50;
padding: 1rem;
}
Use the wp:details block for collapsible FAQ items inside a constrained-width Group.
Group (align: wide, layout: constrained)
└── Heading (h2, "Frequently Asked Questions")
└── Details (summary: "Question one?")
│ └── Paragraph (answer)
└── Details (summary: "Question two?")
│ └── Paragraph (answer)
└── Details (summary: "Question three?")
└── Paragraph (answer)
A single row of client or partner logos. Use a wp:columns block with evenly spaced columns, each containing a centered image. Keep logos grayscale or muted to avoid visual clutter.
Group (align: wide)
└── Paragraph (align: center, "Trusted by leading companies")
└── Columns (align: wide)
└── Column (width: 16.66%) → Image (logo, centered)
└── Column (width: 16.66%) → Image (logo, centered)
└── Column (width: 16.66%) → Image (logo, centered)
└── Column (width: 16.66%) → Image (logo, centered)
└── Column (width: 16.66%) → Image (logo, centered)
└── Column (width: 16.66%) → Image (logo, centered)
Not every site needs the same patterns. Adapt the set to the context:
When invoked as a builder subagent by the /liberate or /replicate-with-blocks orchestrator, you MUST return your output as a structured JSON envelope — not free-form prose:
{
"patterns": [
{ "slug": "theme-slug/hero-split", "php": "<?php\n/**\n * Title: Hero Split\n * ...\n */\n?>\n<!-- wp:group ... -->" }
],
"sitewideFlags": [],
"notes": []
}
patterns — array of objects with slug (the pattern's registered slug, matching the file header) and php (the full pattern file contents as a string, including the PHP comment header).sitewideFlags — optional array of strings naming sitewide concerns (e.g. "commercial-font-reckless", "missing-mobile-hero-image").notes — optional array of strings with builder observations or deferred items.Section markup comes from the references/section-mapping.md catalog. Emit all source-derived text as literal, pre-escaped HTML in the markup itself — HTML-entity-escape &, <, > (and quotes inside attributes) at authoring time. Do not wrap visible text in <?php echo esc_html(…) ?> / esc_attr(…): liberate_validate_artifacts sanctions exactly TWO PHP forms — the pattern's doc-comment header and <?php echo esc_url( get_theme_file_uri('assets/…') ); ?> for theme-shipped asset paths (see section-mapping.md) — and treats ANY other <?php, including esc_html('literal'), as injection. So: visible text → literal escaped HTML; theme-asset URLs → esc_url( get_theme_file_uri(…) ); nothing else emits PHP. The result must pass liberate_validate_artifacts (escaping/injection + provenance). A malformed or partial return — missing patterns, wrong types, raw <?php outside the two sanctioned forms — is treated as a builder failure and triggers a retry or sequential fallback. Never return partial results silently.
Take automattic/generating-patterns from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.