Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    bobmatnyc

    wordpress-block-editor-fse

    bobmatnyc/wordpress-block-editor-fse
    Design
    10

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Modern WordPress block development and Full Site Editing with theme.json, block themes, and custom blocks for WordPress 6.7+

    SKILL.md

    WordPress Block Editor & Full Site Editing

    Overview

    Full Site Editing (FSE) is production-ready (since WP 6.2) and treats everything as blocks—headers, footers, templates, not just content. Block themes use HTML templates + theme.json instead of PHP files + style.css.

    Key Components:

    • theme.json: Centralized colors, typography, spacing, layout
    • HTML Templates: Block-based files (index.html, single.html)
    • Template Parts: Reusable components (header.html, footer.html)
    • Block Patterns: Pre-designed block layouts
    • Site Editor: Visual template customization

    When to Use: ✅ New themes, consistent design systems, non-technical user customization ❌ Complex server logic, team unfamiliar with blocks, heavy PHP dependencies

    Full Site Editing Architecture

    Block Themes vs Classic Themes

    Block Themes Classic Themes
    HTML files with blocks PHP files with template tags
    theme.json + CSS functions.php + style.css
    Site Editor (visual) Customizer (settings)
    User edits templates Limited customization

    Site Editor Capabilities

    • Template editing (pages, posts, archives)
    • Template parts (header/footer variations)
    • Global styles (colors, typography site-wide)
    • Pattern library (save/reuse block compositions)
    • Navigation menus (block-based)
    • Style variations (alternate design presets)

    theme.json Configuration

    theme.json v3 (WP 6.7) provides centralized design control. WordPress auto-generates CSS custom properties.

    Production Example

    {
      "$schema": "https://schemas.wp.org/trunk/theme.json",
      "version": 3,
      "settings": {
        "appearanceTools": true,
        "useRootPaddingAwareAlignments": true,
        "layout": {
          "contentSize": "800px",
          "wideSize": "1200px"
        },
        "color": {
          "palette": [
            { "slug": "primary", "color": "#0073aa", "name": "Primary" },
            { "slug": "secondary", "color": "#005177", "name": "Secondary" },
            { "slug": "base", "color": "#ffffff", "name": "Base" },
            { "slug": "contrast", "color": "#000000", "name": "Contrast" }
          ],
          "defaultPalette": false,
          "defaultGradients": false
        },
        "typography": {
          "fontFamilies": [
            {
              "fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
              "slug": "system",
              "name": "System Font"
            }
          ],
          "fontSizes": [
            { "slug": "small", "size": "0.875rem", "name": "Small" },
            { "slug": "medium", "size": "1rem", "name": "Medium" },
            {
              "slug": "large",
              "size": "1.5rem",
              "name": "Large",
              "fluid": { "min": "1.25rem", "max": "1.5rem" }
            }
          ],
          "fontWeight": true,
          "lineHeight": true
        },
        "spacing": {
          "units": ["px", "em", "rem", "vh", "vw", "%"],
          "padding": true,
          "margin": true,
          "spacingSizes": [
            { "slug": "30", "size": "0.5rem", "name": "XS" },
            { "slug": "40", "size": "1rem", "name": "S" },
            { "slug": "50", "size": "1.5rem", "name": "M" },
            { "slug": "60", "size": "2rem", "name": "L" }
          ]
        },
        "border": { "radius": true, "color": true, "width": true }
      },
      "styles": {
        "color": {
          "background": "var(--wp--preset--color--base)",
          "text": "var(--wp--preset--color--contrast)"
        },
        "typography": {
          "fontFamily": "var(--wp--preset--font-family--system)",
          "fontSize": "var(--wp--preset--font-size--medium)",
          "lineHeight": "1.6"
        },
        "elements": {
          "link": {
            "color": { "text": "var(--wp--preset--color--primary)" },
            ":hover": {
              "color": { "text": "var(--wp--preset--color--secondary)" }
            }
          },
          "h1": {
            "typography": {
              "fontSize": "var(--wp--preset--font-size--large)",
              "fontWeight": "700"
            }
          },
          "button": {
            "color": {
              "background": "var(--wp--preset--color--primary)",
              "text": "var(--wp--preset--color--base)"
            },
            "border": { "radius": "4px" },
            ":hover": {
              "color": { "background": "var(--wp--preset--color--secondary)" }
            }
          }
        },
        "blocks": {
          "core/quote": {
            "border": {
              "width": "0 0 0 4px",
              "color": "var(--wp--preset--color--primary)"
            },
            "spacing": { "padding": { "left": "var(--wp--preset--spacing--60)" } }
          }
        }
      },
      "customTemplates": [
        {
          "name": "page-wide",
          "title": "Full Width Page",
          "postTypes": ["page"]
        }
      ]
    }
    

    CSS Custom Properties Auto-Generated

    • Colors: var(--wp--preset--color--primary)
    • Fonts: var(--wp--preset--font-family--system)
    • Sizes: var(--wp--preset--font-size--large)
    • Spacing: var(--wp--preset--spacing--50)

    Fluid Typography

    Font sizes with fluid: { min, max } auto-scale using clamp():

    {
      "slug": "large",
      "size": "1.5rem",
      "fluid": { "min": "1.25rem", "max": "1.5rem" }
    }
    

    Block Theme Architecture

    Required Files

    my-block-theme/
    ├── style.css                 # Theme metadata (REQUIRED)
    ├── theme.json                # Settings/styles (REQUIRED)
    ├── templates/
    │   ├── index.html           # Fallback (REQUIRED)
    │   ├── single.html
    │   ├── page.html
    │   └── archive.html
    ├── parts/
    │   ├── header.html
    │   └── footer.html
    ├── patterns/                 # Block patterns
    │   └── hero.php
    └── functions.php             # Optional setup
    

    style.css Metadata

    /*
    Theme Name: My Block Theme
    Requires at least: 6.4
    Requires PHP: 8.1
    Version: 1.0.0
    */
    

    HTML Template Structure

    templates/single.html:

    <!-- wp:template-part {"slug":"header","tagName":"header"} /-->
    
    <!-- wp:group {"layout":{"type":"constrained"}} -->
    <div class="wp-block-group">
      <!-- wp:post-title {"level":1} /-->
      <!-- wp:post-featured-image /-->
      <!-- wp:post-content /-->
      <!-- wp:post-date /-->
    </div>
    <!-- /wp:group -->
    
    <!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->
    

    templates/index.html (with query loop):

    <!-- wp:template-part {"slug":"header"} /-->
    
    <!-- wp:group {"tagName":"main"} -->
    <main class="wp-block-group">
      <!-- wp:query {"queryId":1,"query":{"perPage":10,"postType":"post"}} -->
      <div class="wp-block-query">
        <!-- wp:post-template {"layout":{"type":"grid","columnCount":3}} -->
          <!-- wp:post-featured-image {"isLink":true} /-->
          <!-- wp:post-title {"isLink":true} /-->
          <!-- wp:post-excerpt /-->
        <!-- /wp:post-template -->
    
        <!-- wp:query-pagination -->
          <!-- wp:query-pagination-previous /-->
          <!-- wp:query-pagination-numbers /-->
          <!-- wp:query-pagination-next /-->
        <!-- /wp:query-pagination -->
      </div>
      <!-- /wp:query -->
    </main>
    <!-- /wp:group -->
    
    <!-- wp:template-part {"slug":"footer"} /-->
    

    Template Parts

    parts/header.html:

    <!-- wp:group {"layout":{"type":"flex","justifyContent":"space-between"}} -->
    <div class="wp-block-group">
      <!-- wp:site-logo {"width":60} /-->
      <!-- wp:navigation /-->
    </div>
    <!-- /wp:group -->
    

    Block Patterns

    patterns/hero.php:

    <?php
    /**
     * Title: Hero Section
     * Slug: my-theme/hero
     * Categories: featured
     */
    ?>
    <!-- wp:cover {"url":"<?php echo esc_url(get_template_directory_uri()); ?>/assets/images/hero.jpg","dimRatio":50,"minHeight":500,"align":"full"} -->
    <div class="wp-block-cover alignfull">
      <div class="wp-block-cover__inner-container">
        <!-- wp:heading {"textAlign":"center","level":1,"fontSize":"xx-large"} -->
        <h1>Welcome to Our Site</h1>
        <!-- /wp:heading -->
    
        <!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
        <div class="wp-block-buttons">
          <!-- wp:button -->
          <div class="wp-block-button"><a class="wp-block-button__link">Get Started</a></div>
          <!-- /wp:button -->
        </div>
        <!-- /wp:buttons -->
      </div>
    </div>
    <!-- /wp:cover -->
    

    Register pattern categories:

    add_action('init', 'register_pattern_categories');
    function register_pattern_categories() {
      register_block_pattern_category('hero', [
        'label' => __('Hero Sections', 'my-theme')
      ]);
      register_block_pattern_category('cta', [
        'label' => __('Call to Action', 'my-theme')
      ]);
    }
    

    Custom Block Development

    block.json Metadata (Block API v3)

    blocks/testimonial/block.json:

    {
      "$schema": "https://schemas.wp.org/trunk/block.json",
      "apiVersion": 3,
      "name": "my-theme/testimonial",
      "title": "Testimonial",
      "category": "widgets",
      "icon": "format-quote",
      "attributes": {
        "content": {
          "type": "string",
          "source": "html",
          "selector": ".testimonial-content"
        },
        "author": { "type": "string", "default": "" },
        "role": { "type": "string", "default": "" },
        "rating": { "type": "number", "default": 5 }
      },
      "supports": {
        "html": false,
        "align": ["wide", "full"],
        "color": { "background": true, "text": true },
        "spacing": { "padding": true, "margin": true }
      },
      "render": "file:./render.php"
    }
    

    Attribute Sources

    Different ways to extract data from HTML:

    "attributes": {
      "title": {
        "type": "string",
        "source": "html",
        "selector": "h2"
      },
      "linkUrl": {
        "type": "string",
        "source": "attribute",
        "selector": "a",
        "attribute": "href"
      },
      "isActive": {
        "type": "boolean",
        "default": false
      },
      "items": {
        "type": "array",
        "source": "query",
        "selector": ".item",
        "query": {
          "text": { "type": "string", "source": "text" }
        }
      }
    }
    

    Server-Side Rendering (render.php)

    blocks/testimonial/render.php:

    <?php
    $content = $attributes['content'] ?? '';
    $author = $attributes['author'] ?? '';
    $role = $attributes['role'] ?? '';
    $rating = absint($attributes['rating'] ?? 5);
    
    $wrapper_attributes = get_block_wrapper_attributes([
      'class' => 'testimonial-block',
    ]);
    ?>
    
    <div <?php echo $wrapper_attributes; ?>>
      <blockquote class="testimonial-content">
        <?php echo wp_kses_post($content); ?>
      </blockquote>
    
      <?php if ($rating > 0) : ?>
        <div class="testimonial-rating">
          <?php for ($i = 1; $i <= 5; $i++) : ?>
            <span class="star <?php echo $i <= $rating ? 'filled' : 'empty'; ?>">
              <?php echo $i <= $rating ? '★' : '☆'; ?>
            </span>
          <?php endfor; ?>
        </div>
      <?php endif; ?>
    
      <?php if ($author || $role) : ?>
        <cite class="testimonial-author">
          <span class="author-name"><?php echo esc_html($author); ?></span>
          <?php if ($role) : ?>
            <span class="author-role"><?php echo esc_html($role); ?></span>
          <?php endif; ?>
        </cite>
      <?php endif; ?>
    </div>
    

    Client-Side Rendering (React)

    blocks/testimonial/index.js:

    import { registerBlockType } from '@wordpress/blocks';
    import { useBlockProps, RichText, InspectorControls } from '@wordpress/block-editor';
    import { PanelBody, RangeControl, TextControl } from '@wordpress/components';
    import { __ } from '@wordpress/i18n';
    
    registerBlockType('my-theme/testimonial', {
      edit: ({ attributes, setAttributes }) => {
        const { content, author, role, rating } = attributes;
        const blockProps = useBlockProps();
    
        return (
          <>
            <InspectorControls>
              <PanelBody title={__('Settings', 'my-theme')}>
                <TextControl
                  label={__('Author', 'my-theme')}
                  value={author}
                  onChange={(v) => setAttributes({ author: v })}
                />
                <TextControl
                  label={__('Role', 'my-theme')}
                  value={role}
                  onChange={(v) => setAttributes({ role: v })}
                />
                <RangeControl
                  label={__('Rating', 'my-theme')}
                  value={rating}
                  onChange={(v) => setAttributes({ rating: v })}
                  min={1}
                  max={5}
                />
              </PanelBody>
            </InspectorControls>
    
            <div {...blockProps}>
              <RichText
                tagName="blockquote"
                value={content}
                onChange={(v) => setAttributes({ content: v })}
                placeholder={__('Testimonial text...', 'my-theme')}
              />
    
              <div className="testimonial-rating">
                {[1, 2, 3, 4, 5].map((star) => (
                  <span
                    key={star}
                    onClick={() => setAttributes({ rating: star })}
                  >
                    {star <= rating ? '★' : '☆'}
                  </span>
                ))}
              </div>
    
              <cite>
                <RichText
                  tagName="span"
                  value={author}
                  onChange={(v) => setAttributes({ author: v })}
                  placeholder={__('Author', 'my-theme')}
                />
              </cite>
            </div>
          </>
        );
      },
      save: () => null, // Server-side rendering
    });
    

    Block Registration

    functions.php:

    add_action('init', 'register_custom_blocks');
    function register_custom_blocks() {
      register_block_type(__DIR__ . '/blocks/testimonial');
    }
    

    InspectorControls (Settings Sidebar)

    Common controls for block settings:

    import {
      InspectorControls,
      PanelColorSettings,
      MediaUpload
    } from '@wordpress/block-editor';
    import {
      PanelBody,
      SelectControl,
      ToggleControl,
      RangeControl,
      Button
    } from '@wordpress/components';
    
    <InspectorControls>
      <PanelBody title="Layout">
        <SelectControl
          label="Columns"
          value={columns}
          options={[
            { label: '2', value: 2 },
            { label: '3', value: 3 },
            { label: '4', value: 4 }
          ]}
          onChange={(v) => setAttributes({ columns: parseInt(v) })}
        />
    
        <ToggleControl
          label="Enable Shadow"
          checked={enableShadow}
          onChange={(v) => setAttributes({ enableShadow: v })}
        />
    
        <RangeControl
          label="Border Radius"
          value={borderRadius}
          onChange={(v) => setAttributes({ borderRadius: v })}
          min={0}
          max={50}
        />
      </PanelBody>
    
      <PanelBody title="Media">
        <MediaUpload
          onSelect={(media) => setAttributes({ imageUrl: media.url })}
          allowedTypes={['image']}
          render={({ open }) => (
            <Button onClick={open} variant="secondary">
              {imageUrl ? 'Change Image' : 'Select Image'}
            </Button>
          )}
        />
      </PanelBody>
    
      <PanelColorSettings
        title="Colors"
        colorSettings={[
          {
            value: bgColor,
            onChange: (v) => setAttributes({ bgColor: v }),
            label: 'Background'
          }
        ]}
      />
    </InspectorControls>
    

    Block Supports

    Enable WordPress features:

    "supports": {
      "html": false,
      "anchor": true,
      "align": ["wide", "full"],
      "color": {
        "background": true,
        "text": true,
        "gradients": true
      },
      "spacing": {
        "padding": true,
        "margin": true,
        "blockGap": true
      },
      "typography": {
        "fontSize": true,
        "lineHeight": true,
        "fontWeight": true
      }
    }
    

    Custom Post Types with Block Editor

    add_action('init', 'register_book_cpt');
    function register_book_cpt() {
      register_post_type('book', [
        'labels' => [
          'name' => __('Books', 'my-theme'),
          'singular_name' => __('Book', 'my-theme'),
        ],
        'public' => true,
        'has_archive' => true,
        'supports' => ['title', 'editor', 'thumbnail'],
        'show_in_rest' => true,  // REQUIRED for block editor
        'menu_icon' => 'dashicons-book',
        'template' => [          // Default blocks
          ['core/paragraph', ['placeholder' => 'Book description...']],
          ['core/image'],
          ['my-theme/book-details'],
        ],
        'template_lock' => 'insert', // Can't add/remove blocks
      ]);
    
      // Register taxonomy
      register_taxonomy('genre', 'book', [
        'labels' => ['name' => __('Genres', 'my-theme')],
        'hierarchical' => true,
        'show_in_rest' => true,  // REQUIRED
      ]);
    }
    

    Template Locking

    • false: No restrictions
    • 'all': Cannot modify structure
    • 'insert': Cannot add/remove, can reorder
    • 'contentOnly': Content edits only

    Register in theme.json

    "customTemplates": [
      {
        "name": "single-book",
        "title": "Book Template",
        "postTypes": ["book"]
      }
    ]
    

    Development Workflow

    @wordpress/scripts

    package.json:

    {
      "scripts": {
        "start": "wp-scripts start",
        "build": "wp-scripts build"
      },
      "devDependencies": {
        "@wordpress/scripts": "^27.0.0"
      }
    }
    

    Commands:

    npm install
    npm run start  # Development with hot reload
    npm run build  # Production build (minified)
    

    wp-env Setup

    .wp-env.json:

    {
      "core": "WordPress/WordPress#6.7",
      "phpVersion": "8.3",
      "themes": ["./my-block-theme"],
      "config": {
        "WP_DEBUG": true,
        "SCRIPT_DEBUG": true
      }
    }
    

    Usage:

    npx @wordpress/env start
    # Access: http://localhost:8888
    # Admin: admin / password
    
    npx @wordpress/env stop
    npx @wordpress/env clean  # Reset database
    

    Migration from Classic Themes

    Template Tag to Block Mapping

    Classic Block Equivalent
    the_title() <!-- wp:post-title /-->
    the_content() <!-- wp:post-content /-->
    the_post_thumbnail() <!-- wp:post-featured-image /-->
    the_date() <!-- wp:post-date /-->
    wp_nav_menu() <!-- wp:navigation /-->
    get_header() <!-- wp:template-part {"slug":"header"} /-->
    get_footer() <!-- wp:template-part {"slug":"footer"} /-->
    get_sidebar() <!-- wp:template-part {"slug":"sidebar"} /-->

    Migration Steps

    1. Extract design tokens from style.css → theme.json
    2. Convert PHP templates to HTML block templates
    3. Add block support in functions.php:
    add_theme_support('wp-block-styles');
    add_theme_support('align-wide');
    add_theme_support('responsive-embeds');
    
    1. Test thoroughly with real content

    Block Validation

    WordPress validates block markup against registered block definitions. Invalid blocks show errors in the editor:

    Common validation errors:

    • Attribute type mismatch (string vs number)
    • Missing required attributes
    • Incorrect HTML structure
    • Changed attribute names

    Fix validation errors:

    // Add deprecated versions for backward compatibility
    const deprecated = [
      {
        attributes: {
          oldName: { type: 'string' }
        },
        migrate: (attributes) => ({
          newName: attributes.oldName
        }),
        save: (props) => {
          // Old save function
        }
      }
    ];
    

    Performance & Best Practices

    Performance

    ✅ Use server-side rendering (render.php) when possible ✅ Leverage block supports (reduces custom CSS) ✅ Disable unused features: "defaultPalette": false ✅ Use CSS custom properties for consistency ❌ Avoid client-side rendering for static content ❌ Don't override core blocks with !important

    Accessibility

    ✅ Semantic HTML (<header>, <main>, <footer>) ✅ Keyboard navigation for custom blocks ✅ WCAG AA color contrast (4.5:1 minimum) ✅ Alt text for all images ❌ Don't assume FSE = accessible (test required)

    Anti-Patterns

    ❌ Mixing classic and block approaches ❌ Hardcoding colors (use CSS variables) ❌ Reinventing block supports ❌ Skipping accessibility testing ❌ Using get_header() in HTML templates

    Related Skills

    • wordpress-plugin-fundamentals: Hook system, CPTs
    • react: Block editor components
    • typescript: Type-safe block development
    • php-security: Sanitize block attributes

    Key Reminders

    1. theme.json is mandatory for block themes
    2. HTML templates replace PHP in FSE
    3. Server-side rendering often better than client-side
    4. Block supports reduce custom code
    5. Accessibility requires testing

    Red Flags

    • More than 5 CSS files → Use theme.json
    • PHP tags in HTML templates → Use blocks
    • Client rendering for static content → Use render.php
    • No keyboard testing → Accessibility issues
    • Hardcoded values → Use CSS custom properties

    WordPress: 6.7+ | PHP: 8.1+ | Tools: @wordpress/scripts, wp-env

    Recommended Servers
    OpenZeppelin
    OpenZeppelin
    Google Docs
    Google Docs
    Blockscout MCP Server
    Blockscout MCP Server
    Repository
    bobmatnyc/claude-mpm-skills
    Files