WordPress.org

WordPress Developer Blog

Everything you need to know about spacing in block themes

In design, spacing is everything. Too little of it, and your text gets cramped and tough to read. Too much of it, things start to feel like they’re floating away. As with most things in life, you need a balance, and when you’re building a website, spacing plays a crucial role in accomplishing that.

Traditionally, web designers handled spacing in a stylesheet. Some designers would spend hours meticulously tinkering with spacing to get everything just right. Then they’d scrap it all and start over the minute they added a typeface with a different x-height. Others would build on top of a system—maybe a design framework—that suited their style well enough they use it for most projects. Either way, it typically meant working directly with CSS.

With block themes, you will set most of your spacing in the theme.json file (classic/hybrid themes may also support this feature). Making the switch to JSON can feel a little foreign if you are new to block theming. But once you open the file, the style rules will look more like CSS rules than you might have expected; the main differences are in syntax. And, ultimately, every style configuration maps back to CSS properties on output.

This tutorial will walk you through configuring global spacing settings and styling properties like margin and padding in theme.json. It will also outline some additional tips, such as adding contextual spacing and customizing the Spacer block.

Table of contents

Global settings and styles

Themes can define global settings and styles via their theme.json files. The settings.spacing object lets you configure options for the user interface and define preset values your users can apply to many other places. Under the styles object, you can use spacing presets at the root, element, or block level.

Configuring global settings

The settings.spacing key in theme.json is a configuration object for spacing. Each of the options let you do one of two things:

  • Enable or disable an option in the interface
  • Create a group of presets or options that users can select in the interface or use in block or element styles

Here is the complete list of available settings as of WordPress 6.2:

  • blockGap: (boolean/null) Whether to enable the Block Spacing option for blocks that support it. Defaults to null. If set to null, the CSS is also disabled.
  • margin: (boolean) Whether to enable the Margin option for supported blocks. Defaults to false.
  • padding: (boolean) Whether to enable the Padding option for supported blocks. Defaults to false.
  • customSpacingSize: (boolean) Whether to allow users to input custom spacing values for supported blocks. Defaults to true.
  • spacingScale: (object) A configuration object for defining a custom spacing scale. WordPress defines a default scale with seven steps that increment by 1.5rem.
  • spacingSizes: (array) A set of custom size objects that may overrule the spacing scale or add to it.
  • units: (array) A set of possible CSS units that users can select from when adding custom spacing.

Most of these should be straightforward for you to configure. You will learn more about the two most complex settings, spacingScale and spacingSizes, in the next sections.

The following is an example theme.json with all of the settings enabled for the user. It also includes a set of custom spacing sizes:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"spacing": {
			"blockGap": true,
			"margin": true,
			"padding": true,
			"customSpacingSize": true,
			"spacingScale": {
				"steps": 0
			},
			"spacingSizes": [
				{
					"name": "Step 1 - Fixed",
					"size": "0.25rem",
					"slug": "10"
				},
				{
					"name": "Step 2 - Fixed",
					"size": "0.5rem",
					"slug": "20"
				}
			],
			"units": [
				"%",
				"px",
				"em",
				"rem",
				"vh",
				"vw"
			]
		}
	}
}

Customizing the spacing scale

WordPress generates a default spacing scale, as shown in this table:

CSS Custom PropertyCSS Value
--wp--preset--spacing--200.44rem
--wp--preset--spacing--300.67rem
--wp--preset--spacing--401rem
--wp--preset--spacing--501.5rem
--wp--preset--spacing--602.25rem
--wp--preset--spacing--703.38rem
--wp--preset--spacing--805.06rem

Your users can select from the range of values if your theme enables support for the blockGap, margin, or padding settings. You can also use these values in the styles section of your theme.json file, or your stylesheets—just reference the CSS custom property.

You can also configure a custom spacing scale if the default one in WordPress does not suit your design. How? Configure settings.spacing.spacingScale. It accepts these properties:

  • operator: (string) The operator used to increment the scale, which can be a * for multiplication or a + for addition. Defaults to *.
  • increment:  (integer) A numeric value used to increment the scale. The default value is 1.5.
  • steps: (integer) The total number of steps in the scale. The default value is 7.
  • mediumStep: (integer) The medium value of the scale. The default value is 1.5.
  • unit: (string) A valid CSS spacing unit (e.g., px, em, rem, vh, vw, and %). The default value is rem.

The following theme.json example creates a scale with seven steps that increments by 0.25rem:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"spacing": {
			"spacingScale": {
				"operator": "+",
				"increment": 0.25,
				"steps": 7,
				"mediumStep": 1,
				"unit": "rem"
			}
		}
	}
}

The code will create a scale that is much different than the original WordPress scale. Compare the default values to those shown in this table:

CSS Custom PropertyCSS Value
--wp--preset--spacing--200.25rem
--wp--preset--spacing--300.5rem
--wp--preset--spacing--400.75rem
--wp--preset--spacing--501rem
--wp--preset--spacing--601.25rem
--wp--preset--spacing--701.5rem
--wp--preset--spacing--801.75rem

While settings.spacing.spacingScale makes it easy to create a scale on the fly, it is limited. It does let you use viewport units, but it does not let you calculate your own fluid sizes. For that reason, you might want to opt out of the spacing scale and register custom sizes.

Registering custom spacing sizes

If you want tighter control over the spacing, opt for registering custom sizes instead of a scale. 

Start by defining an array of presets in settings.spacing.spacingSizes. The array should contain objects that define individual sizes and set these properties:

  • name: (string) A human-readable title for the spacing size that can be translated into other languages.
  • size: (string) A valid CSS size. This can be a number and unit, a fluid size using clamp(), or a reference to another custom CSS property.
  • slug: (string) The slug for the size, which will be appended to a generated CSS custom property: --wp--preset--spacing--{slug}.

The following code example registers a mix of five spacing sizes, both fixed and fluid. Also, note that settings.spacingScale.steps is set to 0, which disables the default core scale.

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"spacing": {
			"spacingScale": {
				"steps": 0
			},
			"spacingSizes": [
				{
					"name": "Step 1 - Fixed",
					"size": "0.25rem",
					"slug": "10"
				},
				{
					"name": "Step 2 - Fixed",
					"size": "0.5rem",
					"slug": "20"
				},
				{
					"name": "Step 3 - Fixed",
					"size": "0.75rem",
					"slug": "30"
				},
				{
					"name": "Step 4 - Fixed",
					"size": "1rem",
					"slug": "40"
				},
				{
					"name": "Step 5 - Fluid",
					"size": "clamp(1.125rem, 1.25vw + 0.7rem, 1.25rem)",
					"slug": "50"
				}
			]
		}
	}
}

These options should appear as choices for the Block Spacing, Margin, and Padding controls in supported blocks, as shown in the following screenshot:

You can register as many custom sizes as you need. But once you make more than seven, the UI will change to a dropdown select field instead of a range slider. 

Technically, you can use the spacing scale and custom sizes at the same time. It’s generally less cumbersome to opt for one over the other. But, there are times when you might need to tack an extra custom size on top of the generated scale.

Configuring global styles

In the previous sections, you learned how to configure custom spacing options and presets. Those earlier steps were important, because now you will use what you just built as the foundation for styling blocks and elements in theme.json—and maybe for accessing presets in custom CSS.

A primer on global styles

The styles object in theme.json governs the look of the root element, blocks, and supported HTML elements. (For more on how this works, read the guide in the Theme Handbook.) 

For now, know that you can add custom styles at these levels:

  • styles: Any styles applied here are added directly to the root element of the document.
  • blocks: Houses block objects with custom styles. These can also house an elements object for styling elements within a specific block.
  • elements: Holds HTML element objects with custom styles.

This should be structured like this theme.json example:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"styles": {
		"blocks": {},
		"elements": {}
	}
}

The spacing object

When you are styling the root element, blocks, or supported HTML elements, you must add a spacing object to hold the blockGap, margin, and padding properties.

The following is an example of what this object might look like:

{
	"spacing": {
		"blockGap": "2rem",
		"margin": {
			"top": "1rem",
			"bottom": "1rem"
		},
		"padding": {
			"top": "2rem",
			"bottom": "2rem",
			"right": "1rem",
			"left": "1rem"
		}
	}
}

The values you add must be valid CSS that includes references to the CSS custom properties configured via settings.spacing in theme.json. Both margin and padding support setting all four sides, but the blockGap style should only be a single value.

Creating a vertical rhythm (block spacing)

A huge part of creating a pleasing design is getting the vertical rhythm of the page just right. Of course, spacing is only one aspect; your typography, particularly line heights, is just as important. This tutorial will assume you know what values to apply and will focus on how to add those values to your theme.

You will learn how to control the spacing in between blocks like the whitespace between paragraphs in this screenshot:

In traditional design, you might define this as margin-top to all block-level HTML elements in your CSS to keep them consistently spaced. In WordPress, this property is named blockGap. In essence, the two methods work the same.

Technically, the blockGap property maps to both the CSS margin-top and gap properties, depending on whether the blocks are in a flow or flex layout, respectively. So, it is, quite literally, the “gap” between blocks.

The blockGap property is displayed as Block Spacing in the user interface.

By default, WordPress sets a block gap of 24px, which will work in some designs but not every single one. It’s likely this is the one style you will customize for every theme.

Suppose your design needed 2rem between blocks and elements by default. You would add this to the top level of the styles object, as you see in this code:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"styles": {
		"spacing": {
			"blockGap": "2rem"
		}
	}
}

Applying spacing to blocks

Now, it’s time to start applying some of those custom spacing presets you registered earlier. Remember you can reference a preset with its CSS custom property, which will follow the --wp--preset--spacing--{slug} naming convention.

Suppose you want to use this design for the core Pullquote block:

Since you have top and bottom borders, you will want some spacing between them and the inner text. You can do that using one of your custom presets.

Here’s an example set of styles you could use for the Pullquote block:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"styles": {
		"blocks": {
			"core/pullquote": {
				"border": {
					"color": "var( --wp--preset--color--black )",
					"style": "solid",
					"width": "var( --wp--preset--spacing--10 )"
				},
				"spacing": {
					"padding": {
						"top": "var( --wp--preset--spacing--30 )",
						"bottom": "var( --wp--preset--spacing--30 )"
					}
				}
			}
		}
	}
}

To get the whitespace you need between the text and borders, the code added top and bottom padding using one of the presets from the spacing scale.

You likely noticed that the preceding code also used a preset for the border width. Because presets are just CSS custom properties, you can use them in lots of places and for lots of things when you need them.

Contextual spacing for headings

When you’re designing body copy, it’s common to add spacing to a heading that follows a paragraph and then close up the space between the heading and elements that follow. Depending on your design, that may be true of other elements.

It won’t always work just to add some extra margin above your headings in theme.json, because the blockGap setting will almost always override vertical margin settings. Often, all you can do is write some custom CSS.

Depending on your actual scenario, you may have to adjust things. So, think of the example below as a guideline, not a rule.

Assume you have a blockGap setting of 2rem. But, in the normal “flow” layout, you want to add 3rem of top margin to headings and reduce the top margin of sibling elements to 1rem, so your content looks like this:

This CSS will do the job:

.is-layout-flow * + :is( h1, h2, h3, h4, h5, h6 ),
.wp-block-post-content * + :is( h1, h2, h3, h4, h5, h6 )  {
	margin-top: 3rem;
}

.is-layout-flow :is( h1, h2, h3, h4, h5, h6 ) + *,
.wp-block-post-content :is( h1, h2, h3, h4, h5, h6 ) + * {
	margin-top: 1rem;
}

The code specifically targets .wp-block-post-content so that it works in the post editor. That’s because the editor itself has no way of knowing the front-end layout of your post content. In some cases, you might only target the Post Content block; in others, you might target other blocks.

There are some edge cases this CSS does not cover. For example, you might want different spacing between a Heading block and Spacer or some other block/element.

Root padding aware alignments

In some designs, the root element of the page needs horizontal padding. But when you build those designs, you sometimes need to let blocks stretch the full width of the screen—beyond the left and right padding. Fortunately, WordPress 6.1 introduced a setting that lets you do exactly that.

Technically, this isn’t only a spacing feature—it partly falls in the world of layout. But, it does affect decisions you make with horizontal spacing on the root element.

If you want to enable alignments that are aware of the root padding, you can enable settings.useRootPaddingAwareAlignments in theme.json, like this:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"useRootPaddingAwareAlignments": true
	},
	"styles": {
		"spacing": {
			"padding": {
				"left": "var( --wp--preset--spacing--40 )",
				"right": "var( --wp--preset--spacing--40 )"
			}
		}
	}
}

Of course, this setting only makes sense if you have set left and right padding on the root element, as in the preceding code block.

WordPress works a little magic behind the scenes when this setting is enabled. Instead of applying the padding to the root, it adds a .has-global-padding class to inner container blocks, which applies the padding to the blocks themselves. And, when a full-width block is nested inside of those, it applies a negative margin to stretch outside their container.

Here’s a full-width image stretching beyond its padded container to the edges of the screen:

Using the Spacer block

From a technical viewpoint, the Spacer block is not an ideal method of spacing. It adds an empty <div> to the document output, just to create whitespace. This breaks every good web design standard out there, especially the separation of content and style.

However, it is arguably the most intuitive spacing tool in WordPress. From a user-experience standpoint, it’s easier to recommend users place Spacer blocks wherever they need them than to teach new users, especially, to configure spacing block by block.

But, is it a good theme development tool? Maybe, at least in a few cases.

In general, when building themes, you should lean on block gap, margin, and padding settings to handle spacing. But, there is a case for deploying the Spacer block in custom patterns, or in template parts, that users might need to adjust for themselves, without a lot of fuss. 

Sometimes, the user experience wins out over the purist’s vision of designing by the book. Ultimately, you’ll need to decide where and when you use the block.

Because the fact remains that users do have access to it. Which means some of them will use it. In the next few sections, you will learn how to improve this experience with workarounds for the Spacer block’s current limitations.

Update: As of Gutenberg 15.6, the Spacer block supports spacing presets. The following sub-sections of this article will likely not be necessary once WordPress 6.3 has shipped.

Custom Spacer block variations

One downside to the Spacer block is that it does not currently support theme-defined height presets. What’s more, the custom heights users select are not responsive. 

The good news: there are ways to design around those limitations. The rest of the news: those workarounds have their limitations.

For example, one workaround is to register block variations with custom heights. These heights can be fixed or fluid sizes.

Create a new assets/js/editor.js file in your theme folder. Then, add the following code to register small, medium, and large variations of the Spacer block:

wp.domReady( () => {
	wp.blocks.registerBlockVariation( 'core/spacer', {
		name: 'small',
		title: 'Spacer: Small',
		attributes: {
			height: '2rem'
		},
		scope: [ 'block', 'inserter', 'transform' ]
	} );

	wp.blocks.registerBlockVariation( 'core/spacer', {
		name: 'medium',
		title: 'Spacer: Medium',
		attributes: {
			height: 'clamp(2rem, calc(2rem + ((1vw - 0.4rem) * 8.3333)), 4rem)'
		},
		scope: [ 'block', 'inserter', 'transform' ]
	} );

	wp.blocks.registerBlockVariation( 'core/spacer', {
		name: 'large',
		title: 'Spacer: Large',
		attributes: {
			height: 'clamp(4rem, calc(4rem + ((1vw - 0.4rem) * 16.6667)), 8rem)'
		},
		scope: [ 'block', 'inserter', 'transform' ]
	} );
} );

For the small variation, the code uses a fixed height value. The medium and large variations use clamp() to create fluid sizes that adjust based on the screen’s width. 

A clamp() value will show up as empty in the Height control in the Spacer block’s sidebar. But, it is saved correctly as an attribute, and the CSS works as it should.

For your custom variations to appear, load the editor.js file in the editor by adding the following code your theme’s functions.php file:

add_action( 'enqueue_block_editor_assets', 'themeslug_editor_scripts' );

function themeslug_editor_scripts() {
	wp_enqueue_script(
		'themeslug-editor',
		get_theme_file_uri( 'assets/js/editor.js' ),
		[ 'wp-blocks', 'wp-dom-ready', 'wp-edit-post' ],
		filemtime( get_theme_file_path( 'assets/js/editor.js' ) ),
		true
	);
}

Now, when you insert the Spacer block, you should see a dropdown of your custom variations in the editor, as in the screenshot below:

The variations should also appear in the inserter and be available via slash commands. If you want to control where the variations are available, you can do that in the scope parameter when you’re registering them.

Responsive Spacer blocks

Another trick: you can limit overly tall Spacer blocks with custom CSS media queries. You do risk overruling your user’s intention, so think about your theme’s audience before you do this.

You could add the custom CSS to your theme’s primary style.css file. But, since you will probably tie this code to a specific block, you should register a custom block stylesheet, as described in Leveraging theme.json and per-block styles for more performant themes.

Create a new assets/css/blocks/core-spacer.css file in your theme and add this code to it:

@media ( max-width: 48rem ) {
        .wp-block-spacer {
                max-height: 4rem;
        }
}

@media ( max-width: 40rem ) {
        .wp-block-spacer {
                max-height: 2rem;
        }
}

The preceding code limits every instance of the Spacer block to a maximum height of 4rem on medium screen sizes and 2rem on small screens. Of course, you should adjust those numbers to suit your design.

Now, add this code to your functions.php file to load the block stylesheet on both the editor and front-end views:

add_action( 'init', 'themeslug_enqueue_block_styles' );

function themeslug_enqueue_block_styles() {
	wp_enqueue_block_style( 'core/spacer', array(
		'handle' => 'themeslug-block-spacer',
		'src'    => get_theme_file_uri( 'assets/css/blocks/core-spacer.css' ),
		'path'   => get_theme_file_path( 'assets/css/blocks/core-spacer.css' )
	) );
}

Props to @marybaum and @bph for feedback and review. Photo of dog and Wapuu by @wapuu from the WordPress photos directory.

Categories:

13 responses to “Everything you need to know about spacing in block themes”

  1. Scott Fennell Avatar
    Scott Fennell

    Thanks for this article and everything else you’ve done for the community.

    HOWEVER!!!

    Media queries being relegated to custom css makes it harder to get excited about embracing The New WordPress. It seems like they should be a first class citizen when registering styles, anywhere styles are registered.

    1. Justin Tadlock Avatar

      Thank you for reading and the feedback.

      The one example of using a media query here is just a workaround for a custom use case. I’d much prefer fluid sizing for the Spacer block based on presets, which would be at the moment of registration. The example in the post could’ve also been just as easily handled with a fluid size, but I wanted to provide another approach. Regardless, I agree that it should be handled when registering the style preset.

      There’s an open ticket, which definitely could use a PR (maybe even a “good first issue” for anyone looking to contribute). That would definitely fix the need for the custom CSS in this case.

      1. Scott Fennell Avatar
        Scott Fennell

        I set myself up here for a pretty straightforward rebuttal, because of the existence of fluid sizing. Touche.

        But more broadly, the non-size-related cases of…
        – “show this element on small screens, and instead show that on large screens”
        – “make this red on small screens and blue on large screens.”

        still seem to be in need of acknowledgement from the Gutenberg project.

        Apologies for being a bit off topic. It’s an excellent article about spacing.

        1. Justin Tadlock Avatar

          Oh, I thought you were talking about the media query example in the post. I actually have another post here on the Dev Blog that dives into the current approach of following more of an intrinsic design path rather than responsive UI controls: Intrinsic design, theming, and rethinking how to design with WordPress. That’s a good place to add feedback on the broader topic.

          There are definitely folks working on the Gutenberg project who acknowledge those use cases. It’s a topic that is discussed often and with a lot of care, particularly around the user experience.

          Speaking for myself, I want to see how much we can push without too many responsive settings first. I don’t know if the correct answer is putting everything in core vs. letting plugins extend what core is doing—there’s probably a good middle ground somewhere in there. I’ve personally coming around more and more to the idea of adding more settings directly into core (vs. the old approach of limiting options), which has proven to be a good thing in many cases.

  2. Matt Whiteley Avatar

    Great article Justin. Out of curiosity, do you know of a way to limit the spacing to vertical only (top & bottom)? I love the steps spacing, but almost 100% of the time I only want the vertical padding to vary, not he horizontal and since it adds the CSS inline, aside form overriding the horizontal padding with !important CSS, which is no good, it isn’t the greatest solution.

    1. brettsmason Avatar

      I had a similar issue. I worked out that this can be achieved using the blocks.registerBlockType JavaScript filter.

      Here is a full example I use for the group block:

      /**
      * @param {Object} settings The block settings
      * @param {string} name The block name
      * @return {Object} The block settings
      */
      function groupSupports(settings, name) {
      if (name !== 'core/group') {
      return settings;
      }

      return assign({}, settings, {
      supports: assign({}, settings.supports, {
      spacing: {
      padding: ['top', 'bottom'],
      margin: ['top', 'bottom'],
      },
      }),
      });
      }

      addFilter(
      'blocks.registerBlockType',
      'pulsar/group-block/supports',
      groupSupports
      );

      1. Matt Whiteley Avatar

        Great tip! I’ll be giving this a try today. Thanks!

      2. Dima Avatar
        Dima

        Hi, perhaps you know how to set default values for margin and padding? I’ve tried various options, but something didn’t work out. Thank you in advance.

  3. AvaGen Avatar

    Great resource. Thank you for this

  4. mfs Avatar
    mfs

    Very helpful, thanks!

    But can you clarify the section “Customizing the spacing scale” as there are a couple properties said to be (integer) but the defaults are floats.

    Maybe this happens elsewhere on this page as well, IDK? I’m focused on Spacing Scale ATM 🙂

  5. Ren2049 Avatar
    Ren2049

    Can’t we set a fluid root em value (to define what 1 rem is) to make use of the auto spacing scale including fluidity, without having to set custom sizes?

  6. Link Avatar
    Link

    Thanks for the tutorial, these are helpful! However it blows my mind why we need to use code to enable basic functionality in WordPress, such as for enable gap, rem, some tags/categories, svg upload or even custom css wont load unless you know about the very hidden methods to enable it. Why no GUI settings/switches?
    It makes me wonder who WordPress really is targeting. No-skilled users living in their fantasy wanting no-code ends up copy-pasting random code from the web and/or downloading some random theme publishing a site they have no idea how to manage (however they adds up the “popularity” of CMS’s like wordpress, which comes back biting us from behind..) and we who do like the web and real code ends up banging our heads while trying to understand the wordpress way. It just feels like a really strong identity crisis.

    1. Justin Tadlock Avatar

      WordPress’ target audience is a wide range of people. And many (most?) people will never need to touch code.

      This is the Developer Blog, so things discussed here are going to be from a developer’s viewpoint. There are many UI features that are controllable by the user, but much of what we focus on will be teaching developers how to enable/disable features in the UI (often to control the user experience). For example, theme developers can enable blockGap based on the needs of their theme or audience.

Leave a Reply

Your email address will not be published. Required fields are marked *