Skip to content
Skip to the article
In WordPress: 10 articles
WordPress

Register custom block styles in a classic theme

Add selectable block style variations to core and third-party blocks from a classic or child theme, with one stylesheet per style.

Updated
Applies to
  • WordPress 6.x
Tags
  • wordpress
  • block-editor
  • themes
  • css
Reading time
4 min

Block styles are the "Styles" options that appear in the block sidebar (for example "Outline" on a Button). A classic theme or child theme can register its own so editors can apply consistent design treatments without writing CSS classes by hand. Use this pattern when a site has a classic theme but editors build pages with the block editor.

How It Works

register_block_style() adds a style to a block. When an editor picks it, WordPress adds the class is-style-<name> to the block's wrapper. The style can point to a registered stylesheet through style_handle.

When WordPress loads block assets on demand, the stylesheet only loads on pages that render that block. Block themes always do this. On a classic theme, return true from the should_load_separate_core_block_assets filter to make sure it's on. When on-demand loading is off, the stylesheet loads on every page. Either way, it also loads in the editor so the preview matches the front end.

Folder Layout

Keep one small CSS file per style, named after the style:

my-child-theme/
├── functions.php
├── inc/
│   └── block-styles.php
└── block-styles/
    ├── button-accent.css
    ├── columns-reversed-mobile.css
    ├── group-feature-left.css
    └── heading-tight.css

Register the Styles

Put this in the child theme (for example in inc/block-styles.php, loaded from functions.php as shown in Child Theme File Includes). Each entry maps a style name to its blocks and the label editors see.

<?php
/**
 * Register custom block styles. Each style loads block-styles/<name>.css.
 */
add_action( 'init', function () {
	$styles = [
		// name                       => [ block(s), label ]
		'button-accent'             => [ 'core/button', 'Accent' ],
		'columns-reversed-mobile'   => [ 'core/columns', 'Reversed on Mobile' ],
		'columns-spaced'            => [ 'core/columns', 'Spaced' ],
		'gallery-portfolio'         => [ 'core/gallery', 'Portfolio' ],
		'group-feature-left'        => [ 'core/group', 'Left Feature' ],
		'heading-tight'             => [ 'core/heading', 'No Top Margin' ],
		'image-free-width'          => [ 'core/image', 'Free Width' ],
		'paragraph-subtitle'        => [ 'core/paragraph', 'Subtitle' ],
		// Third-party blocks work too.
		'contact-form-inline'       => [ 'contact-form-7/contact-form-selector', 'Inline Controls' ],
		// WordPress 6.6+ accepts several blocks for one style.
		'padded-bottom'             => [ [ 'core/group', 'core/columns' ], 'Padded Bottom' ],
	];

	foreach ( $styles as $name => [ $blocks, $label ] ) {
		$file = "/block-styles/{$name}.css";

		wp_register_style(
			"block-style-{$name}",
			get_theme_file_uri( $file ),
			[],
			filemtime( get_theme_file_path( $file ) )
		);

		register_block_style(
			$blocks,
			[
				'name'         => $name,
				'label'        => $label,
				'style_handle' => "block-style-{$name}",
			]
		);
	}
} );

Notes:

  • get_theme_file_uri() and get_theme_file_path() look in the child theme first and fall back to the parent, so the parent theme can provide a default file.

  • Using filemtime() as the version busts browser caches whenever you edit a stylesheet.

  • Passing an array of block names needs WordPress 6.6 or later. On older versions, register the style once per block.

  • register_block_style() has existed since WordPress 5.3, so you don't need the function_exists() guard that older snippets used.

Write the CSS

Target the is-style-<name> class that WordPress adds. For example, block-styles/columns-reversed-mobile.css:

@media (max-width: 781px) {
	.wp-block-columns.is-style-columns-reversed-mobile {
		flex-direction: column-reverse;
	}
}

Tip

For a style that needs only a line or two of CSS, skip the file and pass 'inline_style' => '.is-style-heading-tight { margin-top: 0; }' instead of style_handle.

Remove or Replace a Style

unregister_block_style() only removes styles that were registered in PHP with register_block_style(), such as one added by a parent theme or plugin. Run it after the original registration:

add_action( 'init', function () {
	unregister_block_style( 'core/group', 'parent-theme-card' );
}, 20 );

Core styles (for example the Button block's "Outline") are defined in each block's block.json and handled in the editor, so remove those from an editor script instead:

wp.domReady( () => {
	wp.blocks.unregisterBlockStyle( 'core/button', 'outline' );
} );

Enqueue that script on the enqueue_block_editor_assets action with wp-blocks and wp-dom-ready as dependencies.

Sources

This article is in the public domain (CC0 1.0), code samples included. Use it however helps you.