Skip to content

Instantly share code, notes, and snippets.

@mathetos
Created March 19, 2026 12:20
Show Gist options
  • Select an option

  • Save mathetos/ce960a5e891f68392e18293f25327739 to your computer and use it in GitHub Desktop.

Select an option

Save mathetos/ce960a5e891f68392e18293f25327739 to your computer and use it in GitHub Desktop.
Grid Gap Control for WordPress Core Grid Block
<?php
/**
* Grid Gap Width Control — adds per-block gap controls to core/group Grid layout.
*
* Drop into Fluent Snippets (or any code-snippets plugin).
* Writes to native style.spacing.blockGap so Core handles frontend output.
*/
add_action( 'enqueue_block_editor_assets', function () {
$handle = 'np-grid-gap-control';
wp_register_script(
$handle,
false,
array(
'wp-block-editor',
'wp-blocks',
'wp-components',
'wp-compose',
'wp-data',
'wp-element',
'wp-hooks',
'wp-i18n',
),
'1.0.0',
true
);
wp_enqueue_script( $handle );
ob_start();
?>
( function( wp ) {
if ( ! wp || ! wp.hooks || ! wp.compose || ! wp.element ) {
return;
}
var addFilter = wp.hooks.addFilter;
var createHOC = wp.compose.createHigherOrderComponent;
var Fragment = wp.element.Fragment;
var el = wp.element.createElement;
var InspectorControls = wp.blockEditor.InspectorControls;
var PanelBody = wp.components.PanelBody;
var ToggleControl = wp.components.ToggleControl;
var Flex = wp.components.Flex;
var FlexItem = wp.components.FlexItem;
var Button = wp.components.Button;
var UnitControl = wp.components.UnitControl || wp.components.__experimentalUnitControl;
var __ = wp.i18n.__;
if ( ! InspectorControls || ! PanelBody || ! UnitControl ) {
return;
}
/* Pull theme spacing units when available. */
function getUnits() {
var fallback = [
{ value: 'px', label: 'px' },
{ value: 'rem', label: 'rem' },
{ value: 'em', label: 'em' },
{ value: '%', label: '%' },
{ value: 'vw', label: 'vw' },
];
try {
var settings = wp.data.select( 'core/block-editor' ).getSettings();
var units = settings && settings.spacing && Array.isArray( settings.spacing.units )
? settings.spacing.units : null;
if ( ! units || ! units.length ) { return fallback; }
return units.map( function( u ) {
if ( typeof u === 'string' ) { return { value: u, label: u }; }
if ( u && typeof u.value === 'string' ) { return { value: u.value, label: u.label || u.value }; }
return null;
} ).filter( Boolean );
} catch ( e ) {
return fallback;
}
}
function isGrid( props ) {
var layout = props.attributes && props.attributes.layout ? props.attributes.layout : {};
return props.name === 'core/group' && layout.type === 'grid';
}
function gapState( attributes ) {
var gap = ( ( attributes.style || {} ).spacing || {} ).blockGap;
if ( typeof gap === 'string' ) {
return { linked: true, gap: gap, row: gap, col: gap };
}
if ( gap && typeof gap === 'object' ) {
return {
linked: false,
gap: '',
row: typeof gap.top === 'string' ? gap.top : '',
col: typeof gap.left === 'string' ? gap.left : '',
};
}
return { linked: true, gap: '', row: '', col: '' };
}
function setGap( props, next ) {
var style = Object.assign( {}, props.attributes.style || {} );
var spacing = Object.assign( {}, style.spacing || {} );
if ( ! next || ( typeof next === 'object' && ! next.top && ! next.left ) ) {
delete spacing.blockGap;
} else {
spacing.blockGap = next;
}
if ( Object.keys( spacing ).length ) {
style.spacing = spacing;
} else {
delete style.spacing;
}
props.setAttributes( { style: style } );
}
var withGridGap = createHOC( function( BlockEdit ) {
return function( props ) {
if ( ! isGrid( props ) ) {
return el( BlockEdit, props );
}
var s = gapState( props.attributes );
var units = getUnits();
return el(
Fragment, null,
el( BlockEdit, props ),
props.isSelected && el(
InspectorControls, { group: 'settings' },
el( PanelBody, { title: __( 'Grid Gap' ), initialOpen: true },
el( ToggleControl, {
label: __( 'Separate row & column gaps' ),
checked: ! s.linked,
onChange: function( on ) {
if ( on ) {
setGap( props, { top: s.row || s.gap || '', left: s.col || s.gap || '' } );
} else {
setGap( props, s.row || s.col || s.gap || '' );
}
},
} ),
s.linked && el( UnitControl, {
label: __( 'Gap width' ),
value: s.gap,
onChange: function( v ) { setGap( props, v || '' ); },
units: units,
min: 0,
step: 0.1,
} ),
! s.linked && el( Fragment, null,
el( UnitControl, {
label: __( 'Row gap' ),
value: s.row,
onChange: function( v ) { setGap( props, { top: v || '', left: s.col || '' } ); },
units: units,
min: 0,
step: 0.1,
} ),
el( UnitControl, {
label: __( 'Column gap' ),
value: s.col,
onChange: function( v ) { setGap( props, { top: s.row || '', left: v || '' } ); },
units: units,
min: 0,
step: 0.1,
} )
),
el( Flex, { justify: 'flex-end' },
el( FlexItem, null,
el( Button, {
variant: 'tertiary',
onClick: function() { setGap( props, '' ); },
}, __( 'Reset' ) )
)
)
)
)
);
};
}, 'npGridGapControl' );
addFilter( 'editor.BlockEdit', 'np/grid-gap-control', withGridGap );
} )( window.wp );
<?php
wp_add_inline_script( $handle, ob_get_clean() );
} );
/**
* Enable blockGap support so Core outputs gap CSS for grid/flex layouts.
* Only needed when the active theme doesn't declare it in theme.json.
*/
add_filter( 'wp_theme_json_data_theme', function ( $theme_json ) {
$data = $theme_json->get_data();
if ( empty( $data['settings']['spacing']['blockGap'] ) ) {
$data['settings']['spacing']['blockGap'] = true;
$theme_json->update_with( $data );
}
return $theme_json;
} );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment