DaveRobinson

gutenberg-block-development

0
0
# Install this skill:
npx skills add DaveRobinson/claude-skill--gutenberg-block

Or install specific skill: npx add-skill https://github.com/DaveRobinson/claude-skill--gutenberg-block

# Description

Best practices for creating custom WordPress Gutenberg blocks with React/TypeScript, covering scaffolding, block architecture, attributes, dynamic vs static rendering, and testing

# SKILL.md


name: gutenberg-block-development
description: Best practices for creating custom WordPress Gutenberg blocks with React/TypeScript, covering scaffolding, block architecture, attributes, dynamic vs static rendering, and testing
license: MIT


WordPress Gutenberg Custom Block Development

This skill provides guidance for creating custom Gutenberg blocks for WordPress using modern JavaScript/TypeScript and React.

Prerequisites & Environment Setup

Required Knowledge:
- WordPress plugin development fundamentals
- JavaScript ES6+ / TypeScript
- React basics (components, hooks, JSX)
- Understanding of WordPress block editor concepts

Development Environment:
- Node.js and npm installed
- WordPress development environment (Local, wp-env, or Docker)
- Code editor with TypeScript/React support

Essential Packages:
- @wordpress/create-block - Official scaffolding tool
- @wordpress/scripts - Build tooling (webpack, babel, etc.)
- @wordpress/components - UI component library
- wp-env (optional) - Local WordPress environment via Docker

Quick Start: Scaffolding a Block

Using @wordpress/create-block

Basic scaffolding (static block):

cd /path/to/wordpress/wp-content/plugins
npx @wordpress/create-block@latest my-custom-block --namespace=my-namespace
cd my-custom-block
npm start

Dynamic block with standard template:

npx @wordpress/create-block@latest my-dynamic-block --namespace=my-namespace --variant dynamic

Interactive template (always dynamic, uses Interactivity API):

# JavaScript variant (default)
npx @wordpress/create-block@latest my-interactive-block --template @wordpress/create-block-interactive-template

# TypeScript variant
npx @wordpress/create-block@latest my-interactive-block --template @wordpress/create-block-interactive-template --variant typescript

Note: Interactive template blocks are always dynamic (use render.php) and include the WordPress Interactivity API for reactive frontend experiences.

Key flags:
- --namespace - Your unique namespace (required to avoid conflicts)
- --no-plugin - Scaffold block files only (no plugin wrapper)
- --wp-env - Add wp-env configuration for local development
- --category - Set block category (text, media, design, widgets, theme, embed)

Generated Structure

my-custom-block/
├── build/           # Compiled production code (don't edit)
├── node_modules/    # Dependencies (don't commit)
├── src/            # Your development files
│   ├── block.json  # Block metadata (THE BRAIN)
│   ├── edit.js     # Editor component
│   ├── save.js     # Frontend output (static blocks)
│   ├── style.scss  # Frontend styles
│   └── editor.scss # Editor-only styles
├── package.json
└── my-custom-block.php  # Plugin entry point

Development Workflow

After scaffolding a block:

1. Get the plugin into your WordPress environment:
- Copy/move to your WordPress plugins folder, or
- Use wp-env for a containerized WordPress environment (see below)

2. Start development or build for production:

# Development (watches for changes, rebuilds automatically)
npm start

# Production (optimized, minified build)
npm run build

Using wp-env:

# 1. Start build process
npm start          # or npm run build for production

# 2. Launch WordPress environment
npx wp-env start

# Access at http://localhost:8888
# Admin: http://localhost:8888/wp-admin (admin/password)

Note: For development, run npm start in one terminal, then wp-env start in another (npm start runs continuously).

Skill Scope: Single Block Per Plugin

This skill focuses on the standard scaffolding pattern: one block per plugin. The structure is:

my-custom-block/
├── src/
│   ├── block.json        # Block metadata at src root
│   ├── edit.js
│   ├── save.js
│   ├── style.scss
│   └── editor.scss
├── build/                # Compiled output
└── my-custom-block.php   # Plugin entry

Multiple blocks per plugin requires advanced build configuration and is outside this skill's scope. For theme-based blocks or multiple blocks, consult the Block Development Examples repository.

Post-Scaffolding: Customize Default Styles

The scaffolded style.scss contains placeholder styles meant to be customized:

// src/style.scss - SCAFFOLDED PLACEHOLDER
.wp-block-my-namespace-my-block {
    background-color: #21759b;  // Placeholder - customize or remove
    color: #fff;                 // Placeholder - customize or remove
    padding: 2px;
}

Why customize:
- style.scss applies to BOTH editor AND frontend
- Placeholder styles may not match your design
- For reusable blocks, keep minimal to respect user themes

Recommended approach:

// src/style.scss - CUSTOMIZED
/**
 * Shared styles (editor and frontend).
 * Keep minimal for maximum theme compatibility.
 */

.wp-block-my-namespace-my-block {
    // Add only essential structural styles
    // Let themes control colors, typography, spacing
}

Note: editor.scss is editor-only and can have more specific styling for the editing experience.

Core Concepts

1. block.json - The Block's Brain

The block.json file is the single source of truth for block metadata:

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "my-namespace/my-block",
  "title": "My Custom Block",
  "category": "widgets",
  "icon": "smiley",
  "description": "A custom block example",
  "keywords": ["custom", "example"],
  "version": "1.0.0",
  "textdomain": "my-custom-block",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    }
  },
  "supports": {
    "html": false,
    "anchor": true,
    "align": true
  }
}

Critical fields:
- apiVersion - Use 3 for latest features
- name - Must be unique: namespace/block-name
- icon - Visual identifier (see Icon Selection below)
- attributes - Define data structure and storage
- supports - Enable/disable core features

Icon Selection

Icons appear in the block inserter and help users identify your block quickly.

Built-in Dashicons (easiest):

"icon": "smiley"

Common choices:
- Text/content: "text", "editor-paragraph", "editor-alignleft"
- Media: "format-image", "format-video", "format-gallery"
- Layout: "layout", "columns", "grid-view"
- Interactive: "button", "forms", "testimonial"
- Data: "chart-bar", "analytics", "database"
- Social: "share", "email", "twitter"

Full list: https://developer.wordpress.org/resource/dashicons/

Custom SVG icon:

"icon": {
  "src": "<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><path d='M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z' fill='currentColor'/></svg>"
}

SVG with custom colors:

"icon": {
  "background": "#7e70af",
  "foreground": "#fff",
  "src": "<svg>...</svg>"
}

Best practices:
- Keep SVG viewBox at 0 0 24 24 for consistency
- Use currentColor for fill/stroke to respect theme colors
- Avoid complex SVGs (keep paths simple)
- Test icon visibility in both light and dark editor themes
- Choose icons that visually represent the block's purpose
- For brand blocks, use brand colors in background/foreground

2. Attributes - Data Management

Attributes control how blocks store and retrieve data:

"attributes": {
  "title": {
    "type": "string",
    "source": "html",
    "selector": "h2",
    "default": "Default Title"
  },
  "isActive": {
    "type": "boolean",
    "default": false
  },
  "items": {
    "type": "array",
    "default": []
  },
  "settings": {
    "type": "object",
    "default": {}
  }
}

Type options: string, boolean, number, integer, array, object, null

Source options:
- attribute - Extract from HTML attribute
- text - Extract text content
- html - Extract inner HTML
- query - Extract multiple elements
- meta - Store in post meta

3. Static vs Dynamic Blocks

Scaffolding Choice:
- Static block: npx @wordpress/create-block my-block
- Dynamic block: npx @wordpress/create-block my-block --variant dynamic

Using --variant dynamic sets up the correct structure from the start.

Static Blocks:
- HTML saved to database at save time
- Content persists even if plugin deactivated
- Requires manual updates (re-save post)
- Best for: Content that rarely changes, distributed plugins

// edit.js
export default function Edit({ attributes, setAttributes }) {
  return (
    <div {...useBlockProps()}>
      <RichText
        tagName="p"
        value={attributes.content}
        onChange={(content) => setAttributes({ content })}
      />
    </div>
  );
}

// save.js
export default function Save({ attributes }) {
  return (
    <div {...useBlockProps.save()}>
      <RichText.Content tagName="p" value={attributes.content} />
    </div>
  );
}

Dynamic Blocks:
- Rendered via PHP at runtime
- Always current (updates automatically)
- More database queries
- Best for: Theme-specific blocks, data that changes, client projects

Scaffolded with --variant dynamic:

// render.php (automatically created)
<?php
/**
 * Available variables:
 * @var array    $attributes The block attributes
 * @var string   $content    The block default content
 * @var WP_Block $block      The block instance
 */
?>
<div <?php echo get_block_wrapper_attributes(); ?>>
  <?php echo esc_html( $attributes['content'] ?? 'Default content' ); ?>
</div>
// index.js - No save function needed
registerBlockType( metadata.name, {
  edit: Edit,  // Only edit function
} );

block.json configuration:

{
  "render": "file:./render.php",
  "viewScript": "file:./view.js"  // Optional frontend JS
}

Converting static to dynamic:
If you scaffolded a static block and need to convert it:
1. Create render.php in the block directory
2. Add "render": "file:./render.php" to block.json
3. Remove the save function from index.js
4. Rebuild with npm run build

Decision Guide:
- Theme-specific design/layout → Dynamic (theme-coupled)
- Functionality/features → Static (plugin)
- Real-time data (posts, users, API) → Dynamic
- User-generated content → Static
- Open source distribution → Static

4. Block Controls

Toolbar Controls (quick access):

import { BlockControls } from '@wordpress/block-editor';
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';

<BlockControls>
  <ToolbarGroup>
    <ToolbarButton
      icon="admin-links"
      label="Add Link"
      onClick={() => {/* handle */}}
    />
  </ToolbarGroup>
</BlockControls>

Inspector Panel (detailed settings):

import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, ToggleControl, TextControl } from '@wordpress/components';

<InspectorControls>
  <PanelBody title="Settings" initialOpen={true}>
    <ToggleControl
      label="Enable Feature"
      checked={attributes.isActive}
      onChange={(isActive) => setAttributes({ isActive })}
    />
    <TextControl
      label="Custom Text"
      value={attributes.customText}
      onChange={(customText) => setAttributes({ customText })}
    />
  </PanelBody>
</InspectorControls>

TypeScript Integration

Setting Up TypeScript (Manual)

1. Install dependencies:

npm install --save-dev typescript @types/react @types/wordpress__block-editor @types/wordpress__blocks @types/wordpress__components

2. Create tsconfig.json:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "lib": ["dom", "esnext"],
    "jsx": "react",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

3. Update webpack.config.js:

const defaultConfig = require('@wordpress/scripts/config/webpack.config');

module.exports = {
  ...defaultConfig,
  resolve: {
    ...defaultConfig.resolve,
    extensions: ['.tsx', '.ts', '.js', '.jsx']
  },
  module: {
    ...defaultConfig.module,
    rules: [
      ...defaultConfig.module.rules,
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  }
};

4. Type your Edit component:

// edit.tsx
import { useBlockProps } from '@wordpress/block-editor';

interface EditProps {
  attributes: {
    content: string;
    isActive: boolean;
  };
  setAttributes: (attrs: Partial<EditProps['attributes']>) => void;
  className?: string;
}

export default function Edit({ attributes, setAttributes, className }: EditProps) {
  const blockProps = useBlockProps();

  return (
    <div {...blockProps}>
      {/* Your block UI */}
    </div>
  );
}

Common Patterns

Pattern: Custom Post Query Block

// Dynamic block that queries posts
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';

export default function Edit({ attributes }) {
  const { postsPerPage = 5 } = attributes;

  const posts = useSelect((select) => {
    return select(coreStore).getEntityRecords('postType', 'post', {
      per_page: postsPerPage,
      _embed: true
    });
  }, [postsPerPage]);

  if (!posts) return <Spinner />;

  return (
    <div {...useBlockProps()}>
      {posts.map(post => (
        <article key={post.id}>
          <h3>{post.title.rendered}</h3>
        </article>
      ))}
    </div>
  );
}

Pattern: Block with InnerBlocks (Static)

import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';

// edit.js
export default function Edit() {
  const TEMPLATE = [
    ['core/heading', { level: 2, placeholder: 'Section Title' }],
    ['core/paragraph', { placeholder: 'Section content...' }]
  ];

  return (
    <div {...useBlockProps()}>
      <InnerBlocks template={TEMPLATE} />
    </div>
  );
}

// save.js
export default function Save() {
  return (
    <div {...useBlockProps.save()}>
      <InnerBlocks.Content />
    </div>
  );
}

Pattern: Dynamic Block with InnerBlocks

⚠️ Important Official Guidance: Per the Block Editor Handbook:

"For many dynamic blocks, the save callback function should be returned as null... If you are using InnerBlocks in a dynamic block you will need to save the InnerBlocks in the save callback function using <InnerBlocks.Content/>"

This pattern is essential when you need to process or transform inner blocks on the server.

// edit.js
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';

export default function Edit() {
  const TEMPLATE = [
    ['core/group', {
      className: 'slot-one',
      metadata: { name: 'slot-one-content' }
    }, [
      ['core/paragraph', { placeholder: 'First slot content...' }]
    ]],
    ['core/group', {
      className: 'slot-two',
      metadata: { name: 'slot-two-content' }
    }, [
      ['core/paragraph', { placeholder: 'Second slot content...' }]
    ]]
  ];

  return (
    <div {...useBlockProps()}>
      <InnerBlocks template={TEMPLATE} />
    </div>
  );
}

// save.js
// Must save InnerBlocks even though block is dynamic!
export default function save() {
  return (
    <div {...useBlockProps.save()}>
      <InnerBlocks.Content />
    </div>
  );
}

PHP Render Callback:

The $block parameter provides access to inner blocks as WP_Block objects. Use the render() method to output them (Code Reference).

function my_block_render_callback( $attributes, $content, $block ) {
  // Inner blocks are WP_Block objects
  $inner_blocks = $block->inner_blocks;

  $slot_one = '';
  $slot_two = '';

  // Render using ->render() method for WP_Block objects
  if ( isset( $inner_blocks[0] ) ) {
    $slot_one = $inner_blocks[0]->render();
  }

  if ( isset( $inner_blocks[1] ) ) {
    $slot_two = $inner_blocks[1]->render();
  }

  // Transform inner content for custom output
  return sprintf(
    '<custom-element><div slot="one">%s</div><div slot="two">%s</div></custom-element>',
    $slot_one,
    $slot_two
  );
}

register_block_type( __DIR__ . '/build', [
  'render_callback' => 'my_block_render_callback'
] );

Key Methods (WordPress 6.0+):
- WP_Block::render() - For WP_Block objects from $block->inner_blocks
- render_block($array) - For parsed block arrays from parse_blocks()

Common Use Cases:
- Wrapping inner blocks with custom HTML (web components, special layouts)
- Adding server-side data or context to nested content
- Conditionally showing/hiding sections based on attributes
- Processing inner blocks based on user permissions or state

Pattern: Block Variations

// block.json
"variations": [
  {
    "name": "blue-variant",
    "title": "Blue Style",
    "icon": "admin-appearance",
    "attributes": {
      "backgroundColor": "blue"
    }
  },
  {
    "name": "red-variant",
    "title": "Red Style",
    "icon": "admin-appearance",
    "attributes": {
      "backgroundColor": "red"
    }
  }
]

Pattern: Frontend JavaScript with Blocks

Blocks can load frontend JavaScript using viewScript in block.json:

{
  "viewScript": "file:./view.js"
}

Or reference multiple script handles (your own, core, or third-party):

{
  "viewScript": ["file:./view.js", "wp-api-fetch", "my-external-library"]
}

External scripts are registered using standard WordPress wp_register_script() in your plugin's main PHP file.

Important: Define all scripts in block.json's viewScript array. Don't use view_script_handles in register_block_type() as it overrides the viewScript from block.json.

Note: Scripts only enqueue on pages where the block is used.

Testing & Quality Assurance

Manual Testing Checklist

When developing or modifying a block, verify:

Registration & Discovery:
- [ ] Block appears in inserter with correct icon/title
- [ ] Block appears in correct category
- [ ] Block can be searched by keywords

Functionality:
- [ ] Block can be added to editor without errors
- [ ] All toolbar controls work as expected
- [ ] Inspector panel controls update attributes correctly
- [ ] Attributes save and restore correctly
- [ ] Block renders correctly on frontend
- [ ] Dynamic blocks fetch and display current data

Compatibility:
- [ ] Works with block themes and classic themes
- [ ] Responsive across mobile/tablet/desktop
- [ ] No JavaScript console errors or warnings
- [ ] Works in posts, pages, and custom post types
- [ ] Works in widget areas (if applicable)
- [ ] Compatible with Full Site Editing (if applicable)

Accessibility:
- [ ] Keyboard navigation works throughout
- [ ] Screen reader announces elements correctly
- [ ] ARIA labels present where needed
- [ ] Focus indicators visible
- [ ] Color contrast meets WCAG standards

Performance:
- [ ] No unnecessary re-renders
- [ ] Scripts/styles only load when block present
- [ ] Images optimized and lazy-loaded
- [ ] No blocking JavaScript

Validation Testing

Block Deprecation:
When changing save output, always test migration:

deprecated: [
  {
    attributes: { /* old structure */ },
    save: OldSaveFunction,
    migrate: (attributes) => {
      // Transform old to new
      return newAttributes;
    }
  }
]

Browser Testing:
- Chrome/Edge (Chromium)
- Firefox
- Safari (especially for CSS grid/flexbox)

WordPress Version Testing:
- Current stable release
- One version back (if supporting older sites)
- Beta/RC (if planning ahead)

Common Pitfalls & Solutions

Pitfall 1: Block Validation Errors

Problem: "This block contains unexpected or invalid content"

Cause: Save function output changed between versions

Solution:
- Use block deprecations when changing save output
- Never change existing attribute sources
- Test migration before deploying

Pitfall 2: Infinite Re-renders

Problem: Block constantly re-renders, editor freezes

Cause: Creating new objects/arrays in render

Solution:

// ❌ Bad - creates new array every render
const items = [];

// ✅ Good - memoize or use state
const [items, setItems] = useState([]);

Pitfall 3: Missing useBlockProps

Problem: Block wrapper styling doesn't work

Cause: Forgot useBlockProps() in edit or save

Solution:

// Always wrap your block
<div {...useBlockProps()}>
  {/* content */}
</div>

Pitfall 4: RichText Content Loss

Problem: Content disappears on save

Cause: Missing RichText.Content in save function

Solution:

// edit.js
<RichText
  tagName="p"
  value={attributes.content}
  onChange={(content) => setAttributes({ content })}
/>

// save.js
<RichText.Content tagName="p" value={attributes.content} />

Pitfall 5: Placeholder Styles Not Customized

Problem: Block has unexpected styling that clashes with themes

Cause: Scaffolded style.scss contains placeholder styles

Location: src/style.scss (applies to both editor and frontend)

Solution:
Customize or remove the placeholder styles after scaffolding:

// Scaffolded placeholder
.wp-block-my-namespace-my-block {
    background-color: #21759b;  // Remove or customize
    color: #fff;                 // Remove or customize
    padding: 2px;
}

// Customized for production
.wp-block-my-namespace-my-block {
    // Only essential structural styles
    // Let themes control appearance
}

Remember:
- style.scss → Both frontend + editor (keep minimal)
- editor.scss → Editor only (can be more specific)

Pitfall 6: InnerBlocks Content Lost in Dynamic Blocks

Problem: InnerBlocks content disappears after saving in a dynamic block

Cause: Returning null in save.js when using InnerBlocks

Official Guidance: Per the Block Editor Handbook: "If you are using InnerBlocks in a dynamic block you will need to save the InnerBlocks in the save callback function using <InnerBlocks.Content/>"

Solution:

// ❌ WRONG - InnerBlocks content won't persist
export default function save() {
  return null;
}

// ✅ CORRECT - Save InnerBlocks even for dynamic blocks
export default function save() {
  return (
    <div {...useBlockProps.save()}>
      <InnerBlocks.Content />
    </div>
  );
}

Pitfall 7: Fatal Error with Inner Block Rendering

Problem: Fatal error: Cannot use object of type WP_Block as array

Cause: Using wrong rendering method for WP_Block objects

Context: $block->inner_blocks returns an array of WP_Block objects, not arrays.

Solution:

// ❌ WRONG - Causes fatal error
$inner_blocks = $block->inner_blocks;
$output = render_block( $inner_blocks[0] ); // Fatal!

// ✅ CORRECT - Use ->render() method
$inner_blocks = $block->inner_blocks;
$output = $inner_blocks[0]->render();

Reference:
- WP_Block::render() - For WP_Block objects
- render_block() - For parsed block arrays

Performance Best Practices

  1. Minimize attribute updates - Batch setAttributes calls
  2. Lazy load dependencies - Import heavy libraries only when needed
  3. Optimize asset loading - Load scripts/styles only on pages using block
  4. Use block context - Share data between nested blocks efficiently
  5. Debounce user input - For search/filter controls

Internationalization (i18n)

Always wrap text strings:

import { __ } from '@wordpress/i18n';

const title = __('My Block Title', 'my-text-domain');
const label = _x('Settings', 'block settings label', 'my-text-domain');
const plural = _n('1 item', '%d items', count, 'my-text-domain');

Resources

Official Documentation:
- Block Editor Handbook: https://developer.wordpress.org/block-editor/
- Block Development Examples: https://github.com/WordPress/block-development-examples
- Block API Reference: https://developer.wordpress.org/block-editor/reference-guides/
- Gutenberg Storybook: https://wordpress.github.io/gutenberg/ (interactive component reference)

Learning Platforms:
- Learn WordPress: https://learn.wordpress.org/ (courses on block development)
- WordPress Developer Blog: Latest features and tutorials

Community:
- Make WordPress Slack: #core-editor channel
- GitHub Discussions: WordPress/gutenberg repository

Decision Trees

Should I Build a Custom Block?

Need custom functionality?
├─ No → Use core blocks or block patterns
└─ Yes → Is it reusable across sites?
    ├─ Yes → Build as plugin (static blocks)
    └─ No → Is it theme-specific design?
        ├─ Yes → Build in theme (dynamic blocks)
        └─ No → Consider if block is best solution

Static vs Dynamic?

Will content change frequently without editor intervention?
├─ Yes → Dynamic block
└─ No → Is this for wide distribution?
    ├─ Yes → Static block
    └─ No → Are you comfortable with theme coupling?
        ├─ Yes → Dynamic block (simpler development)
        └─ No → Static block (portable)

Version Notes

  • WordPress 6.0+: apiVersion: 3 introduced
  • WordPress 5.8+: block.json is standard approach
  • WordPress 5.5+: InnerBlocks improvements
  • Always check compatibility requirements in plugin header

This skill should be used when:
- Creating new custom Gutenberg blocks
- Scaffolding block plugins
- Deciding between static/dynamic rendering
- Setting up TypeScript for block development
- Troubleshooting block validation errors
- Planning block architecture

# README.md

WordPress Gutenberg Block Development Skill

Best practices for creating custom WordPress Gutenberg blocks following official patterns.

Scope

  • Scaffolding blocks with @wordpress/create-block
  • Static vs dynamic blocks
  • Block.json configuration and attributes
  • InnerBlocks patterns (static and dynamic)
  • Frontend JavaScript with blocks
  • Common pitfalls and solutions

Focuses on: Single block per plugin (standard WordPress pattern)
Out of scope: Multiple blocks per plugin, theme-based blocks

Installation

Claude Code (CLI)

Copy the skill directory to your skills folder:

cp -r gutenberg-block-creation ~/.claude/skills/

Claude.ai (Web)

  1. Go to Settings
  2. Navigate to Capabilities > Skills
  3. Use the upload option to add this skill

Usage

Activates automatically when working on Gutenberg blocks, or invoke directly:

@gutenberg-block-creation

License

MIT

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.