Understanding the Difference Between @wordpress/components and @wordpress/block-editor

Since the introduction of Gutenberg, WordPress development has undergone a significant transformation. The shift from a primarily PHP-driven architecture to one that embraces modern JavaScript practices has introduced developers to a suite of powerful packages that form the backbone of today’s WordPress experience. Among these packages, @wordpress/components and @wordpress/block-editor often cause confusion for developers who are new to this ecosystem. While they may appear similar at first glance, they serve distinctly different purposes and understanding their differences is crucial for effective WordPress development.

The Evolution of WordPress Development

WordPress has been on a journey of modernization for several years now. The introduction of Gutenberg marked a pivotal moment in this evolution, signaling a move toward a more JavaScript-centric approach. This shift wasn’t merely cosmetic; it represented a fundamental rethinking of how WordPress content is created, edited, and displayed.

At the heart of this transformation lies a modular JavaScript architecture. WordPress now maintains dozens of JavaScript packages, each designed with specific responsibilities and capabilities. These packages follow a monorepo approach – they’re developed within a single repository but published as separate npm packages. This structure allows developers to pick and choose exactly what they need for their projects without carrying the burden of unnecessary code.

Diving Into @wordpress/components

The @wordpress/components package represents one of the most fundamental building blocks in the new WordPress JavaScript ecosystem. At its core, this package provides a comprehensive library of reusable UI components designed with WordPress’s unique needs and aesthetics in mind.

Think of @wordpress/components as the basic building blocks of the WordPress admin interface. These components range from simple elements like buttons, text inputs, and toggles to more complex interfaces such as modals, popovers, and color pickers. What makes these components special is their adherence to WordPress design principles, ensuring that anything built with them feels naturally at home within the WordPress admin.

The beauty of these components lies in their versatility. They aren’t tied to any specific context within WordPress. A Button component from this package is equally at home on a plugin settings page as it is within the block editor. This universality makes @wordpress/components incredibly valuable for developers who want to create interfaces that feel consistent with the core WordPress experience.

Accessibility is another strength of this package. WordPress has made significant strides in ensuring that its admin interface is accessible to all users, including those with disabilities. The components in this package inherit this commitment, with built-in keyboard navigation, screen reader support, and other accessibility features. For developers, this means that building accessible interfaces becomes less of a manual effort and more of an inherent quality of the tools they’re using.

Let’s consider a practical example. Imagine you’re developing a custom plugin that needs a settings page. Using components from @wordpress/components, you might create panels with collapsible sections, toggle controls for enabling or disabling features, text inputs for configuration values, and buttons for saving changes. All of these elements would automatically match the look and feel of WordPress, providing users with a familiar experience that feels like a natural extension of the platform.

The Specialized World of @wordpress/block-editor

While @wordpress/components focuses on providing general-purpose UI elements, @wordpress/block-editor serves a much more specialized role. This package is dedicated to the needs of the block editor specifically, offering components and utilities that understand and interact with WordPress blocks.

The block editor (Gutenberg) represents a paradigm shift in how content is created in WordPress. Instead of a single monolithic text area, content is now composed of discrete “blocks” – paragraphs, images, galleries, embeds, and countless other content types. Each block has its own unique properties, behaviors, and editing interfaces. This complexity requires specialized tools, which is precisely what @wordpress/block-editor provides.

The components in this package are designed with an inherent understanding of the block ecosystem. They know about concepts like block selection, focus states, and editing contexts. They’re aware of how blocks can be moved, inserted, removed, and transformed. This context-awareness enables them to provide sophisticated functionality that would be difficult or impossible to achieve with generic UI components alone.

Consider the RichText component – one of the most frequently used elements from @wordpress/block-editor. Unlike a standard text input, RichText understands formatting, handles keyboard shortcuts, manages selections, and integrates with the block editor‘s formatting toolbar. It knows how to convert its content to and from HTML, and it maintains consistency with WordPress’s content handling conventions. This level of specialization makes it perfect for block development but would be unnecessary overkill for a simple settings field.

Similarly, components like BlockControls and InspectorControls understand their place within the block editing experience. BlockControls renders a toolbar that appears when a block is selected, while InspectorControls populates the sidebar with block-specific settings. These components handle the complex task of placing their content in the right location within the editor interface, freeing developers from having to manage this themselves.

Block development often involves a combination of components from both packages. The specialized components from @wordpress/block-editor provide the block-specific functionality, while the general-purpose components from @wordpress/components handle basic UI needs within those specialized contexts. For instance, you might use InspectorControls from @wordpress/block-editor to place settings in the sidebar, but then use SelectControl and ToggleControl from @wordpress/components to create the actual interface elements within that sidebar.

Understanding the Relationship Between These Packages

The relationship between these two packages is hierarchical, not parallel. @wordpress/block-editor depends on @wordpress/components, building upon and extending its functionality for the specific context of block editing. The reverse is not true – @wordpress/components has no dependency on or awareness of @wordpress/block-editor.

This relationship becomes clearer when examining the composition of block editor components. Many of them incorporate or enhance components from @wordpress/components to provide block-specific capabilities while maintaining consistency with the broader WordPress admin interface.

Context-awareness represents another key difference between these packages. Components from @wordpress/components are generally context-agnostic – they don’t have built-in knowledge of where they’re being used or what they’re being used for. In contrast, components from @wordpress/block-editor are deeply context-aware, understanding concepts like the current block, selected blocks, available transformations, and editing modes.

This difference in context-awareness influences where and how these components should be used. The generic components can be used practically anywhere within the WordPress admin, from plugin settings pages to widget areas to custom admin screens. The block editor components, however, are designed specifically for use within the block editor context and may not function correctly when used elsewhere.

Practical Applications and Best Practices

Understanding when to use each package is crucial for effective WordPress development. As a general rule, use @wordpress/components when building any WordPress admin interface that isn’t directly related to block editing. This includes plugin settings pages, custom admin pages, metaboxes, and widgets.

For block development, the choice becomes more nuanced. Use components from @wordpress/block-editor when you need functionality that’s specific to the block editing experience, such as rich text editing, block selection handling, or toolbar controls. Within these block-specific interfaces, you’ll often still use components from @wordpress/components for basic UI elements like buttons, panels, and form controls.

Proper importing is essential when working with these packages. Always import components from their specific package rather than trying to access everything through a single import. This ensures that your dependencies are correctly tracked and that you’re not inadvertently including code that you don’t need.

// Good practice
import { Button, Panel, TextControl } from '@wordpress/components';
import { RichText, BlockControls } from '@wordpress/block-editor';

// Avoid this
import { Button, RichText } from '@wordpress/block-editor'; // Incorrect!

Another best practice is to minimize dependencies by only importing the specific components you need rather than the entire package. This helps reduce bundle size and improves performance, especially for plugins that might be used on sites with limited resources.

When developing blocks, take advantage of the specialized components provided by @wordpress/block-editor rather than trying to recreate their functionality using more basic components. These specialized components handle many complex tasks automatically, such as placing toolbar controls in the correct location and managing block selection states.

The Evolving WordPress Ecosystem

Both of these packages continue to evolve as WordPress development advances. The WordPress team regularly introduces new components, enhances existing ones, and sometimes deprecates older approaches in favor of better solutions. Staying current with these changes is important for maintaining modern, compatible WordPress extensions.

Recent trends include increased component abstraction, with more high-level components that combine functionality in useful ways. There’s also been a push for better TypeScript support, providing improved type definitions for a better developer experience. Enhanced customization options are making it easier to style and adapt components to specific needs, while performance improvements are reducing bundle sizes and improving rendering efficiency.

For developers working with blocks, understanding the interplay between these packages is becoming increasingly important. As the block editor ecosystem expands to include full-site editing, theme blocks, and pattern directories, the specialized capabilities of @wordpress/block-editor are becoming even more central to WordPress development.

Building with Confidence

The @wordpress/components and @wordpress/block-editor packages represent different layers in the WordPress JavaScript ecosystem, each with its own purpose and specialization. By understanding their differences and how they complement each other, developers can build more effective, efficient, and maintainable WordPress extensions.

The foundational nature of @wordpress/components makes it the go-to choice for creating consistent, accessible interfaces anywhere in the WordPress admin. Its generic UI building blocks provide the versatility needed for a wide range of admin interfaces, ensuring that they feel like natural extensions of WordPress itself.

The specialized focus of @wordpress/block-editor makes it indispensable for block development, providing the tools needed to create rich, interactive editing experiences that integrate seamlessly with the block editor. Its context-awareness and block-specific capabilities enable developers to create blocks that behave exactly as users would expect.

By leveraging both packages appropriately, WordPress developers can create experiences that are both powerful and familiar, extending WordPress in ways that feel natural and intuitive to users. As WordPress continues its evolution toward a more block-oriented future, mastery of these packages will become increasingly valuable for developers who want to stay at the forefront of WordPress development.

Whether you’re building a simple plugin or developing complex custom blocks, understanding the right tool for the job will make your development process more efficient and your code more maintainable. With @wordpress/components and @wordpress/block-editor in your toolkit, you’re well-equipped to create WordPress experiences that are modern, accessible, and truly integrated with the platform.

The Importance of Defining block.json Attributes in WordPress Gutenberg Blocks

The WordPress Gutenberg editor, introduced in WordPress 5.0, revolutionized content creation with its block-based system. For developers building custom blocks, the block.json file serves as the backbone of block configuration, defining metadata, settings, and attributes. Among its many roles, defining attributes in block.json is critical to ensuring blocks function correctly, persist data, and provide a seamless user experience. Failing to define attributes properly can lead to significant issues, from lost settings to broken functionality. In this article, we explore why block.json attribute definitions are essential, the consequences of getting them wrong, and real-world examples to illustrate the impact.

What Are block.json Attributes?

In WordPress Gutenberg blocks, attributes are the pieces of data that store a block’s configuration and content. They define the customizable properties of a block, such as text content, toggle settings, or selected options, which are saved to the WordPress database as part of a post’s content. The block.json file is where developers declare these attributes, specifying their type (e.g., string, boolean, array), default value, and other properties.

For example, a custom navigation block might define attributes like this:

{
  "attributes": {
    "menuId": {
      "type": "number",
      "default": 0
    },
    "useCategories": {
      "type": "boolean",
      "default": false
    },
    "selectedCategories": {
      "type": "array",
      "default": []
    }
  }
}

Here, menuId stores a numeric menu ID, useCategories toggles category-based navigation, and selectedCategories holds an array of category IDs. These attributes are manipulated in the block’s JavaScript (edit.js) and PHP (render.php) files and saved to the database to persist user settings.

Why Defining Attributes in block.json Is Important

Properly defining attributes in block.json is not just a best practice—it’s a requirement for robust block development. Here’s why it matters:

1. Data Persistence

Attributes defined in block.json are serialized and saved to the WordPress database (in the wp_posts table as part of the post content). This ensures that user-configured settings, such as toggle states or selected options, are retained when a page is saved, reloaded, or displayed on the front end. Without a proper definition, WordPress doesn’t know to save the attribute, leading to data loss.

2. Block Validation

WordPress uses the block.json schema to validate block data when loading or saving a post. If an attribute is used in the block’s code but not defined in block.json, WordPress may flag the block as invalid or silently discard the undefined attribute’s value, causing unexpected behavior.

3. Improved Developer Experience

Defining attributes in block.json provides a clear contract for what data the block uses. This makes it easier for developers to understand, maintain, and extend the block. It also enables WordPress to provide better tooling, such as automatic TypeScript support or editor hints, when attributes are properly declared.

4. Front-End Rendering

Attributes are often used in the block’s server-side rendering (via render.php or a render_callback). If attributes are not defined in block.json, the front-end output may fail to reflect user settings, leading to inconsistent or broken displays.

5. Future-Proofing and Compatibility

A well-defined block.json ensures compatibility with future WordPress updates and tools like the Block Editor’s serialization process. It also supports features like block deprecation and migration, where attributes can be transformed if the block’s structure changes.

Consequences of Not Defining Attributes Correctly

Failing to define attributes in block.json or defining them incorrectly can lead to a range of issues that frustrate users and developers alike. Here are the most common consequences, with examples to illustrate the impact.

1. Loss of User Settings

If an attribute is used in the block’s JavaScript (e.g., in edit.js) but not defined in block.json, WordPress will not save its value to the database. As a result, user-configured settings are lost when the page is refreshed or reloaded.

Example: Custom Navigation Block
Consider a navigation block that allows users to toggle “Hide Empty Categories” and “Show Only Selected Categories.” The edit.js file might include:

<ToggleControl
  label="Hide Empty Categories"
  checked={attributes.hideEmpty}
  onChange={(value) => setAttributes({ hideEmpty: value })}
/>
<ToggleControl
  label="Show Only Selected Categories"
  checked={attributes.useSelectedCategoriesOnly}
  onChange={(value) => setAttributes({ useSelectedCategoriesOnly: value })}
/>

If hideEmpty and useSelectedCategoriesOnly are not defined in block.json, toggling these settings will appear to work in the editor but will reset to their initial (or undefined) values after a page refresh. This frustrates users who expect their settings to persist.

Fix:
Add the attributes to block.json:

{
  "attributes": {
    "hideEmpty": {
      "type": "boolean",
      "default": false
    },
    "useSelectedCategoriesOnly": {
      "type": "boolean",
      "default": false
    }
  }
}

This ensures WordPress saves the toggle states, preserving user settings.

2. Broken Front-End Rendering

Undefined attributes may not be available in the block’s server-side rendering, leading to incorrect or missing output on the front end.

Example: Category Navigation
Suppose the navigation block uses a selectedCategories attribute to store an array of category IDs for display. The render.php file might fetch categories like this:

$categories = get_categories([
  'include' => $attributes['selectedCategories'],
  'hide_empty' => $attributes['hideEmpty']
]);

If selectedCategories and hideEmpty are not defined in block.json, $attributes['selectedCategories'] and $attributes['hideEmpty'] will be null or undefined, causing the query to fail or return incorrect results. This could result in no categories being displayed or all categories being shown, ignoring user settings.

Fix:
Define the attributes in block.json:

{
  "attributes": {
    "selectedCategories": {
      "type": "array",
      "default": []
    },
    "hideEmpty": {
      "type": "boolean",
      "default": false
    }
  }
}

This ensures the attributes are available and correctly populated in render.php.

3. Block Validation Errors

When WordPress loads a post, it validates blocks against their block.json schema. Undefined attributes can trigger validation errors, causing the block to enter recovery mode or fail to load properly in the editor.

Example: Text Alignment
Imagine a block with a text alignment setting:

setAttributes({ align: 'center' });

If align is not defined in block.json (even if supports.align is enabled), WordPress may mark the block as invalid, prompting users to recover or convert it, which disrupts the editing experience.

Fix:
Explicitly define the attribute, even if it’s supported via supports:

{
  "attributes": {
    "align": {
      "type": "string",
      "default": "left"
    }
  },
  "supports": {
    "align": true
  }
}

4. Debugging Nightmares

Undefined attributes make debugging difficult, as developers may not immediately realize that missing block.json definitions are causing issues. This can lead to wasted time chasing symptoms (e.g., settings not saving) instead of addressing the root cause.

Example: Debugging a Toggle
In the navigation block example, a developer might spend hours debugging why hideEmpty resets, checking edit.js for state management issues, only to realize the attribute was never defined in block.json. Proper definitions prevent such confusion.

Best Practices for Defining block.json Attributes

To avoid these pitfalls, follow these best practices when defining attributes in block.json:

  1. Define All Attributes Used:
    Ensure every attribute manipulated in edit.js or render.php is declared in block.json. Even if an attribute seems minor (e.g., a toggle state), it must be defined to be saved.
  2. Specify Correct Types:
    Use the appropriate type (string, number, boolean, array, object) to match the attribute’s data. For example, use array for lists of values, like selectedCategories.
  3. Provide Sensible Defaults:
    Set default values that align with the block’s initial behavior. For example, a boolean toggle should default to false if it’s off by default, and arrays should default to [] if empty.
  4. Document Attributes:
    Use comments or a separate documentation file to explain each attribute’s purpose, especially for complex blocks. This helps future developers understand the block’s data model.
  5. Test Attribute Persistence:
    After defining attributes, test the block by saving settings, refreshing the editor, and checking the front-end output. Verify that all settings persist and render correctly.
  6. Plan for Migration:
    If updating an existing block to add new attributes, use the deprecated property in block.json to migrate old instances. For example:
{
  "deprecated": [
    {
      "attributes": { /* old attributes */ },
      "migrate": (attributes) => ({
        ...attributes,
        newAttribute: false
      })
    }
  ]
}

Real-World Example: A Navigation Block

Let’s revisit the navigation block example to tie it all together. A developer creates a block allowing users to display a WordPress menu, custom menu items, or categories. The edit.js file includes controls for:

  • Toggling “Use Categories” (useCategories)
  • Toggling “Hide Empty Categories” (hideEmpty)
  • Toggling “Show Only Selected Categories” (useSelectedCategoriesOnly)
  • Selecting specific categories (selectedCategories)
  • Sorting categories by name, count, or ID (categoryOrderBy)
  • Sorting order (ascending or descending) (categoryOrder)

Initially, the block.json only defines:

{
  "attributes": {
    "menuId": { "type": "number", "default": 0 },
    "useCategories": { "type": "boolean", "default": false },
    "categoryCount": { "type": "number", "default": 6 },
    "customItems": { "type": "array", "default": [...] },
    "className": { "type": "string", "default": "tmenu" }
  }
}

Problem: The hideEmpty, useSelectedCategoriesOnly, selectedCategories, categoryOrderBy, and categoryOrder attributes are missing. As a result:

  • Toggling “Hide Empty Categories” or “Show Only Selected Categories” works in the editor but resets on refresh.
  • Selected categories are not saved, so the front-end shows all categories or none.
  • Sorting settings default to hardcoded values (e.g., name, asc) and cannot be customized.

Solution: Update block.json to include all attributes:

{
  "attributes": {
    "menuId": { "type": "number", "default": 0 },
    "useCategories": { "type": "boolean", "default": false },
    "categoryCount": { "type": "number", "default": 6 },
    "customItems": { "type": "array", "default": [...] },
    "className": { "type": "string", "default": "tmenu" },
    "hideEmpty": { "type": "boolean", "default": false },
    "useSelectedCategoriesOnly": { "type": "boolean", "default": false },
    "selectedCategories": { "type": "array", "default": [] },
    "categoryOrderBy": { "type": "string", "default": "name" },
    "categoryOrder": { "type": "string", "default": "asc" }
  }
}

Outcome:

  • User settings for all toggles and selections are saved and persist across page refreshes.
  • The render.php file can use these attributes to fetch and display the correct categories, respecting user preferences.
  • The block is more reliable, maintainable, and user-friendly.

Conclusion

Defining attributes in block.json is a fundamental aspect of WordPress Gutenberg block development. It ensures data persistence, validates block content, supports front-end rendering, and enhances the developer experience. Failing to define attributes correctly can lead to lost settings, broken rendering, validation errors, and debugging headaches, as seen in the navigation block example. By following best practices—defining all attributes, using correct types, providing defaults, and testing thoroughly—developers can create robust, user-friendly blocks that work seamlessly in the Gutenberg editor and beyond.

If you’re building a custom block, take the time to audit your block.json file and ensure every attribute used in your code is properly declared. Your users (and future self) will thank you for it.

Taking Control of WordPress Block Styles: A Developer’s Guide

Have you ever felt frustrated when WordPress’s default block styles don’t quite match your theme’s aesthetic? You’re not alone. Those rounded button edges when you want sharp corners, or that default outline style that clashes with your brand colors – we’ve all been there.

The good news? You have complete control over these styles. Let me show you three powerful ways to bend WordPress block styles to your will.

What Are Block Style Variations?

Before we dive in, let’s clarify what we’re dealing with. WordPress includes pre-designed style variations for many of its blocks. Take the Button block – it comes with two styles out of the box: “Fill” and “Outline”. These are handy, but they might not fit your design vision.

Method 1: The Non-Developer Approach – Using the Site Editor

This is perfect if you’re not comfortable with code or just need a quick fix:

  1. Navigate to your Site Editor
  2. Go to StylesBlocks
  3. Find the block you want to modify (e.g., Buttons)
  4. Select the style variation you want to change
  5. Adjust colors, borders, spacing – whatever you need

Pro tip: Changes made here apply site-wide, saving you from manually updating each instance.

Method 2: The Developer’s Choice – theme.json

For more granular control, let’s get our hands dirty with theme.json. This is where you can really make WordPress blocks sing your tune.

Here’s a real-world example that transforms the outline button style:

{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 2,
  "styles": {
    "blocks": {
      "core/button": {
        "variations": {
          "outline": {
            "border": {
              "color": "#2563eb",  // Your brand blue
              "radius": "0",       // Sharp corners
              "style": "solid",
              "width": "3px"       // Bold border
            }
          }
        }
      }
    }
  }
}

This code turns those rounded, thin-bordered outline buttons into sharp-edged, attention-grabbing elements with a bold blue border.

Quick Reference for Common Blocks

  • Image block styles: styles.blocks.core/image
  • Quote block styles: styles.blocks.core/quote
  • List block styles: styles.blocks.core/list

Just replace the block name in your theme.json structure to customize any core block.

Method 3: The Nuclear Option – Removing Styles Entirely

Sometimes you don’t want to modify a style – you want it gone. Since WordPress registers its blocks using JavaScript, you’ll need to fight fire with fire:

wp.domReady( function() {
    // Remove the 'rounded' style from image blocks
    wp.blocks.unregisterBlockStyle( 'core/image', [ 'rounded' ] );
    
    // Remove multiple styles at once
    wp.blocks.unregisterBlockStyle( 'core/button', [ 'outline', 'squared' ] );
});

Add this to your theme’s JavaScript file (properly enqueued, of course) to eliminate unwanted style options from the block editor completely.

Best Practices and Common Pitfalls

Do’s:

  • Test your changes across different screen sizes
  • Keep accessibility in mind – ensure sufficient color contrast
  • Document your customizations for future you (or your team)
  • Use version control to track theme.json changes

Don’ts:

  • Don’t forget that JavaScript changes require proper enqueueing
  • Avoid overly specific selectors that might break with WordPress updates
  • Don’t remove styles that your content creators actively use

Real-World Example: Creating a Cohesive Design System

Let’s say you’re building a corporate theme with specific brand guidelines:

  • Primary color: #1e40af
  • No rounded corners anywhere
  • All interactive elements need 2px borders

Here’s how you’d implement this across all button styles:

{
  "styles": {
    "blocks": {
      "core/button": {
        "border": {
          "radius": "0"
        },
        "variations": {
          "fill": {
            "border": {
              "width": "2px",
              "style": "solid",
              "color": "transparent"
            }
          },
          "outline": {
            "border": {
              "width": "2px",
              "style": "solid", 
              "color": "#1e40af"
            }
          }
        }
      }
    }
  }
}

Tools and Resources

  • WordPress Developer Handbook: Your official guide to block customization
  • Theme.json Validator: Use the built-in schema to catch errors
  • Chrome DevTools: Inspect existing styles to understand what you’re overriding

Wrapping Up

Mastering WordPress block styles isn’t just about making things look pretty – it’s about creating a consistent, branded experience that aligns with your design vision. Whether you choose the visual approach through the Site Editor or dive into theme.json for pixel-perfect control, you now have the tools to make WordPress blocks truly yours.

Remember: Start small, test thoroughly, and always keep your users in mind. Happy styling!


Have questions about customizing WordPress blocks? Found a creative solution to share? Drop a comment below – I’d love to hear about your experiences with block style customization.