references/anti-patterns.md
# Anti-Patterns
Common mistakes that produce generic, broken, or inaccessible patterns.
## Design Quality Checklist
- [ ] **Not generic**: pattern makes at least 3 distinctive design choices
- [ ] **Layout variety**: not defaulting to 3 equal columns or uniform symmetric layouts
- [ ] **Color rhythm**: sections alternate or vary backgrounds — not all the same
- [ ] **Typography contrast**: headings clearly distinct from body (size, family, or weight)
- [ ] **Spatial intention**: padding and gaps vary by context, not uniform everywhere
- [ ] **Meaningful content**: placeholder text reflects real use, buttons describe actions
- [ ] **Specific structure**: comparisons, timelines, schedules, pricing, menus, and documentation cards have labels that make sense without relying on visual position alone
- [ ] **Restrained but distinctive**: corporate/professional patterns still include a clear hierarchy, accent, or layout choice; playful patterns do not become a one-note palette or repeated gradient treatment
## Technical Anti-Patterns
### Inline Styles and Custom CSS
```html
<!-- WRONG: inline <style> tag -->
<style>.my-custom-hero { background: linear-gradient(...); }</style>
<!-- WRONG: custom CSS class not from blocks -->
<div class="my-custom-card">
<!-- CORRECT: use block attributes -->
<!-- wp:group {"style":{"color":{"gradient":"linear-gradient(...)"}}} -->
```
### Hardcoded Colors
```json
// WRONG: hardcoded hex when a preset exists
{"style":{"color":{"background":"#000000","text":"#ffffff"}}}
// CORRECT: use theme presets for theme compatibility
{"backgroundColor":"contrast","textColor":"base"}
```
Use hardcoded values only when no suitable `preset` exists and the design requires a specific color.
### Missing Escaping and i18n
```php
// WRONG: raw text, not translatable, not escaped
<h2>Our Services</h2>
// WRONG: translatable but not escaped
<h2><?php _e( 'Our Services', 'theme-slug' ); ?></h2>
// CORRECT: escaped and translatable
<h2><?php esc_html_e( 'Our Services', 'theme-slug' ); ?></h2>
```
### Query-Dependent PHP
```php
// WRONG: runs at registration time, not render time
<?php $recent = get_posts( array( 'numberposts' => 3 ) ); ?>
// CORRECT: use Query Loop block for dynamic content
<!-- wp:query {"query":{"perPage":3,"postType":"post"}} -->
```
### Unclosed or Mismatched Blocks
```html
<!-- WRONG: missing closing comment -->
<!-- wp:group -->
<div class="wp-block-group">
<!-- wp:heading -->
<h2>Title</h2>
<!-- /wp:heading -->
<!-- Missing: /wp:group -->
<!-- WRONG: mismatched nesting -->
<!-- wp:group -->
<!-- wp:columns -->
<!-- /wp:group -->
<!-- /wp:columns -->
```
### Placeholder Image URLs
```html
<!-- WRONG: external placeholder service -->
<img src="https://via.placeholder.com/800x400" alt=""/>
<!-- CORRECT: use theme assets or descriptive placeholder -->
<img src="<?php echo esc_url( get_theme_file_uri( 'assets/images/placeholder.webp' ) ); ?>"
alt="<?php esc_attr_e( 'Featured image', 'theme-slug' ); ?>"/>
```
## Accessibility Failures
### Missing Alt Text
```html
<!-- WRONG -->
<!-- wp:image -->
<figure class="wp-block-image"><img src="photo.jpg" alt=""/></figure>
<!-- /wp:image -->
<!-- CORRECT: descriptive alt for informational images -->
<!-- wp:image {"alt":"Team members collaborating around a whiteboard"} -->
```
Decorative images (backgrounds, dividers) can use empty alt, but informational images must describe content.
### Skipped Heading Levels
```html
<!-- WRONG: jumps from h2 to h5 -->
<!-- wp:heading {"level":2} --> Section Title
<!-- wp:heading {"level":5} --> Subsection
<!-- CORRECT: sequential levels -->
<!-- wp:heading {"level":2} --> Section Title
<!-- wp:heading {"level":3} --> Subsection
```
Patterns should use `h2` as the top level (h1 is the page title). Descend sequentially: h2 → h3 → h4.
### Insufficient Color Contrast
When using dark backgrounds, verify text presets provide adequate contrast:
- `{"backgroundColor":"contrast","textColor":"base"}` — typically safe (dark bg, light text)
- Custom color combinations must meet WCAG 2.1 AA (4.5:1 for body text, 3:1 for large text)
### Non-Descriptive Button Text
```html
<!-- WRONG -->
<a class="wp-block-button__link">Click Here</a>
<a class="wp-block-button__link">Read More</a>
<!-- CORRECT: describes the action or destination -->
<a class="wp-block-button__link"><?php esc_html_e( 'View Our Services', 'theme-slug' ); ?></a>
<a class="wp-block-button__link"><?php esc_html_e( 'Download the Report', 'theme-slug' ); ?></a>
```
### Missing ARIA on Decorative Elements
Spacer blocks should include `aria-hidden="true"` (WordPress adds this automatically). If generating custom separator patterns, ensure decorative elements don't announce to screen readers.
## Technical Validation Checklist
- [ ] Every `<!-- wp:block -->` has matching `<!-- /wp:block -->`
- [ ] JSON in block comments is valid (no trailing commas, strings double-quoted)
- [ ] All user-visible strings use `esc_html_e()` or `esc_html__()`
- [ ] All URLs use `esc_url()`
- [ ] All attribute values with translatable text use `esc_attr_e()` or `esc_attr__()`
- [ ] Informational images have descriptive translated alt text; decorative images use empty alt text intentionally
- [ ] Heading levels are sequential (h2 → h3 → h4, never skip)
- [ ] Preset slugs are valid defaults or documented as theme-specific
- [ ] `Slug` in header uses correct namespace: `theme-slug/pattern-name`
- [ ] No inline `<style>`, no `<script>`, no custom CSS classes
- [ ] No query-dependent PHP functions
- [ ] Button/link labels are action-specific; avoid vague labels such as "Click Here" or "Read More"
- [ ] Updating an existing pattern preserves the `Slug` unless intentionally creating a new pattern
references/block-markup-reference.md
# Block Markup Reference
## Comment Syntax
WordPress blocks are serialized as HTML comments with JSON attributes.
```html
<!-- wp:block-name {"attr":"value"} -->
<div class="wp-block-block-name">Inner content</div>
<!-- /wp:block-name -->
<!-- wp:block-name {"attr":"value"} /--> (self-closing, no inner content)
```
Rules:
- Opening tag: `<!-- wp:namespace/name {JSON} -->`
- Core blocks omit namespace: `<!-- wp:heading -->` (not `core/heading`)
- JSON must be valid — no trailing commas, strings double-quoted
- Self-closing blocks (spacer, separator, image with no caption): end with `/-->`
- Every opening comment **must** have a matching closing comment
## Most-Used Blocks in Patterns
### Layout Blocks
**Group** — primary container, supports all layout types:
```html
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- inner blocks -->
</div>
<!-- /wp:group -->
```
Layout types:
- `{"type":"constrained"}` — centered with max-width (default for sections)
- `{"type":"constrained","contentSize":"800px","wideSize":"1200px"}` — custom widths
- `{"type":"flex","flexWrap":"nowrap"}` — horizontal row
- `{"type":"flex","orientation":"vertical"}` — vertical stack
- `{"type":"grid","columnCount":3}` — CSS grid with fixed columns
- `{"type":"grid","minimumColumnWidth":"250px"}` — responsive auto-fill grid
Tag name override: `{"tagName":"section"}`, `{"tagName":"header"}`, `{"tagName":"footer"}`
**Columns / Column:**
```html
<!-- wp:columns -->
<div class="wp-block-columns">
<!-- wp:column {"width":"66.66%"} -->
<div class="wp-block-column" style="flex-basis:66.66%">
<!-- inner blocks -->
</div>
<!-- /wp:column -->
<!-- wp:column {"width":"33.33%"} -->
<div class="wp-block-column" style="flex-basis:33.33%">
<!-- inner blocks -->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
```
### Content Blocks
**Heading:**
```html
<!-- wp:heading {"level":2,"fontSize":"x-large"} -->
<h2 class="wp-block-heading has-x-large-font-size">Heading text</h2>
<!-- /wp:heading -->
```
**Paragraph:**
```html
<!-- wp:paragraph {"fontSize":"medium","textColor":"contrast"} -->
<p class="has-contrast-color has-text-color has-medium-font-size">Body text</p>
<!-- /wp:paragraph -->
```
**Image:**
```html
<!-- wp:image {"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full">
<img src="https://example.com/image.jpg" alt="Descriptive alt text"/>
</figure>
<!-- /wp:image -->
```
**Cover:**
```html
<!-- wp:cover {"url":"https://example.com/bg.jpg","dimRatio":60,"overlayColor":"contrast","minHeight":500,"minHeightUnit":"px","layout":{"type":"constrained"}} -->
<div class="wp-block-cover" style="min-height:500px">
<span aria-hidden="true" class="wp-block-cover__background has-contrast-background-color has-background-dim-60 has-background-dim"></span>
<img class="wp-block-cover__image-background" alt="" src="https://example.com/bg.jpg" data-object-fit="cover"/>
<div class="wp-block-cover__inner-container">
<!-- inner blocks -->
</div>
</div>
<!-- /wp:cover -->
```
**Buttons / Button:**
```html
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
<div class="wp-block-buttons">
<!-- wp:button {"backgroundColor":"primary","textColor":"base"} -->
<div class="wp-block-button"><a class="wp-block-button__link has-base-color has-primary-background-color has-text-color has-background wp-element-button">Click me</a></div>
<!-- /wp:button -->
<!-- wp:button {"className":"is-style-outline"} -->
<div class="wp-block-button is-style-outline"><a class="wp-block-button__link wp-element-button">Secondary</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
```
**Spacer:**
```html
<!-- wp:spacer {"height":"var:preset|spacing|50"} -->
<div style="height:var(--wp--preset--spacing--50)" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->
```
**Separator:**
```html
<!-- wp:separator {"backgroundColor":"contrast","className":"is-style-wide"} -->
<hr class="wp-block-separator has-text-color has-contrast-color has-alpha-channel-opacity has-contrast-background-color has-background is-style-wide"/>
<!-- /wp:separator -->
```
**Media & Text:**
```html
<!-- wp:media-text {"mediaPosition":"right","mediaType":"image","mediaWidth":40} -->
<div class="wp-block-media-text has-media-on-the-right is-stacked-on-mobile" style="grid-template-columns:auto 40%">
<div class="wp-block-media-text__content">
<!-- inner blocks -->
</div>
<figure class="wp-block-media-text__media">
<img src="https://example.com/image.jpg" alt="Description"/>
</figure>
</div>
<!-- /wp:media-text -->
```
**Query Loop (post listing):**
```html
<!-- wp:query {"queryId":0,"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","inherit":false}} -->
<div class="wp-block-query">
<!-- wp:post-template {"layout":{"type":"grid","columnCount":3}} -->
<!-- wp:post-featured-image {"isLink":true} /-->
<!-- wp:post-title {"isLink":true,"fontSize":"large"} /-->
<!-- wp:post-excerpt {"excerptLength":20} /-->
<!-- /wp:post-template -->
</div>
<!-- /wp:query -->
```
## Style Attribute Structure
The `style` attribute holds custom values (not preset slugs):
```json
{
"style": {
"spacing": {
"padding": {"top":"var:preset|spacing|50","right":"var:preset|spacing|50","bottom":"var:preset|spacing|50","left":"var:preset|spacing|50"},
"margin": {"top":"0","bottom":"0"},
"blockGap": "var:preset|spacing|30"
},
"border": {
"radius": "8px",
"width": "1px",
"color": "var:preset|color|contrast",
"style": "solid"
},
"color": {
"background": "#1a1a2e",
"text": "#ffffff",
"gradient": "linear-gradient(135deg,rgb(6,147,227) 0%,rgb(155,81,224) 100%)"
},
"typography": {
"fontSize": "clamp(1rem, 2vw, 1.5rem)",
"lineHeight": "1.4",
"letterSpacing": "-0.02em"
}
}
}
```
Preset reference syntax in style values: `var:preset|{type}|{slug}` (not CSS `var()`)
## Preset Class Naming Convention
When using preset slugs (not inline style), blocks get CSS classes:
- `"backgroundColor":"primary"` → `has-primary-background-color has-background`
- `"textColor":"contrast"` → `has-contrast-color has-text-color`
- `"fontSize":"large"` → `has-large-font-size`
- `"fontFamily":"heading"` → `has-heading-font-family`
- `"gradient":"vivid-cyan-blue-to-vivid-purple"` → `has-vivid-cyan-blue-to-vivid-purple-gradient-background has-background`
## Block Locking
Prevent users from modifying pattern structure:
```json
{
"lock": {"move": true, "remove": true}
}
```
On container blocks, `templateLock` constrains children:
- `"templateLock":"all"` — no insert, move, or remove
- `"templateLock":"insert"` — no adding/removing, can move
- `"templateLock":"contentOnly"` — only text/media editable, structure locked
## Block Alignment
Use `align` attribute for wide/full-width:
```json
{"align":"wide"} <!-- respects wideSize -->
{"align":"full"} <!-- breaks out to viewport edge -->
```
references/design-with-tokens.md
# Design with Tokens
How to translate design intent into WordPress block attributes using theme.json presets.
## Design Decisions
### Purpose
What does this pattern achieve for the end user? A hero converts visitors. A testimonial grid builds trust. A pricing table drives comparison. Let purpose drive every subsequent choice.
### Tone
| Tone | Color Strategy | Typography | Layout |
|------|---------------|------------|--------|
| **Bold/energetic** | High contrast, accent backgrounds, gradients | XX-large headings, tight letter-spacing, uppercase accents | Full-width, asymmetric columns, large padding |
| **Minimal/refined** | Base + contrast only, subtle tertiary sections | Restrained sizes, generous line-height | Constrained width, generous whitespace, centered |
| **Editorial/magazine** | Dark sections alternating with light | Mixed font families, varied heading scales | Asymmetric splits (66/33), media-text blocks |
| **Playful/creative** | Multiple accent colors, bright backgrounds | Large display sizes, varied weights | Grid layouts, unexpected column ratios, rounded corners |
| **Corporate/professional** | Neutral palette, primary for CTAs only | Consistent scale, body font dominant | Equal columns, structured grid, minimal decoration |
### Spatial Composition
Primary layout strategies:
- **Constrained centered** — classic content width with wide breakouts
- **Full-width sections** — alternating background bands
- **Asymmetric split** — 60/40 or 70/30 columns with content + media
- **Grid** — block-native grid or columns layouts for cards, team members, portfolio items
- **Stacked vertical** — flex column with varied spacing for editorial feel
### Typography Hierarchy
Plan your type scale before writing markup:
- **Hero heading**: `fontSize:"xx-large"` + `fontFamily:"heading"` + tight `lineHeight`
- **Section heading**: `fontSize:"x-large"` + `fontFamily:"heading"`
- **Subtitle/lead**: `fontSize:"large"` + `textColor:"secondary"` or `fontFamily:"body"`
- **Body**: `fontSize:"medium"` or default
- **Caption/meta**: `fontSize:"small"` + `textColor:"secondary"`
Add at least one typographic accent:
- Uppercase + letter-spacing for labels: `{"style":{"typography":{"textTransform":"uppercase","letterSpacing":"0.1em"}}}`
- Tight letter-spacing on display headings: `{"style":{"typography":{"lineHeight":"1.1"}}}`
- Italic for pull quotes
- Monospace for technical/code content
- Generous leading on body: `{"style":{"typography":{"lineHeight":"1.8"}}}`
### Color Strategy
Plan section-by-section color flow:
- **Light section**: `base` background, `contrast` text (default)
- **Dark section**: `contrast` background, `base` text (inverted)
- **Accent section**: `primary` or `tertiary` background
- **Gradient section**: cover block with gradient overlay
A pattern with multiple sections should vary backgrounds — don't use the same background for every section.
## Core Principle
In block patterns, **all visual design** is expressed through:
1. **`preset` slugs** — reference theme.json values by name (preferred)
2. **Style attributes** — inline custom values in block JSON (fallback)
3. **Block nesting** — layout and composition via container blocks
Never use inline `<style>` tags, custom CSS classes not generated by blocks, or arbitrary HTML outside block wrappers.
## theme.json Preset Mapping
### Color Palette (Default WP 6.7+ Presets)
| Slug | Typical Use | Design Role |
|------|-------------|-------------|
| `base` | Background | Page/section background |
| `contrast` | Text on base | Primary text, dark sections |
| `primary` | Brand accent | CTAs, links, highlights |
| `secondary` | Supporting accent | Tags, secondary buttons |
| `tertiary` | Subtle background | Cards, alternating sections |
Usage in block attributes:
```json
{"backgroundColor":"tertiary","textColor":"contrast"}
```
Custom themes define their own slugs. Patterns for distribution should use generic slugs above or provide fallback hex values.
### Font Size Presets
| Slug | CSS Variable | Typical Use |
|------|-------------|-------------|
| `small` | `--wp--preset--font-size--small` | Captions, metadata |
| `medium` | `--wp--preset--font-size--medium` | Body text emphasis |
| `large` | `--wp--preset--font-size--large` | Subheadings, intro text |
| `x-large` | `--wp--preset--font-size--x-large` | Section headings |
| `xx-large` | `--wp--preset--font-size--xx-large` | Hero headings, display |
Usage: `{"fontSize":"xx-large"}` on heading/paragraph blocks.
For custom fluid sizes in style attribute:
```json
{"style":{"typography":{"fontSize":"clamp(2rem, 4vw, 4.5rem)"}}}
```
### Font Family Presets
Common theme slugs:
- `"fontFamily":"heading"` — display/heading typeface
- `"fontFamily":"body"` — body text typeface
- `"fontFamily":"system-sans-serif"` — system fallback
- `"fontFamily":"monospace"` — code/technical
### Spacing Scale
Default spacing presets use numeric slugs `20` through `80`:
| Slug | CSS Variable | Scale Position |
|------|-------------|----------------|
| `20` | `--wp--preset--spacing--20` | Extra small |
| `30` | `--wp--preset--spacing--30` | Small |
| `40` | `--wp--preset--spacing--40` | Medium (default) |
| `50` | `--wp--preset--spacing--50` | Large |
| `60` | `--wp--preset--spacing--60` | Extra large |
| `70` | `--wp--preset--spacing--70` | 2X large |
| `80` | `--wp--preset--spacing--80` | 3X large |
Reference in style attributes: `"var:preset|spacing|50"`
## Design-to-Attribute Translation
| Design Intent | Block Attribute |
|---------------|----------------|
| Large hero heading | `{"level":1,"fontSize":"xx-large","fontFamily":"heading"}` on `wp:heading` |
| Muted subtitle | `{"fontSize":"medium","textColor":"secondary"}` on `wp:paragraph` |
| Section background | `{"backgroundColor":"tertiary"}` on outer `wp:group` |
| Dark section | `{"backgroundColor":"contrast","textColor":"base"}` on `wp:group` |
| Card with padding | `{"style":{"spacing":{"padding":{"top":"var:preset|spacing|50",...}}}}` on `wp:group` |
| Accent border left | `{"style":{"border":{"left":{"width":"4px","color":"var:preset|color|primary"}}}}` |
| Rounded corners | `{"style":{"border":{"radius":"12px"}}}` on `wp:group` or `wp:image` |
| Primary CTA button | `{"backgroundColor":"primary","textColor":"base"}` on `wp:button` |
| Outline button | `{"className":"is-style-outline"}` on `wp:button` |
| Centered section | `{"layout":{"type":"constrained"},"align":"full"}` on `wp:group` |
## Spatial Rhythm Techniques
### Vertical Rhythm
Use consistent spacing presets on section groups:
```json
{"style":{"spacing":{"padding":{"top":"var:preset|spacing|60","bottom":"var:preset|spacing|60"}}}}
```
Vary `blockGap` between inner elements for hierarchy:
- Tight grouping (related items): `"blockGap":"var:preset|spacing|20"`
- Standard flow: `"blockGap":"var:preset|spacing|40"`
- Generous breathing room: `"blockGap":"var:preset|spacing|60"`
### Horizontal Composition
- **Asymmetric columns**: `{"width":"66.66%"}` + `{"width":"33.33%"}` — avoid always using equal splits
- **Offset content**: Use constrained layout with custom `contentSize` narrower than `wideSize`
- **Grid with varied spans**: CSS grid on Group with `columnCount` and child `columnSpan`
### Creating Visual Weight
- **Large padding** on groups creates breathing room and perceived importance
- **Contrast inversion** (dark bg + light text) draws attention to sections
- **Border accents** (left border, bottom border) create hierarchy without heaviness
- **Spacer blocks** between sections: use `preset` spacing, not arbitrary pixel values
## Gradient and Shadow Presets
Gradients in attributes:
```json
{"gradient":"vivid-cyan-blue-to-vivid-purple"}
```
Or custom in style:
```json
{"style":{"color":{"gradient":"linear-gradient(135deg,#1a1a2e 0%,#16213e 100%)"}}}
```
Shadow presets (WP 6.7+):
```json
{"shadow":"var:preset|shadow|natural"}
```
Common: `natural`, `deep`, `sharp`, `outlined`, `crisp`
## Duotone on Images
Apply duotone filter to images and covers:
```json
{"style":{"color":{"duotone":["#000000","#ffffff"]}}}
```
Or use preset: `{"style":{"color":{"duotone":"var:preset|duotone|dark-grayscale"}}}`
references/example-prompts.md
# Example Prompts
## Hero Section
> "Create a bold hero pattern with a large heading, subtitle, and two CTA buttons. Dark background, full-width, for a creative agency theme."
Expected: Cover or Group block with contrast bg, constrained inner, heading with xx-large + heading font, paragraph with secondary color, Buttons with primary + outline styles.
## Testimonial Grid
> "Create a 3-column testimonial grid with avatar, quote, name, and role. Alternating card backgrounds."
Expected: Group wrapper, block-native grid or Columns layout (3 columns, responsive), inner Group cards with varied tertiary/base backgrounds, Image block for avatar (rounded border-radius), Paragraph for quote (italic), Heading h3 for name, Paragraph small for role.
## Blog Post Listing
> "Create a starter page pattern for a blog index with featured post hero and 3-column grid of recent posts below."
Expected: `Block Types: core/post-content` header, Query Loop for featured post (perPage 1, large layout), second Query Loop for grid (perPage 3, grid layout with post-template), clear visual separation between sections.
## Footer with Columns
> "Create a 4-column footer pattern with logo, navigation links, contact info, and social icons. Dark background."
Expected: `Block Types: core/template-part/footer` header, Group full-width with contrast bg, Columns (4), Site Logo block, Navigation or list blocks, Paragraph blocks for contact, Social Icons block. `Inserter: no`.
references/pattern-categories-and-types.md
# Pattern Categories and Types
## Default Pattern Categories
WordPress registers these categories by default:
| Slug | Label | Typical Content |
|------|-------|-----------------|
| `banner` | Banner | Hero sections, full-width announcements |
| `buttons` | Buttons | Button groups and CTA layouts |
| `call-to-action` | Call to Action | Conversion-focused sections |
| `columns` | Columns | Multi-column layouts |
| `contact` | Contact | Contact forms, info, maps |
| `featured` | Featured | Highlighted content sections |
| `footer` | Footer | Site footer layouts |
| `gallery` | Gallery | Image galleries and grids |
| `header` | Header | Site header/navigation layouts |
| `media` | Media | Image/video showcase sections |
| `posts` | Posts | Blog post listing layouts |
| `pricing` | Pricing | Pricing tables and comparison |
| `services` | Services | Service/feature showcases |
| `team` | Team | Team member displays |
| `testimonials` | Testimonials | Reviews, quotes, social proof |
| `text` | Text | Text-focused sections and articles |
| `about` | About | About page sections |
| `portfolio` | Portfolio | Portfolio/work showcases |
| `newsletter` | Newsletter | Email signup sections |
| `query` | Query | Custom post queries/loops |
### Category Selection Guidance
- Use **multiple categories** when a pattern fits more than one (e.g., `banner, call-to-action`)
- A hero section with signup form → `banner, call-to-action, newsletter`
- A team grid with bios → `team, about`
- A blog layout → `posts, query`
## Starter Patterns (Page Patterns)
Starter patterns appear when a user creates a new page, offering full-page layout starting points.
Register by setting `Block Types` to `core/post-content`:
```php
<?php
/**
* Title: About Page
* Slug: theme-slug/page-about
* Categories: about, featured
* Block Types: core/post-content
* Post Types: page
*/
?>
```
Starter patterns should be **complete page layouts** — not partial sections.
Include `Block Types: core/post-content` unless the pattern is specifically for a template or template part.
## Template Patterns
Patterns that suggest themselves when editing a specific template type in the Site Editor.
Register with `Template Types`:
```php
<?php
/**
* Title: Blog Home Template
* Slug: theme-slug/template-home
* Categories: posts
* Template Types: home, front-page
* Inserter: no
*/
?>
```
### Supported Template Types
| Type | Description |
|------|-------------|
| `404` | 404 error page |
| `archive` | Archive (category, tag, date) |
| `author` | Author archive |
| `category` | Category archive |
| `date` | Date-based archive |
| `front-page` | Static front page |
| `home` | Blog home / posts page |
| `index` | Default fallback template |
| `page` | Single page |
| `search` | Search results |
| `single` | Single post |
| `singular` | Single post or page |
| `tag` | Tag archive |
| `taxonomy` | Custom taxonomy archive |
| `attachment` | Attachment page |
| `comments` | Comments template section |
## Query Loop Patterns
Use `core/query` with `core/post-template`, post title/excerpt/date/featured image blocks, **pagination** when needed, and **`core/query-no-results`** where relevant.
For archive, search, category, and author templates, **prefer inherited query context** (`"inherit":true`) instead of custom PHP or hardcoded query args.
For CPT or event listings, confirm the post type slug, taxonomy/date assumptions, and available blocks before generating markup.
## Comparison, Pricing, Timeline, and Schedule Patterns
Use clear headings, list/table or labeled-card structure, and non-color-only emphasis for featured states.
## Social, Navigation, and Search Patterns
Use native blocks and verify accessible labels, link text, and search context. In 404 patterns, pair the Search block with recovery copy that explains what the user can try next.
## Block Type Connections
Connect patterns to specific block types so they appear as options for those blocks:
| Block Type | Use Case |
|------------|----------|
| `core/post-content` | Starter page patterns |
| `core/template-part/header` | Header template part patterns |
| `core/template-part/footer` | Footer template part patterns |
| `core/query` | Query loop layout variations |
| `core/columns` | Column layout starting points |
Example — header template part pattern:
```php
<?php
/**
* Title: Header with Navigation
* Slug: theme-slug/header-nav
* Categories: header
* Block Types: core/template-part/header
* Inserter: no
*/
?>
```
Setting `Inserter: no` hides these from the general pattern inserter — they only appear in context (e.g., when choosing a header template part design).
## Hiding Patterns from Inserter
Use `Inserter: no` for:
- Template patterns (shown only in Site Editor template selection)
- Template part patterns (shown only when choosing template part designs)
- Internal patterns used by other patterns or theme code
These patterns are still registered and usable — just not browsable in the general inserter.
references/pattern-registration.md
# Pattern Registration
## Auto-Registration via `/patterns/` Directory
WordPress 6.0+ automatically registers patterns from `.php` files in the theme's `patterns/` directory.
### File Header (Required Fields)
```php
<?php
/**
* Title: Hero with Call to Action
* Slug: theme-slug/hero-cta
* Categories: banner, call-to-action
*/
?>
<!-- wp:group ... -->
```
### All Header Fields
| Field | Required | Description | Example |
|-------|----------|-------------|---------|
| `Title` | Yes | Display name in inserter | `Hero with Call to Action` |
| `Slug` | Yes | Unique identifier (`namespace/name`) | `theme-slug/hero-cta` |
| `Categories` | Yes | Comma-separated category slugs | `banner, call-to-action` |
| `Description` | No | Tooltip/help text in inserter | `A full-width hero section` |
| `Viewport Width` | No | Preview width in inserter (px) | `1400` |
| `Inserter` | No | Show in inserter (`yes`/`no`) | `no` (hides from inserter) |
| `Keywords` | No | Comma-separated search keywords | `hero, header, banner` |
| `Block Types` | No | Blocks this pattern replaces | `core/post-content` |
| `Post Types` | No | Restrict to post types | `page, wp_template` |
| `Template Types` | No | Template types this pattern suggests for | `home, front-page` |
### File Structure
```
my-theme/
patterns/
hero-cta.php
testimonial-grid.php
footer-columns.php
theme.json
style.css
...
```
File names are descriptive but don't affect registration — the `Slug` header is the identifier.
## PHP in Pattern Files
Pattern files execute PHP at registration time (not render time). This is critical:
### Safe Functions
```php
<?php esc_html_e( 'Read More', 'theme-textdomain' ); ?>
<?php esc_attr_e( 'Submit', 'theme-textdomain' ); ?>
<?php echo esc_url( get_theme_file_uri( 'assets/images/placeholder.webp' ) ); ?>
<?php echo esc_attr( get_theme_file_uri( 'assets/images/bg.jpg' ) ); ?>
```
Always use:
- `esc_html_e()` / `esc_html__()` for visible text (i18n + escape)
- `esc_attr_e()` / `esc_attr__()` for attribute values (i18n + escape)
- `esc_url()` for URLs
- `get_theme_file_uri()` for theme asset paths
### Unsafe — Do NOT Use
```php
// WRONG: query-dependent — runs at init, not render
<?php $posts = get_posts(); ?>
<?php the_title(); ?>
<?php wp_get_current_user(); ?>
<?php is_admin(); ?>
```
Pattern PHP executes once when the pattern is registered (during `init`), not when the page renders. Any dynamic runtime data will be stale or unavailable.
### i18n Requirements
All user-visible strings must be translatable:
```php
<!-- wp:heading -->
<h2 class="wp-block-heading"><?php esc_html_e( 'Our Services', 'theme-textdomain' ); ?></h2>
<!-- /wp:heading -->
<!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button"><?php esc_html_e( 'Get Started', 'theme-textdomain' ); ?></a></div>
<!-- /wp:button -->
```
For block attribute values containing translatable text, escape appropriately:
```php
<!-- wp:image {"alt":"<?php esc_attr_e( 'Team photo', 'theme-textdomain' ); ?>"} -->
```
## Manual Registration
For plugins or conditional patterns, use `register_block_pattern()`:
```php
register_block_pattern(
'my-plugin/testimonial-card',
array(
'title' => __( 'Testimonial Card', 'my-plugin' ),
'description' => __( 'A single testimonial with avatar and quote.', 'my-plugin' ),
'categories' => array( 'testimonials' ),
'content' => '<!-- wp:group ... --> ... <!-- /wp:group -->',
'keywords' => array( 'quote', 'review' ),
)
);
```
Hook into `init`:
```php
add_action( 'init', function() {
register_block_pattern( ... );
});
```
## Custom Categories
Register before patterns:
```php
add_action( 'init', function() {
register_block_pattern_category(
'theme-slug-portfolio',
array( 'label' => __( 'Portfolio', 'theme-textdomain' ) )
);
});
```
For themes, categories can also be registered via `theme.json`:
```json
{
"patterns": {
"categories": [
{ "name": "theme-slug-portfolio", "label": "Portfolio" }
]
}
}
```
## Unregistering Patterns
Remove core or plugin patterns:
```php
add_action( 'init', function() {
unregister_block_pattern( 'core/query-standard-posts' );
});
```
Remove an entire category:
```php
unregister_block_pattern_category( 'banner' );
```
## Namespace Conventions
- **Themes**: `theme-slug/pattern-name` (e.g., `twentytwentyfive/hero-banner`)
- **Plugins**: `plugin-slug/pattern-name` (e.g., `woocommerce/product-grid`)
- **Core**: `core/pattern-name`
Always prefix with your theme/plugin slug to avoid collisions.
references/visual-composition.md
# Visual Composition Moves
When the request calls for a visually _distinctive_ pattern, use these composition moves:
- **Oversized display type**: use a short word or title at display scale with tight line-height, uppercase or italic treatment, and enough surrounding space. Keep letter spacing at `0` unless the design specifically needs a tracked wordmark.
- **Offset color fields**: split a cover background with a hard-stop gradient or two-tone color field, then let the type or image cross the boundary.
- **Editorial cover image**: use a full-bleed Cover block with image, dim/duotone treatment, and left-anchored copy; place the CTA in a clearly separated lower area.
- **Image plus quiet content**: use Media & Text for case-study or portfolio patterns where the image carries visual weight and the content side stays intentionally sparse.
- **Header plus hero**: combine a flex header with Site Logo, Site Title, and Navigation above a full-width Cover when the pattern is a page opener or starter page.
- **Post index as graphic layout**: for Query Loop patterns, make dates and titles part of the visual hierarchy with large title links, strong separators, and pagination.
- **Repeated display text**: repeat a single word or short phrase in varied tones when the design is poster-like, but keep it readable and avoid repeating long content.
- **Centered image on two-tone field**: place a single image inside a constrained group over a split-color background for a gallery, collection, or album-style pattern.
These moves must still pass the Technical Validation Checklist in `references/anti-patterns.md`.
SKILL.md
---
name: wp-patterns
description: "Pattern: create or update WordPress block patterns (starter pages, templates, template parts, Query Loop layouts), review pattern registration, block markup, categories, accessibility, or i18n/escaping, or improve pattern design quality. Route custom blocks to wp-block-development; route frontend interactivity to wp-interactivity-api."
compatibility: "Targets WordPress 7.0+ (PHP 7.4.0+). Filesystem-based agent with bash + node. Some workflows require WP-CLI."
---
# WordPress Block Patterns
## Inputs required
- Repo root and target theme/plugin directory.
- Pattern type: section, starter page, template, template part, or manually registered plugin pattern.
- Theme/plugin slug, pattern slug, and text domain.
- Pattern title, categories, keywords, block types, template types, and inserter visibility.
- Target WordPress version if it differs from this repo's compatibility contract.
- Available `theme.json` presets for colors, typography, spacing, layout, and gradients.
- Asset paths for images/icons, including whether assets are decorative or informational.
- Verification environment: WordPress Playground, wp-env, local WordPress, or manual Code Editor check.
- If updating an existing pattern: current slug, current file path, and whether existing inserted content must remain compatible.
- For child themes: child theme slug, text domain, and asset root; do not reuse the parent namespace unless explicitly intended.
## Guardrails
1. **Block markup only** — express all visual design through block comment attributes and `preset` slugs. No inline `<style>` tags, no custom CSS classes, no arbitrary HTML outside of block wrappers. Read `references/design-with-tokens.md` for the core principle.
2. **No JavaScript** — patterns are static `block markup`. For interactivity, use blocks that natively support it (Navigation, Search, Query Loop).
3. **Registration-time PHP** — pattern files execute PHP once during registration, not at render. Read `references/pattern-registration.md` for safe output functions, i18n, and functions to avoid.
4. **Valid nesting** — read `references/block-markup-reference.md` for comment syntax and nesting rules.
5. **Native blocks for behavior** — use Query Loop, Search, Navigation, Social Icons, or an existing form block instead of custom PHP/HTML behavior. For newsletter, donation, payment, or map behavior, create a CTA/placeholder or use an existing block/plugin.
6. **Local assets** — use `get_theme_file_uri()` with `esc_url()`; no external placeholder URLs unless the user approves. Read `references/pattern-registration.md` and `references/anti-patterns.md` for examples.
## Procedure
### 0) Triage and locate the pattern target
1. Run triage when working in a repository:
- `node skills/wp-project-triage/scripts/detect_wp_project.mjs`
2. For block themes, locate the target theme root:
- `node skills/wp-block-themes/scripts/detect_block_themes.mjs`
3. Confirm the pattern belongs in a theme `patterns/` directory or needs manual plugin registration.
4. If multiple themes/plugins exist, scope all changes to the requested target.
If the user did not provide required inputs, infer only low-risk defaults. Ask before inventing a theme slug, text domain, asset path, custom post type, taxonomy, event date field, or theme-specific `preset`. If `theme.json` is missing or presets cannot be verified, use conservative core presets or ask before using theme-specific slugs.
**Done when:** target theme/plugin root, pattern type, and registration path are confirmed.
### 1) Design thinking
Make five deliberate design decisions — purpose, tone, spatial composition, typography hierarchy, and color strategy — before writing `block markup`.
Read `references/design-with-tokens.md` for the decision framework and `preset` mapping.
For pattern-type metadata (starter pages, template patterns, template parts, query loops, forms/CTAs, comparison/pricing, social/navigation/search, 404), read `references/pattern-categories-and-types.md` — including the Query Loop patterns section when using `core/query`.
When the request calls for a visually _distinctive_ composition, read `references/visual-composition.md`.
**Done when:** all five design decisions are made and recorded before markup.
### 2) Plan block structure
Sketch the nesting tree before writing markup. Example for a hero pattern:
```
Group (full-width, constrained layout, dark bg, vertical padding 80)
Group (constrained inner, flex vertical, center align)
Paragraph (uppercase label, small, letter-spacing, accent color)
Heading (h2, xx-large, heading font, tight line-height)
Paragraph (lead text, large, secondary color)
Buttons (flex, center)
Button (primary bg, base text)
Button (outline style)
```
**Done when:** nesting tree is sketched and hierarchy is intentional before writing comment tags.
### 3) Write the pattern file
Assemble the PHP header and `block markup`. Read `references/pattern-registration.md` for header fields, PHP rules, manual registration, and file examples.
Use categories and template types from step 1. Read `references/pattern-categories-and-types.md` when header metadata was not decided in step 1.
**Block markup body:**
- Follow the nesting tree from step 2
- Use `preset` slugs for colors, font sizes, spacing
- Use placeholder text that reflects real content — not "Lorem ipsum"
**Done when:** PHP header and block markup file are written.
### 4) Design quality check
Review against the Design Quality Checklist in `references/anti-patterns.md`. Every item must pass.
**Done when:** every Design Quality Checklist item passes.
### 5) Technical validation
Review against the Technical Validation Checklist in `references/anti-patterns.md`. Every item must pass.
**Done when:** every Technical Validation Checklist item passes.
## Verification
Test the pattern in a real WordPress environment:
**Using WordPress Playground (recommended):**
```bash
npx @wp-playground/cli@latest server --auto-mount
```
Mount the theme directory and verify:
- Pattern appears in inserter under specified categories
- Pattern inserts without block validation errors
- Layout renders correctly at desktop and mobile widths
- Content is editable (text, images, buttons)
- If `templateLock` is used, locked elements resist editing
For template patterns, verify the Site Editor offers the pattern in the expected template replacement flow. If `Inserter: no` is used, confirm it is hidden from the general inserter but still available where intended.
**Manual check:**
- Paste block markup into the Code Editor view in WordPress
- Switch to Visual Editor — blocks should parse without "Attempt Block Recovery" prompts
- If recovery is needed, the markup has syntax errors
Run the repo's existing lint, build, or test commands if the pattern change touches assets, generated files, or registration code.
When updating an existing pattern, remember that inserted pattern content is copied into posts/templates. Changing the pattern file does not retroactively update already inserted content, and changing block names or saved markup can create recovery prompts for newly inserted content.
For PR or package review, confirm the diff is scoped to the intended pattern files, references, scripts, and eval scenarios. Do not mix unrelated repo updates into a pattern change.
## Failure modes / debugging
Start with `references/block-markup-reference.md`, `references/pattern-registration.md`, and `references/anti-patterns.md`.
Common failures:
- **Pattern missing from inserter**: check required `Title`, `Slug`, and `Categories` headers; confirm the file is under `patterns/*.php`; confirm `Inserter: no` is not hiding it.
- **Wrong pattern shown or overwritten**: check for slug collisions and ensure the slug is namespaced as `theme-slug/pattern-name` or `plugin-slug/pattern-name`.
- **Block recovery prompt appears**: validate block comment nesting, JSON syntax, and closing comments.
- **Strings are not translated or escaped**: replace raw text/PHP output with `esc_html_e()`, `esc_html__()`, `esc_attr_e()`, `esc_attr__()`, or `esc_url()` as appropriate.
- **Translations do not load**: verify the text domain matches the target theme/plugin.
- **Dynamic content is stale or unavailable**: remove query-dependent PHP (`get_posts()`, `the_title()`, `wp_get_current_user()`) and use blocks such as Query Loop instead.
- **Query Loop output is incomplete**: check for `core/post-template`, post title/excerpt/date/image blocks, pagination when needed, and `core/query-no-results` fallback.
- **Archive/search/category/author context is wrong**: use inherited query context instead of hardcoded runtime PHP.
- **CPT or event listings are wrong**: confirm post type slugs, taxonomy/date fields, and plugin-provided blocks before generating the pattern.
- **Styles do not match the theme**: confirm `preset` slugs exist in `theme.json`; avoid unsupported theme-specific slugs unless documented.
- **Accessibility issues**: fix skipped heading levels, empty alt text for informational images, low-contrast preset combinations, vague button/link text, social icon labels, search labels, and color-only emphasis.
- **Manual registration fails**: confirm the code runs on `init`, categories are registered before patterns, and pattern content remains static block markup.
## Escalation
Stop and ask for help or consult canonical docs when:
- Theme-specific `preset` slugs, text domains, asset paths, or pattern categories cannot be verified.
- Color contrast, image meaning, or content hierarchy needs human design/accessibility judgment.
- Behavior depends on a WordPress/Gutenberg version not covered by this repo's compatibility contract.
## Example prompts
Read `references/example-prompts.md`.