
block-content
โ 470by automattic ยท part of automattic/studio
Write editable WordPress block markup for local Studio sites, including core/html limits, block-theme layout rules, full-width sections, validation, and skeleton-first page/CSS recipes.
This is the playbook your agent receives when the skill activates โ you don't need to read it to use the skill, but it's here to audit before installing.
Block Content
Use this skill before writing or editing page content, post content, templates, template parts, patterns, or any other WordPress block markup.
Core Policy
- Use editable WordPress blocks for content and layout. Prefer
core/group,core/columns,core/heading,core/paragraph,core/list,core/image,core/buttons, and theme CSS. - Only use
core/htmlblocks for inline SVGs, interaction markup with no block equivalent such as marquee or custom cursor markup, or a single bottom-of-page<script>block. - Never use
core/htmlto wrap text content, headings, layout sections, lists, or forms. - For forms or features core blocks do not cleanly provide, load the
plugin-recommendationsskill and use editable plugin blocks. - No decorative HTML comments such as
<!-- Hero Section -->or<!-- Features -->. Only WordPress block delimiter comments are allowed. - No custom class names on inner DOM elements. Put custom classes only on the outermost block wrapper via the block
classNameattribute. - Style buttons via
.wp-element-buttonโ the inner element WordPress applies the button's padding, background, and border to (shared by the button block and buttons from other blocks). A custom class on a button block sits on the.wp-block-buttonwrapper, so descend to.your-class .wp-element-button; never style the wrapper directly, or its padding stacks on top of the default and the button doubles in size. - No inline
styleattributes or blockstyleattributes for styling. UseclassNameplus the theme'sstyle.css. - Prefer theme palette colors over hardcoded hex. Apply block colors with palette slug attributes โ
{"backgroundColor":"accent-1","textColor":"base"}โ and instyle.cssreference palette colors asvar(--wp--preset--color--<slug>). Discover the available slugs from the active theme'stheme.jsonsettings.color.palette(for a theme you are building, the palette you defined there); when you want a color the palette lacks, prefer adding it to the palette and referencing its slug. Keeping colors on the palette keeps sections in sync with Global Styles, theme switching, and light/dark variations. A raw hex value is fine for a deliberate one-off, but it should be the exception, not the default. - Use
core/spacerfor empty spacing elements, not emptycore/groupblocks. - No emojis anywhere in generated content.
Layout Cascade
WordPress constrains children of core/post-content and any constrained-layout container to theme.json's settings.layout.contentSize, which is about 700px by default. Custom CSS such as .hero { width: 100% } does not override core layout selectors like .is-layout-constrained > *:not(.alignwide):not(.alignfull) because they are more specific.
Use these patterns:
- Full-bleed section, constrained inner content: for a full-width hero, banner, or CTA with centered content, use an outer
core/groupwith{"align":"full","layout":{"type":"constrained"}}, then place normal inner blocks inside it. - Full-bleed section, full-bleed inner content: for image grids, edge-to-edge galleries, and similar layouts, use outer and inner
core/groupblocks with{"align":"full","layout":{"type":"default"}}. - Standard constrained content: omit
alignand write normal blocks.
The common failure is a hero or banner that was intended to be full-width but still renders in the narrow content column. Fix that in markup by adding align: "full" on the outer group or correcting the inner layout type, not by trying to force width in CSS.
Root Block Gap
WordPress inserts margin-block-start: var(--wp--style--block-gap) between the top-level children of the rendered template โ between the header template part, the main group, and the footer template part (.wp-site-blocks > * + *). Core supplies a default gap (24px) even when the theme's theme.json never declares styles.spacing.blockGap, so a gap appears there that no markup asked for.
- Themes created with
scaffold_themealready zero this instyle.css(.wp-site-blocks > * + * { margin-block-start: 0; }) โ sections butt edge-to-edge and own their vertical rhythm via their own padding. Keep that reset when editing the file. - When working in a theme without that reset, add the same rule to the theme's
style.cssinstead of compensating with negative margins or guessing at the extra space. - Do not zero the gap by setting
styles.spacing.blockGap: "0"intheme.jsonโ that value cascades as the default gap inside every flow and constrained layout and collapses content rhythm site-wide. - When you want visible space between top-level sections, add it deliberately (padding on the sections) so the spacing is designed, not inherited.
Skeleton-First Recipes
For long files over about 200 lines, write a small skeleton first and fill anchors across later Edit calls.
Theme CSS
For style.css, start with custom properties and anchor comments only:
:root {
/* Map section variables onto the theme palette โ reference preset slugs,
never hardcode hex here. The slugs come from theme.json's color palette. */
--site-bg: var( --wp--preset--color--base );
--site-text: var( --wp--preset--color--contrast );
}
/* === reset === */
/* === typography === */
/* === hero === */
/* === sections === */
/* === cta === */
/* === footer === */
/* === responsive === */Keep the skeleton under 2KB. Fill one anchor per Edit, using the anchor line as old_string and replacing it with the anchor plus the new styles.
When scaffold_theme was used, do not Write over the scaffolded style.css; it already contains the required theme header. Use Edit to append the :root block and anchor comments below the existing content.
Page Content
For long page content:
- Create the page empty:
wp_cli post create --post_content=""- Write
<site>/tmp/page-<slug>.html, not a file inside the theme, with small section anchors:
<!-- section:hero -->
<!-- section:features -->
<!-- section:cta -->- Fill one anchor per
Editusing editable blocks. Never wrap a section incore/html. - Validate before applying (mandatory gate). Once all anchors are filled, you MUST call
validate_blockswithfilePathpointing at<site>/tmp/page-<slug>.htmland get a passing result โ the core/html policy passes and editor validation reports all blocks valid. This is not optional and not a step you can defer to afterwp_cli eval: the scratch file is block content, so it must be validated as a file while it still lives in a file. If validation reports invalid blocks, fix them in the file and callvalidate_blocksagain until it passes. Never apply block content you have not validated. - Apply the validated content once:
wp_cli eval '$content = file_get_contents(ABSPATH . "tmp/page-<slug>.html"); wp_update_post(["ID" => <id>, "post_content" => $content]); echo "ok";'Do not use --post_content-file=<host path>. wp_cli runs inside the PHP-WASM filesystem; the host site directory is mounted at /wordpress/, so ABSPATH === "/wordpress/". Host paths are not readable there and can silently update the post to empty content.
Validation
- Validation is a mandatory gate, not a cleanup step. You MUST call
validate_blocksand get a passing result for any block content you generate before that content reaches the live site โ beforewp_cli post create/update, beforewp_cli eval, and before importing a scratch file. Never apply, import, or save block content you have not validated. A build that skips validation is incomplete, even if the page renders. - Run
validate_blocksafter every write or edit that creates or changes block content. Call it withfilePathwhenever the content lives in a file โ including scratch files such as<site>/tmp/page-<slug>.htmlthat you later import withwp_cli eval. The scratch file is the block content; validate the file, not just the eventual post. It first runs a staticcore/htmlpolicy check: if that reports invalidcore/htmlblocks, editor validation is skipped โ rewrite only those blocks as editable core or plugin blocks, then callvalidate_blocksagain. Once the policy passes it validates in the live editor and applies safe serialization fixes directly to the file. If it says an auto-fix was applied, do not manually replace markup or call validation again unless you intentionally change block markup afterward. Use the diff only to inspect structural changes for CSS impact. Classes added or removed by the validator can affect layout and styling.
npx skills add https://github.com/automattic/studio --skill block-contentRun this in your project โ your agent picks the skill up automatically.
No common issues documented yet. If you hit a problem, the repository's GitHub Issues page is the best place to look.
Licensed under GPL-2.0โ you can use, modify, and redistribute it under that license's terms.
View the full license file on GitHub โ