A minimal companion plugin that registers its own block with FluidBlocks' built-in controls and adds one custom per-breakpoint key, heroOpacity, with its own control and frontend CSS. Four files, no build step; it works as-is on WordPress 6.9+ with FluidBlocks active.
fluidblocks-hero-example.php
<?php
/**
* Plugin Name: FluidBlocks Hero Example
* Description: A third-party block with FluidBlocks responsive controls plus one custom per-breakpoint key.
* Requires Plugins: fluidblocks
*/
defined( 'ABSPATH' ) || exit;
// 1. Register the block with FluidBlocks. This one entry gives example/hero the
// responsive attribute, the toolbar button with these controls, the frontend
// CSS and a row in Settings → FluidBlocks → Blocks & Controls.
add_filter( 'fluidblocks_settings_defaults', function ( $defaults ) {
$defaults['blocks']['example/hero'] = [
'enabled' => true,
'visibility' => true,
'padding' => true,
'margin' => true,
];
return $defaults;
} );
// 2. Frontend CSS for the custom `heroOpacity` key. FluidBlocks hands over the
// raw attribute; cascade it the same mobile-first way the built-in keys are
// cascaded and wrap each breakpoint in its media query.
add_filter( 'fluidblocks_render_block_css', function ( $css, $class_name, $block_name, $responsive, $breakpoints ) {
if ( 'example/hero' !== $block_name ) {
return $css;
}
foreach ( \FluidBlocks\Render::cascade_responsive( $responsive ) as $breakpoint => $data ) {
if ( ! isset( $data['heroOpacity'] ) || ! is_numeric( $data['heroOpacity'] ) ) {
continue;
}
$css .= \FluidBlocks\Render::media_query( $breakpoint, $breakpoints )
. '{.' . $class_name . '{opacity:' . (float) $data['heroOpacity'] . '}}';
}
return $css;
}, 10, 5 );
// 3. The block itself — nothing FluidBlocks-specific here.
add_action( 'init', function () {
register_block_type( __DIR__ . '/blocks/hero' );
} );
blocks/hero/block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "example/hero",
"title": "Example Hero",
"category": "design",
"icon": "star-filled",
"attributes": {
"text": { "type": "string", "default": "Hello from the hero block" }
},
"supports": { "html": false },
"editorScript": "file:./editor.js"
}
blocks/hero/editor.js
/* global wp */
( function () {
var el = wp.element.createElement;
var useBlockProps = wp.blockEditor.useBlockProps;
var RangeControl = wp.components.RangeControl;
// No fluidblocks.blockSupports filter needed: the PHP entry already
// registered the block and its controls with the editor.
// 1. A custom control in the responsive popover, rendered once per breakpoint.
// It stores `heroOpacity` next to the built-in keys of that breakpoint.
wp.hooks.addFilter( 'fluidblocks.inspectorControls', 'example/hero-opacity', function ( controls, blockName, breakpoint, attributes, setAttributes ) {
if ( blockName !== 'example/hero' ) {
return controls;
}
var responsive = attributes.fluidblocksResponsive || {};
var bpData = responsive[ breakpoint ] || {};
controls.push(
el( RangeControl, {
key: 'hero-opacity',
label: 'Hero opacity',
min: 0,
max: 1,
step: 0.1,
value: bpData.heroOpacity === undefined ? 1 : Number( bpData.heroOpacity ),
onChange: function ( next ) {
var nextBp = Object.assign( {}, bpData );
if ( next === undefined || next === 1 ) {
delete nextBp.heroOpacity;
} else {
nextBp.heroOpacity = String( next );
}
var nextResponsive = Object.assign( {}, responsive );
nextResponsive[ breakpoint ] = nextBp;
setAttributes( { fluidblocksResponsive: nextResponsive } );
},
} )
);
return controls;
} );
// 2. Let "Copy / paste responsive settings" carry the custom key along.
wp.hooks.addFilter( 'fluidblocks.pasteableAttributeKeys', 'example/hero-opacity', function ( keys, targetBlockName ) {
if ( targetBlockName === 'example/hero' ) {
keys.push( 'heroOpacity' );
}
return keys;
} );
// 3. The block itself.
wp.blocks.registerBlockType( 'example/hero', {
edit: function ( props ) {
return el( 'div', useBlockProps( { className: 'example-hero' } ), props.attributes.text );
},
save: function ( props ) {
return el( 'div', useBlockProps.save( { className: 'example-hero' } ), props.attributes.text );
},
} );
} )();
blocks/hero/editor.asset.php
<?php return [ 'dependencies' => [ 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-hooks' ], 'version' => '1.0.0' ];
What the example relies on
- One registration, in PHP. The
fluidblocks_settings_defaults entry is all FluidBlocks needs — the editor builds its block → features registry from the same settings object. - Typography needs nothing. Had
block.json declared supports.typography, the block would get the responsive typography section automatically, like core's Quote block does. - Custom keys are yours end to end. FluidBlocks stores whatever the inspector control writes under the breakpoint, cascades it mobile-first and hands it to
fluidblocks_render_block_css; the example reuses the public cascade and media-query helpers so its CSS lands in exactly the same media queries. - Editor preview of custom keys is not available yet. The render filter has no JS counterpart, so
heroOpacity renders on the frontend but not in the editor canvas; the built-in keys preview as usual. - Settings. The block appears in Settings → FluidBlocks → Blocks & Controls with its three toggles; the master toggle and per-feature toggles apply to it like to a core block.