Building a Custom Block Part 7: Styling Inner Blocks

By Ian Svoboda on February 19, 2026

In Part 7 of Building a Custom Block, we’ll add default styles to the inner blocks inside our Notice block while making sure they remain fully customizable in the block editor.

In Part 6, we created block variations so users can insert preconfigured versions of our Notice block (Info, Success, Warning, Error) straight from the inserter. Each variation comes with its own icon, default inner blocks, and the correct style class applied automatically.

The Notice block is in a good place functionally. But if you look closely at the inner blocks, they’re still using their default styling. The heading inside the notice is the same size and weight it would be anywhere else on the page. The paragraph has its normal spacing. There’s nothing telling the browser that these elements are inside a notice and should look a little different.

In this part, we’ll add some contextual CSS to the inner blocks that makes them feel like they belong inside the notice. The key challenge is doing this in a way that doesn’t prevent users from customizing those blocks through the editor’s built-in controls.

Setting default styles for inner blocks

Right now, our Notice block has a nicely styled container (background colors, borders, accent stripe) but the content inside it is generic. A heading inside a notice looks the same as a heading anywhere else on the page. There’s no visual connection between the inner content and the notice itself.

Here’s what we’re looking to accomplish:

  • The heading should feel proportional to the notice, not like a full-sized page heading.
  • Paragraphs should have consistent sizing that works within the notice context.
  • Spacing between inner blocks should be tighter than what you’d see in normal page content.
  • All of this should act as defaults that the user can override using the block editor’s built-in typography, color, and spacing controls.

So when we’re done, we’ll have inner block styles that give us a nice starting point while still supporting customization in the editor. The best of both worlds!

Why Descendant Selectors Work Here

The simplest way to style inner blocks is to use descendant selectors in CSS. We target elements that live inside our block’s wrapper class. Something like:

SCSS
.wp-block-lwpd-notice .wp-block-heading {
  // styles for headings inside our notice
}

This approach works well for a couple of reasons:

  1. Specificity is low enough to be overridden. When a user sets a font size or color through the block editor, WordPress applies those values as inline styles on the element or by adding a class directly to the element that typically uses !important. So in both cases, those styles will be more specific than this selector (weight: 0, 2, 0).
  2. It’s predictable. You’re not doing anything clever with JavaScript or block filters. It’s just CSS doing what CSS does. If you’ve written styles for a component that has children before, this will feel very familiar.

The one thing to keep in mind is to avoid using !important on any of these styles.

This advice applies to writing CSS in just about any situation. In most situations where you think you might need to use !important, you probably don’t. There may be a very rare case where you need to and it makes more sense than trying to make a super specific selector.

Adding Inner Block Styles

We’ll be adding our styles to the block’s style.scss file. The contents of this file will display in the editor and the frontend, which is exactly what we need here. We’ll scope these to the container for the inner blocks wp-block-lwpd-notice__children. So any of our new styles will be placed in that selector.

src/blocks/notice/style.scss
.wp-block-lwpd-notice {
	--button-icon-size: 1.5rem;
	--button-padding: 0.5rem;

	--notice-bg: hsl(210deg 50% 95%);
	--notice-border: hsl(210deg 50% 80%);
	--notice-color: hsl(210deg 50% 20%);
	--notice-icon-color: hsl(210deg 50% 40%);
	--notice-accent: hsl(210deg 60% 50%);

	background-color: var(--notice-bg);
	border: 1px solid var(--notice-border);
	border-inline-start: 4px solid var(--notice-accent);
	box-sizing: border-box;
	color: var(--notice-color);
	font-size: 1.25rem;
	padding: 1rem;
	padding-inline-end: calc(var(--button-icon-size) + var(--button-padding) * 2);
	position: relative;

	&.is-style-success {
		--notice-bg: hsl(145deg 50% 93%);
		--notice-border: hsl(145deg 40% 72%);
		--notice-color: hsl(145deg 50% 16%);
		--notice-icon-color: hsl(145deg 45% 32%);
		--notice-accent: hsl(145deg 55% 40%);
	}

	&.is-style-warning {
		--notice-bg: hsl(40deg 90% 93%);
		--notice-border: hsl(40deg 70% 70%);
		--notice-color: hsl(40deg 60% 18%);
		--notice-icon-color: hsl(40deg 70% 38%);
		--notice-accent: hsl(40deg 80% 48%);
	}

	&.is-style-error {
		--notice-bg: hsl(0deg 60% 95%);
		--notice-border: hsl(0deg 50% 75%);
		--notice-color: hsl(0deg 50% 20%);
		--notice-icon-color: hsl(0deg 50% 40%);
		--notice-accent: hsl(0deg 65% 48%);
	}

  &__children {
    // New styles here
  }

	&[data-dismissible] {
		display: none;

		&.is-visible {
			display: block;
		}
	}

	&__dismiss {
		background-color: transparent;
		border: none;
		box-shadow: none;
		color: var(--notice-icon-color, currentColor);
		cursor: pointer;
		display: inline-flex;
		padding: var(--button-padding);
		position: absolute;
		right: 0;
		top: 0;

		svg {
			height: var(--button-icon-size);
			pointer-events: none;
			width: var(--button-icon-size);
		}
	}
}

Heading Styles

SCSS
&__children {

  .wp-block-heading {
      font-size: 1.125em;
      font-weight: 700;
  }
}

We’re targeting .wp-block-heading, which is the class WordPress applies to Heading blocks. A few things to note:

We’re using em units instead of rem. This is intentional. Since em is relative to the parent element’s font size, the heading will scale proportionally if someone changes the notice block’s overall font size. We set font-size: 1.25rem on the notice container, so 1.125em on the heading works out to roughly 22.5px by default. If a user bumps the notice’s font size up through the editor, the heading scales with it. Using rem here would make the heading size fixed regardless of the container’s font size, which isn’t always what you want.

We’re not setting a specific heading level style. Notice that we didn’t write .wp-block-heading h2 or target a specific level. This means our styles apply regardless of whether the user changes the heading to an h3, h4, or anything else. The heading will look consistent inside the notice no matter what level it is. This is a good default for a component like this where the heading is more of a label than a document-level heading.

Paragraph styles

SCSS
&__children {

  .wp-block-heading {
      font-size: 1.125em;
      font-weight: 700;
  }
  
  p {
    font-size: 1em;
  }
}

Here we set the default font-size of any paragraph block to 1em which will match whatever font-size is set on the notice block itself. This will help override any global theme styles while still not being overly specific.

Controlling Spacing with blockGap

Now that we’ve removed the default margins from the heading and paragraph, we need something to handle the spacing between them. This is where blockGap comes in.

If you recall from Part 2, we added blockGap to our block’s supports:

src/blocks/notice/block.json
"spacing": {
    "margin": true,
    "padding": true,
    "blockGap": true,
    "__experimentalDefaultControls": {
        "blockGap": true,
        "margin": false,
        "padding": false
    }
}

With blockGap enabled, WordPress generates layout styles that apply gap (or a margin-based fallback) to the inner blocks container. This means the spacing between inner blocks is controlled by a single value that users can adjust in the editor’s sidebar under “Block spacing.”

When your block supports “layout” there are certain attributes that can be set to determine if the container is a flex, grid, or flow-based layout container. And there are other attributes that control things like wrapping behavior, orientation, and more.

Block spacing (aka blockGap) is defined in one of two ways:

  • Gap (flex and grid based layouts)
  • margin-block-start (flow based layouts)

The Block Editor applies block spacing using generated classes and it makes it trickier to style using custom styles like we’ve been doing so far. This is further complicated by the CSS property being different based on the layout’s type (flow, flex, or grid).

The best solution would allow us to set one value that works for the inner blocks container no matter what its layout settings are. And that’s just what we’re going to do!

To start, head over to block.json and update it to include a new default type and orientation for the layout attributes and set a default blockGap style attribute for this block.

src/blocks/notice/block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "lwpd/notice",
	"version": "0.1.0",
	"title": "Notice",
	"category": "widgets",
	"icon": "info-outline",
	"description": "Example block scaffolded with Create Block tool.",
	"example": {},
	"attributes": {
		"isDismissible": {
			"type": "boolean",
			"default": false
		},
		"style": {
			"type": "object",
			"default": {
				"spacing": {
					"blockGap": "0.5rem"
				}
			}
		}	
	},
	"styles": [
		{
			"name": "info",
			"label": "Info",
			"isDefault": true
		},
		{
			"name": "success",
			"label": "Success"
		},
		{
			"name": "warning",
			"label": "Warning"
		},
		{
			"name": "error",
			"label": "Error"
		}
	],
	"supports": {
		"align": [ "wide", "full" ],
		"anchor": true,
		"color": {
			"background": true,
			"text": true,
			"link": true
		},
		"html": false,
		"layout": {
			"default": {
				"type": "flex",
				"orientation": "vertical"
			}
		},
		"spacing": {
			"margin": true,
			"padding": true,
			"blockGap": true,
			"__experimentalDefaultControls": {
				"blockGap": true,
				"margin": false,
				"padding": false
			}
		},
		"typography": {
			"fontSize": true,
			"lineHeight": true,
			"__experimentalFontFamily": true,
			"__experimentalFontStyle": true,
			"__experimentalFontWeight": true,
			"__experimentalDefaultControls": {
				"fontSize": true,
				"lineHeight": true
			}
		}
	},
	"textdomain": "notice",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"viewScript": "file:./view.js"
}

This gives us a small default gap between all inner blocks. Because we’re setting the default value of the style attribute, only new notice blocks will receive this style. So if you have one in your editor, you should remove it and insert a new one to see the effect in action.

A user can click on the Notice block and see the current Block Spacing value and change it as they see fit:

Screenshot of the block editor block sidebar showing the dimensions UI setting

Adding a Subtle Link Color

While we’re styling inner block elements, let’s add a default link color that works with our notice styles. If someone adds a link inside a notice paragraph, it should look intentional rather than clashing with the notice’s color scheme.

We already have a --notice-accent variable that represents the strongest color in each variant. We can use this for links:

src/blocks/notice/style.scss
&__children {

  .wp-block-heading {
      font-size: 1.125em;
      font-weight: 700;
  }
  
  p {
    font-size: 1em;
  }
  
  a {
    color: var(--notice-accent);
  }
}

Since the accent color changes per variant (blue for info, green for success, etc.), links will automatically match the notice type. And because we enabled link color support in block.json ("link": true inside the color support), users can still override this through the editor if they want.

How the Editor Handles Overrides

Let’s talk about what happens when a user decides to customize one of these inner blocks through the editor.

Say someone selects the heading inside a notice and changes its font size to “Large” using the typography controls. WordPress applies an inline style like style="font-size: var(--wp--preset--font-size--large)" directly on the heading element. Because inline styles have higher specificity than our class-based descendant selector (.wp-block-lwpd-notice .wp-block-heading), the user’s choice wins. Our font-size: 1.125em is overridden and the heading renders at whatever “Large” maps to in the active theme.

The same applies to colors. If someone uses the color controls to change the heading’s text color, that inline style overrides the color value the heading would otherwise inherit from the notice container. The user is in control and our defaults stay out of the way.

This is the whole point of keeping specificity low. If we had written something like:

SCSS
// Don't do this
.wp-block-lwpd-notice .wp-block-heading {
    font-size: 1.125em !important;
}

When styles include !important the selector steps outside of CSS Specificity, which makes debugging styles (or writing other overrides) much more difficult. As noted earlier, you should avoid doing this when writing CSS unless absolutely necessary (which it almost never is).

Editor-Only Adjustments

In some cases, you might need styles that only apply in the block editor. For example, the editor wraps each inner block in additional markup for selection handling, toolbars, and drag-and-drop. This can sometimes affect how your styles render compared to the frontend.

If you run into a situation where the editor needs slightly different styles, you can add them to editor.scss instead of style.scss. Remember that style.scss loads in both the editor and on the frontend, while editor.scss only loads in the editor.

For our notice block, the styles we’ve written so far should look consistent in both contexts. But if you ever notice a discrepancy (maybe the editor adds extra padding around inner blocks, or spacing looks slightly off), editor.scss is where you’d address it.

A common scenario where this comes up is when the editor’s block list markup adds extra wrappers. In those cases, you might need to adjust a selector to account for the editor’s DOM structure. Just be careful to keep these overrides minimal and well-documented so future you understands why they exist.

A Note on Targeting Inner Blocks

You might be tempted to get more specific with your selectors and target things like lists, images, or other blocks that someone could potentially insert into the notice. My recommendation is to only style what you actually expect to be there.

Our variations define a heading and a paragraph as the default inner blocks. Those are the elements worth styling. If someone inserts a list, a code block, or something else, it’s reasonable for those to use their own default styling. You don’t need to anticipate every possible block that could end up inside your notice.

If you find yourself writing styles for ten different block types inside your block, that’s usually a sign that you might want to restrict which blocks are allowed inside the notice. We haven’t covered that yet, but it’s something we’ll look at in a future part.

Additional cleanup

While we’re here, let’s update the button CSS variables we wrote before to include the same notice-* prefix that the other variables have.

Then we can ensure the variables are all nicely organized alphabetically at the block’s top level selector.

src/blocks/notice/style.scss
.wp-block-lwpd-notice {
	--notice-accent: hsl(210deg 60% 50%);
	--notice-bg: hsl(210deg 50% 95%);
	--notice-border: hsl(210deg 50% 80%);
	--notice-button-icon-size: 1.5rem;
	--notice-button-padding: 0.5rem;
	--notice-color: hsl(210deg 50% 20%);
	--notice-icon-color: hsl(210deg 50% 40%);

	background-color: var(--notice-bg);
	border: 1px solid var(--notice-border);
	border-inline-start: 4px solid var(--notice-accent);
	box-sizing: border-box;
	color: var(--notice-color);
	font-size: 1.25rem;
	padding: 1rem;
	padding-inline-end: calc(var(--notice-button-icon-size) + var(--notice-button-padding) * 2);
	position: relative;

	&.is-style-success {
		--notice-accent: hsl(145deg 55% 40%);
		--notice-bg: hsl(145deg 50% 93%);
		--notice-border: hsl(145deg 40% 72%);
		--notice-color: hsl(145deg 50% 16%);
		--notice-icon-color: hsl(145deg 45% 32%);
	}

	&.is-style-warning {
		--notice-accent: hsl(40deg 80% 48%);
		--notice-bg: hsl(40deg 90% 93%);
		--notice-border: hsl(40deg 70% 70%);
		--notice-color: hsl(40deg 60% 18%);
		--notice-icon-color: hsl(40deg 70% 38%);
	}

	&.is-style-error {
		--notice-accent: hsl(0deg 65% 48%);
		--notice-bg: hsl(0deg 60% 95%);
		--notice-border: hsl(0deg 50% 75%);
		--notice-color: hsl(0deg 50% 20%);
		--notice-icon-color: hsl(0deg 50% 40%);
	}
	
	&__children {
		
		.wp-block-heading {
			font-size: 1.125em;
			font-weight: 700;
		}
		
		p {
			font-size: inherit;
		}
		
		a {
			color: var(--notice-accent);
		}
	}

	&[data-dismissible] {
		display: none;

		&.is-visible {
			display: block;
		}
	}

	&__dismiss {
		background-color: transparent;
		border: none;
		box-shadow: none;
		color: var(--notice-icon-color, currentColor);
		cursor: pointer;
		display: inline-flex;
		padding: var(--notice-button-padding);
		position: absolute;
		right: 0;
		top: 0;


		svg {
			height: var(--notice-button-icon-size);
			pointer-events: none;
			width: var(--notice-button-icon-size);
		}
	}
}

The Full Updated Stylesheet

Here’s the complete style.scss with all the inner block styles we’ve added:

src/blocks/notice/style.scss
.wp-block-lwpd-notice {
	--notice-accent: hsl(210deg 60% 50%);
	--notice-bg: hsl(210deg 50% 95%);
	--notice-border: hsl(210deg 50% 80%);
	--notice-button-icon-size: 1.5rem;
	--notice-button-padding: 0.5rem;
	--notice-color: hsl(210deg 50% 20%);
	--notice-icon-color: hsl(210deg 50% 40%);

	background-color: var(--notice-bg);
	border: 1px solid var(--notice-border);
	border-inline-start: 4px solid var(--notice-accent);
	box-sizing: border-box;
	color: var(--notice-color);
	font-size: 1.25rem;
	padding: 1rem;
	padding-inline-end: calc(var(--notice-button-icon-size) + var(--notice-button-padding) * 2);
	position: relative;

	&.is-style-success {
		--notice-accent: hsl(145deg 55% 40%);
		--notice-bg: hsl(145deg 50% 93%);
		--notice-border: hsl(145deg 40% 72%);
		--notice-color: hsl(145deg 50% 16%);
		--notice-icon-color: hsl(145deg 45% 32%);
	}

	&.is-style-warning {
		--notice-accent: hsl(40deg 80% 48%);
		--notice-bg: hsl(40deg 90% 93%);
		--notice-border: hsl(40deg 70% 70%);
		--notice-color: hsl(40deg 60% 18%);
		--notice-icon-color: hsl(40deg 70% 38%);
	}

	&.is-style-error {
		--notice-accent: hsl(0deg 65% 48%);
		--notice-bg: hsl(0deg 60% 95%);
		--notice-border: hsl(0deg 50% 75%);
		--notice-color: hsl(0deg 50% 20%);
		--notice-icon-color: hsl(0deg 50% 40%);
	}
	
	&__children {
		
		.wp-block-heading {
			font-size: 1.125em;
			font-weight: 700;
		}
		
		p {
			font-size: inherit;
		}
		
		a {
			color: var(--notice-accent);
		}
	}

	&[data-dismissible] {
		display: none;

		&.is-visible {
			display: block;
		}
	}

	&__dismiss {
		background-color: transparent;
		border: none;
		box-shadow: none;
		color: var(--notice-icon-color, currentColor);
		cursor: pointer;
		display: inline-flex;
		padding: var(--notice-button-padding);
		position: absolute;
		right: 0;
		top: 0;


		svg {
			height: var(--notice-button-icon-size);
			pointer-events: none;
			width: var(--notice-button-icon-size);
		}
	}
}
Screenshot showing the Notice block with the updated inner block styles applied. The heading is smaller and bolder, spacing is compact, and the overall look is more cohesive.

Quick Recap

Here’s what we covered in this part:

  • Used descendant selectors to apply default styles to heading and paragraph blocks inside the notice.
  • Used em units for inner block font sizes so they scale relative to the notice container.
  • Added a default link color using the existing --notice-accent CSS variable.
  • Added a default gap on the __children wrapper as a baseline for inner block spacing.
  • Kept specificity low so the block editor’s inline styles can always override our defaults.

The core principle here is that your block’s CSS should provide sensible defaults, not rigid constraints. The block editor gives users a lot of control over typography, colors, and spacing.

Your job as the block developer is to make sure the block looks good out of the box while staying out of the way when someone wants to make it their own.

Next Steps

In Part 8, we’ll look at how to restrict which blocks can be inserted inside the notice and discuss how you might use template locking to enforce a specific inner block structure.

Further Reading



Leave a Reply

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