How to Create Block Variations in WordPress to Extend Core Blocks Without Code Duplication

If you have ever copied a core block, renamed it, and tweaked two attributes just to match a brand preset, you already know the cost of block duplication. You end up maintaining parallel markup, parallel styles, and a parallel mental model that future you will not remember. WordPress has a cleaner answer for this exact scenario: block variations. A variation is a lightweight configuration layered on top of a registered block, letting you ship preset attributes, preset inner blocks, and a preset editor identity without forking the block itself.

This guide walks through wp.blocks.registerBlockVariation end to end. We will compare variations against block styles and patterns, study every option on the API, build three real variations of the core Quote block, wire them through PHP, and ship a pattern that uses them. By the end you will have a reliable mental model for when to reach for variations and when a different primitive is the right tool.

Variations, block styles, or patterns: a quick decision matrix

Block extension in WordPress has three distinct primitives, and they often get conflated. A block style is the lightest layer: it toggles a CSS class on an existing block. A block variation is a preset configuration of attributes and inner blocks that shows up as its own item in the editor. A pattern is a saved chunk of block markup users insert as a starting point. Picking the wrong primitive leads to awkward UX and maintenance pain, so here is the side by side.

PrimitiveWhat it changesWhere it appearsBest for
Block styleA single classNameBlock toolbar style pickerVisual themes of the same block
Block variationAttributes, inner blocks, icon, titleInserter, block picker, transform menuReusable configurations of a core block
Block patternArbitrary block markupPattern inserter and pattern librarySection layouts with multiple blocks
Custom blockEverything, with its own save or renderSame places as any blockNew behavior that no core block covers

The trick with variations is that they live at the attribute layer. If your use case is “I want the Quote block, but always starting with a specific accent color, padding, and starter paragraph,” that is a variation. If your use case is “I want a Quote that looks slightly different but is still the same shape,” that is a block style. If you need six blocks arranged into a testimonial grid with a heading, that is a pattern. For a deeper look at that primitive, our Pattern Overrides guide walks through the newer synced-with-customization model. Hold that distinction and you will stop reaching for the wrong tool.

What registerBlockVariation actually does

The API is part of the @wordpress/blocks package and it has existed since WordPress 5.9 hardened the pattern. You call wp.blocks.registerBlockVariation( blockName, variation ) and hand it an object describing the preset. There is no save function, no edit component, no custom markup. The full reference lives in the official Block Variations API documentation. The editor stores the variation, exposes it in the requested scopes, and seeds new blocks with the attributes and inner blocks you defined. Because it only touches the attribute layer, variations work with any theme, respect every attribute the core block already supports, and they cost almost nothing at runtime.

A variation object has seven keys you will use frequently: name, title, description, icon, scope, attributes, and innerBlocks. Two more matter for correctness: isActive and keywords. Let us look at each.

The core keys

  • name: A unique identifier. Namespace it like yourbrand/quote-testimonial-card so it does not collide with other plugins.
  • title: Shown in the inserter, BlockCard, and breadcrumbs. Keep it short and recognizable.
  • description: Appears in the inserter preview. This is where you sell the variation to the editor user.
  • icon: Any dashicon string, SVG component, or object with src and foreground. Give it a distinct visual so users scan the inserter faster.
  • scope: An array of strings: 'inserter', 'block', or 'transform'. This controls where the variation surfaces.
  • attributes: The preset values merged into the block on creation. These are merged, not replaced, with defaults.
  • innerBlocks: An array of [ blockName, attributes, innerInnerBlocks ] tuples that seeds the starter structure.

Understanding scope

Scope is the one key most tutorials gloss over. Each string in the array unlocks a different surface in the editor, and the combinations change the user experience meaningfully.

  • inserter: The variation shows up in the main block inserter as if it were a separate block. Users search for it by title or keyword.
  • block: The variation appears inside the block’s own variation picker (the modal you see when inserting a block that offers a starting choice, like the Embed block).
  • transform: The variation appears in the block toolbar’s Transform menu on compatible sibling blocks, so users can switch into it.

Ship with at least 'inserter' for anything you expect writers to reach for directly. Add 'transform' when the variation is a natural alternative to another existing block. Use 'block' for variations that should only appear when the user is already committed to the parent block, typically to offer starter layouts.

The isActive matcher

Here is the subtle part. When an editor loads a post, it sees a block with a pile of attributes. It does not automatically know which variation produced those attributes. If you skip isActive, the editor will show the generic block title in the BlockCard, in breadcrumbs, and in the list view. Your variation becomes invisible the moment the user reloads.

The isActive property is either an array of attribute names to match on, or a function that receives the block attributes and returns a boolean. The function form is more flexible because you can compare against computed values, check for className substrings, or inspect nested style objects. A reliable pattern is to include a unique className on the variation and let isActive check for that className. It is the only attribute that survives all the editing flows cleanly.

A practical example: three variations of core/quote

Let us build something shippable. We will extend the core Quote block with three variations that cover the most common editorial needs: a pullquote accent for featured lines, a testimonial card with a boxed look, and a compact source highlight for attributions. All three live in one JS file, share icons and keywords, and register from a single enqueue hook.

Three things to notice in that file. First, each variation sets a unique className that matches the isActive function, which is how the editor knows to label the block correctly on reload. Second, innerBlocks seeds the structure but keeps it minimal so writers can edit freely. Third, the style object uses the same shape WordPress uses internally for block supports, so padding, border, and color tokens render exactly as they would if set via the Inspector.

Enqueue the script on the editor side

Variations only matter in the editor, so enqueue them through enqueue_block_editor_assets. You do not need to load them on the front end because variations only seed attributes at creation time. The saved block markup is plain Gutenberg output. Pair the script with matching block styles via register_block_style so your classNames actually produce CSS on both editor and front.

The asset file loaded through variations.asset.php comes from @wordpress/scripts when you run wp-scripts build. It ships the exact list of dependencies and a cache-busting version hash, so your editor does not pull a stale bundle after a release. If you are not using @wordpress/scripts yet, hardcode the dependency array with at least wp-blocks, wp-i18n, and wp-dom-ready.


Variation-driven patterns

Once your variations are registered, patterns become dramatically cheaper to write. Instead of repeating attribute objects in every pattern, you reference the className the variation owns. The editor picks up the variation identity on insertion and the author sees the familiar variation label in the breadcrumb. Here is a testimonial grid that uses the Testimonial Card variation twice.

Because the className carries the variation identity, you can use this same pattern across a dozen templates and every Quote in it will still be identified as the Testimonial Card variation. The pattern stays lean, the variations stay canonical, and writers get a single source of truth for testimonial look and feel.

Hooking variations with PHP and JS together

A common trap is registering variations in JS but forgetting the PHP half. Variations themselves live in JS because they manipulate the editor store, but the matching block styles and any server-registered patterns need PHP. The cleanest structure is to pair them: every variation gets a matching register_block_style call in init, and any pattern that uses the variation gets registered via register_block_pattern in init as well. That way everything travels together when you deploy.

If your variation introduces a preset color or spacing scale, that lives in theme.json, not in the variation. Keep the data layer tidy: theme.json defines tokens, the variation references tokens, and the block style applies any extra CSS. When somebody updates the brand palette in theme.json, every variation that references those tokens updates automatically.


Real world examples beyond the Quote block

Once you have the mental model down, almost every core block starts to look like a candidate for variations. Here are six patterns worth stealing directly into your next project.

  • Heading variations. A “Section Heading” variation that presets a specific font size, uppercase tracking, and accent color underline. Writers reach for it every time they start a new section and the whole site stays visually consistent.
  • Button variations. Primary, secondary, ghost, and destructive variants, each with preset border, padding, and color that maps to your design tokens. No more hunting through the inspector for the right styling.
  • Group variations. A “Feature Card” group preset with background, radius, inner padding, and a starter inner block (heading + paragraph + button). Drop it in and you have a card.
  • Image variations. A “Hero Banner” variation that sizes the image full bleed, sets object-fit, and applies a soft overlay, used across every landing page.
  • Cover variations. A “Section Cover” variation that sets the minimum height to 80vh and preloads a starter overlay opacity writers can tweak.
  • Embed variations. Remove the core Embed variations you do not use and replace them with narrowly focused ones for the providers your content actually references.

Each of these stays thin. The variation layer holds only the preset attributes. The visual tokens live in theme.json. The CSS for each className lives in your theme or a matching block style call. That separation keeps the codebase readable and gives future maintainers one file per concern, not one variation file that tries to do everything.

A concrete workflow for rolling out variations

When a new brand requirement lands, our team runs the same short loop every time. First, sketch the variation on paper or in Figma to confirm it is genuinely different from existing options. Second, decide whether it is really a variation, a block style, or a pattern using the decision matrix from earlier. Third, add the variation to the central JS file with an explicit isActive matcher. Fourth, register the matching block style and theme tokens. Fifth, add it to the test post in staging. Sixth, take a screenshot at 390 pixels wide and at 1440 pixels wide to confirm the responsive behavior survives. Seventh, ship. The whole loop takes about an hour for a simple variation and is fully repeatable.


Performance notes

Variations are nearly free at runtime because they only run in the editor and only fire at registration time. The function call is synchronous, the payload is a plain object, and there is no additional markup in the saved output. You can register fifty variations without measurable impact on editor load time, assuming your JS bundle is reasonably sized. The perf cost, if any, lives in your bundle: keep the attributes object literal, do not pull in heavy icon libraries, and tree shake with @wordpress/scripts.

On the front end, variations produce normal block markup. A Quote variation is a Quote, serialized exactly like any other Quote. That means your front-end CSS is the only thing you pay for, and it is the same CSS you would write for any block style. No hydration, no extra JavaScript, no lazy loading dance.

Deprecation and upgrade considerations

Variations are a loose coupling. If you later rename a variation, existing content does not break because the saved block is still valid core markup. The only casualty is the editor identity: the previous className or attribute signature no longer matches isActive, so the BlockCard reverts to the generic label. Plan for this by either keeping old classNames around as aliases in your isActive function or by writing a one-time data migration that runs through posts and updates the relevant attribute.

If you ever need to remove a variation, use wp.blocks.unregisterBlockVariation( blockName, variationName ) from a later-enqueued script. This is how you remove core variations you do not want (for example the many Embed variations bundled with WordPress) or sunset your own variations without breaking existing content.

Common pitfalls to avoid

  • Skipping isActive. Without it, variations lose their identity on reload and writers get confused. Always include it, and prefer className matching when possible.
  • Overusing innerBlocks. Seed just enough structure for writers to start. Deep trees feel prescriptive and fight the flexibility that makes variations valuable.
  • Registering on the front end. Variations only need the editor. Loading the script on the front end is wasted bandwidth with no benefit.
  • Forgetting keywords. The inserter is a search box. Without keywords, users cannot find variations by synonym or intent.
  • Nesting variation-only CSS inside the variation file. Keep CSS in your theme or block style. Variations should stay data-only.
  • Mixing variations with custom blocks. If you find yourself adding behavior that the core block does not support, you have outgrown variations. Register a custom block.

When not to use a variation

Variations cover a huge surface area but they are not a universal hammer. If you need a different save output, a different attribute schema, a different icon set on each nested child, or any new server rendering, register a custom block. If you need to rearrange a group of blocks with a heading and two columns, that is a pattern. If you just want one visual tweak, that is a block style. When you keep those lanes clean, your editor stays calm and your codebase stays predictable.


Accessibility and content consistency

Accessibility often gets forgotten in the rush to ship visual variations. The good news is that because a variation renders normal block markup, you inherit the accessibility story of the underlying core block. The Quote block ships with a semantic blockquote wrapper and a cite element for the source. Your variations keep those semantics. Where you can go wrong is with color contrast on heavy accent variations or padding so tight that focus outlines disappear. Test each variation with keyboard focus, with a screen reader, and at the 200 percent zoom breakpoint. A block variation that looks stunning in a design mock but fails WCAG AA contrast at the smallest text size is a liability waiting to show up in an audit.

Content consistency is the other payoff. Because variations bake in attributes, every testimonial block across a site of thousands of posts has the same padding, the same border radius, and the same accent color. Brand guidelines that used to live in a PDF now live in code, enforced by the editor. If a new variant is needed, you add one variation and every future insertion picks it up. Teams with multiple writers feel this benefit within the first week: fewer design regressions, fewer brand review cycles, and fewer “can you fix the quote on this post” requests.

Frequently asked questions

Can I register variations for third-party blocks?

Yes. registerBlockVariation takes any registered block name, whether it is core, from another plugin, or from your own theme. Register on wp.domReady or later so the target block has already been registered. If the third-party block adds a custom attribute schema, respect it: your variation attributes should only reference keys the block actually supports, otherwise the editor will silently drop them on save.

Do variations work in classic themes?

Yes, variations live in the block editor, not in the theme. Any theme that uses Gutenberg gets the benefit. Block themes and full site editing amplify the value because your variations can also appear in template parts, patterns, and the Site Editor, but a classic theme with the block editor enabled works fine too. If you are still weighing the jump, our comparison of block themes vs classic themes vs Elementor lays out where each approach pays off.

How do variations interact with reusable blocks or synced patterns?

Synced patterns store concrete block markup. If a synced pattern contains a variation’s className, the variation identity is preserved on every render of that pattern. If the variation changes later, the synced pattern inherits the new styling (since it depends on the className and block styles), but the seeded attributes inside the synced pattern are frozen. Plan your synced patterns carefully if they depend on specific variation presets. Our guide to synced patterns in block themes covers the governance model in more depth.


Testing checklist before shipping

Before you ship a variation to production, run through this short checklist to catch the most common regressions. These take under ten minutes combined and they save you from the painful class of bugs where a variation looks fine in the editor but collapses on reload or on the front end.

  • Insert the variation from the main inserter. Confirm the BlockCard shows the variation title and icon.
  • Reload the post. Confirm the BlockCard still shows the variation title. If it reverts to the generic core block label, your isActive matcher is wrong.
  • Duplicate the block. Confirm the duplicate keeps the variation identity.
  • Transform to another block and back. Confirm the roundtrip does not corrupt attributes.
  • Switch to Code Editor. Confirm the serialized markup is clean and has the expected className.
  • View the post on the front end. Confirm the className produces the CSS you expect.
  • Repeat at 390 pixels viewport width. Confirm responsive behavior matches the design mock.

Wire this checklist into your release process. On plugins with a Playwright or Puppeteer test harness, most of these can be automated with a single end-to-end test per variation. On smaller sites, a shared QA doc that references each variation by name is enough. The point is to make variation regressions visible before they reach writers.


Wrapping up

Block variations are one of the best leverage points in the WordPress editor. You get brand-specific presets, a clean editor identity, and zero runtime cost, all without forking a core block. The three Quote variations we built earlier cover a realistic editorial stack: pullquote for featured lines, testimonial for customer voice, and source highlight for citations. Ship them, pair them with matching block styles, reference them from patterns, and watch your writers stop reaching for custom CSS.

The mental model is worth repeating: variations are attribute presets with an editor identity. Block styles are className toggles. Patterns are saved layouts. Custom blocks are new behavior. Once that distinction is second nature, extending the block editor stops feeling like a maze and starts feeling like a well labeled toolbox.

Scroll to Top