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 tonull
, 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 Property | CSS Value |
---|---|
--wp--preset--spacing--20 | 0.44rem |
--wp--preset--spacing--30 | 0.67rem |
--wp--preset--spacing--40 | 1rem |
--wp--preset--spacing--50 | 1.5rem |
--wp--preset--spacing--60 | 2.25rem |
--wp--preset--spacing--70 | 3.38rem |
--wp--preset--spacing--80 | 5.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 isrem
.
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 Property | CSS Value |
---|---|
--wp--preset--spacing--20 | 0.25rem |
--wp--preset--spacing--30 | 0.5rem |
--wp--preset--spacing--40 | 0.75rem |
--wp--preset--spacing--50 | 1rem |
--wp--preset--spacing--60 | 1.25rem |
--wp--preset--spacing--70 | 1.5rem |
--wp--preset--spacing--80 | 1.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.
Leave a Reply