Create a new Gutenberg block with scaffolding
Create a new Gutenberg block following WordPress best practices.
Search src/blocks/* for anything conceptually similar. If the idea differs from an existing block only by 1β3 attributes and shares the same save() output, register a variation instead β no new block, no deprecation debt (see "Variations vs. new blocks" in .claude/claude.md):
src/blocks/{block}/variations.js.index.js already does: pass variations into registerBlockType() (see modal/), or loop registerBlockVariation( metadata.name, variation ) after registration (see query-filter/).var(--wp--preset--spacing--50)), never hardcoded values.Only when markup, inner-block structure, or behaviour actually differs, scaffold a new block below.
src/blocks/[block-name]/block.json with proper metadata and attributesindex.js to register the blockedit.js with editor controlssave.js with frontend markupstyle.scss for frontend styleseditor.scss for editor-only stylesfrontend.js (if needed for interactivity)render.php (if dynamic rendering needed)Check src/hooks/ and src/components/shared/ first. See Shared Primitives First in .claude/claude.md for the list.
ALWAYS use these in edit.js:
useBlockProps() for block wrapperuseInnerBlocksProps() for nested blocks (NOT plain <InnerBlocks />)useEffect for styles)ALWAYS include in block.json:
supports for FSE compatibilityexample property for pattern libraryColor controls:
ColorGradientSettingsDropdown (NOT PanelColorSettings)<InspectorControls group="color">clientId parameter in edit functionInspector layout (Theme 3 IA):
<DsgoInspectorPanel> (a ToolsPanel wrapper), never bare PanelBodypanelName="settings" (title Settings) then panelName="style" (title Style) β no block-name prefixpanelId={clientId}; wrap each control in <DsgoInspectorPanel.Item label hasValue onDeselect isShownByDefault> with isShownByDefault always true<InspectorControls group="color"> / group="advanced"> β don't duplicate them inside Settings or StyleHorizontal positioning: never use supports.align: ["left","center","right"] to position a block β that's for wide/full bleed only. Use the justification pattern instead: block root gets .dsgo-justify/.dsgo-justify--{left|center|right} from a justification attribute (see getJustificationClass() in src/utils/justification.js and <DsgoJustificationToolbar>), with the visible element shrink-wrapped inside it.
The block is auto-detected from build/blocks/*/block.json by includes/blocks/class-loader.php β no manual PHP registration needed, including for dynamic rendering: a render.php file in the block's own directory is picked up automatically. No changes to src/index.js or any PHP file are required for a standard block.
npm run build
Test in both editor and frontend.
See BEST-PRACTICES-SUMMARY.md for complete patterns.