IMPORTANT NOTE: After WordPress 7.0 Finaly we will have PHP Only Block Development Natively 🙂
I was wondering if it is possible to register custom blocks and develop it inside a single .php each
Looks like the answer is yes.
You just need to include these php files to functions.php and thats it single .php file custom blocks.
If you are new to wordpress I wouldnt recommend making blocks like this. You should just follow the official docs to create your custom blocks.
If you need a way for your edge case that you have to develop like this. Sure this is pretty cool way to develop wordpress blocks as well. Really fast too thanks to AI because everything is in single file and context is easier to handle. usualy AI tends to make mistakes on the multiple file block developing workflows.. I think we will need finetune a model for wordpress core at some point… just system prompts not cutting it.

Section Block
<?php
/**
* Section Block - Dynamic PHP Block with Responsive Controls
*/
// Register the block
add_action('init', function() {
// Define the attributes array separately to reuse it
$block_attributes = [
// Display attributes
'display' => [
'type' => 'object',
'default' => [
'desktop' => 'flex',
'tablet' => 'flex',
'mobile' => 'flex'
]
],
// Flex properties
'flexDirection' => [
'type' => 'object',
'default' => [
'desktop' => 'row',
'tablet' => 'row',
'mobile' => 'column'
]
],
'justifyContent' => [
'type' => 'object',
'default' => [
'desktop' => 'flex-start',
'tablet' => 'flex-start',
'mobile' => 'flex-start'
]
],
'alignItems' => [
'type' => 'object',
'default' => [
'desktop' => 'stretch',
'tablet' => 'stretch',
'mobile' => 'stretch'
]
],
'flexWrap' => [
'type' => 'object',
'default' => [
'desktop' => 'nowrap',
'tablet' => 'wrap',
'mobile' => 'wrap'
]
],
'gap' => [
'type' => 'object',
'default' => [
'desktop' => '20px',
'tablet' => '15px',
'mobile' => '10px'
]
],
// Grid properties
'gridTemplateColumns' => [
'type' => 'object',
'default' => [
'desktop' => 'repeat(3, 1fr)',
'tablet' => 'repeat(2, 1fr)',
'mobile' => '1fr'
]
],
'gridTemplateRows' => [
'type' => 'object',
'default' => [
'desktop' => 'auto',
'tablet' => 'auto',
'mobile' => 'auto'
]
],
'gridGap' => [
'type' => 'object',
'default' => [
'desktop' => '20px',
'tablet' => '15px',
'mobile' => '10px'
]
],
// Common style attributes
'padding' => [
'type' => 'object',
'default' => [
'desktop' => ['top' => '0', 'right' => '0', 'bottom' => '0', 'left' => '0'],
'tablet' => ['top' => '15px', 'right' => '15px', 'bottom' => '15px', 'left' => '15px'],
'mobile' => ['top' => '10px', 'right' => '10px', 'bottom' => '10px', 'left' => '10px']
]
],
'margin' => [
'type' => 'object',
'default' => [
'desktop' => ['top' => '0px', 'right' => 'auto', 'bottom' => '0px', 'left' => 'auto'],
'tablet' => ['top' => '0px', 'right' => 'auto', 'bottom' => '0px', 'left' => 'auto'],
'mobile' => ['top' => '0px', 'right' => 'auto', 'bottom' => '0px', 'left' => 'auto']
]
],
'backgroundColor' => [
'type' => 'object',
'default' => [
'desktop' => '',
'tablet' => '',
'mobile' => ''
]
],
'textColor' => [
'type' => 'object',
'default' => [
'desktop' => '',
'tablet' => '',
'mobile' => ''
]
],
'minHeight' => [
'type' => 'object',
'default' => [
'desktop' => '',
'tablet' => '',
'mobile' => ''
]
],
'maxWidth' => [
'type' => 'object',
'default' => [
'desktop' => '1200px',
'tablet' => '100%',
'mobile' => '100%'
]
],
// Border attributes
'border' => [
'type' => 'object',
'default' => [
'desktop' => ['width' => '', 'style' => '', 'color' => '', 'radius' => ''],
'tablet' => ['width' => '', 'style' => '', 'color' => '', 'radius' => ''],
'mobile' => ['width' => '', 'style' => '', 'color' => '', 'radius' => '']
]
],
// Additional attributes
'className' => [
'type' => 'string',
'default' => ''
],
'anchor' => [
'type' => 'string',
'default' => ''
]
];
register_block_type('custom/section', [
'render_callback' => 'render_section_block',
'attributes' => $block_attributes // Use the defined attributes array
]);
// Store attributes for later use in enqueue_block_editor_assets
// This is a common pattern to pass data from PHP to JS in WordPress blocks
global $custom_section_block_attributes;
$custom_section_block_attributes = $block_attributes;
});
// Render callback
function render_section_block($attributes, $content) {
$class_name = 'wp-block-custom-section';
if (!empty($attributes['className'])) {
$class_name .= ' ' . $attributes['className'];
}
$wrapper_attributes = [
'class' => $class_name
];
if (!empty($attributes['anchor'])) {
$wrapper_attributes['id'] = $attributes['anchor'];
}
// Generate unique ID for this block instance
$block_id = 'section-' . uniqid();
$wrapper_attributes['data-block-id'] = $block_id;
// Build wrapper attributes string
$wrapper_attrs_string = '';
foreach ($wrapper_attributes as $key => $value) {
$wrapper_attrs_string .= sprintf(' %s="%s"', $key, esc_attr($value));
}
// Generate responsive CSS
$css = section_generate_responsive_css($block_id, $attributes);
// Output the block
$output = sprintf(
'<style>%s</style><div%s>%s</div>',
$css,
$wrapper_attrs_string,
$content
);
return $output;
}
// Generate responsive CSS
function section_generate_responsive_css($block_id, $attributes) {
$css = '';
// Desktop styles (default)
$desktop_styles = section_generate_styles_for_breakpoint($attributes, 'desktop');
if (!empty($desktop_styles)) {
$css .= sprintf('[data-block-id="%s"] { %s }', $block_id, $desktop_styles);
}
// Tablet styles
$tablet_styles = section_generate_styles_for_breakpoint($attributes, 'tablet');
if (!empty($tablet_styles)) {
$css .= sprintf('@media (max-width: 781px) { [data-block-id="%s"] { %s } }', $block_id, $tablet_styles);
}
// Mobile styles
$mobile_styles = section_generate_styles_for_breakpoint($attributes, 'mobile');
if (!empty($mobile_styles)) {
$css .= sprintf('@media (max-width: 599px) { [data-block-id="%s"] { %s } }', $block_id, $mobile_styles);
}
return $css;
}
// Generate styles for specific breakpoint
function section_generate_styles_for_breakpoint($attributes, $breakpoint) {
$styles = [];
// Display
if (isset($attributes['display'][$breakpoint])) {
$styles[] = sprintf('display: %s', $attributes['display'][$breakpoint]);
// Flex properties
if ($attributes['display'][$breakpoint] === 'flex') {
if (isset($attributes['flexDirection'][$breakpoint])) {
$styles[] = sprintf('flex-direction: %s', $attributes['flexDirection'][$breakpoint]);
}
if (isset($attributes['justifyContent'][$breakpoint])) {
$styles[] = sprintf('justify-content: %s', $attributes['justifyContent'][$breakpoint]);
}
if (isset($attributes['alignItems'][$breakpoint])) {
$styles[] = sprintf('align-items: %s', $attributes['alignItems'][$breakpoint]);
}
if (isset($attributes['flexWrap'][$breakpoint])) {
$styles[] = sprintf('flex-wrap: %s', $attributes['flexWrap'][$breakpoint]);
}
if (isset($attributes['gap'][$breakpoint])) {
$styles[] = sprintf('gap: %s', $attributes['gap'][$breakpoint]);
}
}
// Grid properties
if ($attributes['display'][$breakpoint] === 'grid') {
if (isset($attributes['gridTemplateColumns'][$breakpoint])) {
$styles[] = sprintf('grid-template-columns: %s', $attributes['gridTemplateColumns'][$breakpoint]);
}
if (isset($attributes['gridTemplateRows'][$breakpoint])) {
$styles[] = sprintf('grid-template-rows: %s', $attributes['gridTemplateRows'][$breakpoint]);
}
if (isset($attributes['gridGap'][$breakpoint])) {
$styles[] = sprintf('grid-gap: %s', $attributes['gridGap'][$breakpoint]);
}
}
}
// Padding
if (isset($attributes['padding'][$breakpoint])) {
$padding = $attributes['padding'][$breakpoint];
if (is_array($padding)) {
$styles[] = sprintf(
'padding: %s %s %s %s',
$padding['top'] ?? '0',
$padding['right'] ?? '0',
$padding['bottom'] ?? '0',
$padding['left'] ?? '0'
);
}
}
// Margin
if (isset($attributes['margin'][$breakpoint])) {
$margin = $attributes['margin'][$breakpoint];
if (is_array($margin)) {
$styles[] = sprintf(
'margin: %s %s %s %s',
$margin['top'] ?? '0',
$margin['right'] ?? '0',
$margin['bottom'] ?? '0',
$margin['left'] ?? '0'
);
}
}
// Colors
if (isset($attributes['backgroundColor'][$breakpoint]) && !empty($attributes['backgroundColor'][$breakpoint])) {
$styles[] = sprintf('background-color: %s', $attributes['backgroundColor'][$breakpoint]);
}
if (isset($attributes['textColor'][$breakpoint]) && !empty($attributes['textColor'][$breakpoint])) {
$styles[] = sprintf('color: %s', $attributes['textColor'][$breakpoint]);
}
// Dimensions
if (isset($attributes['minHeight'][$breakpoint]) && !empty($attributes['minHeight'][$breakpoint])) {
$styles[] = sprintf('min-height: %s', $attributes['minHeight'][$breakpoint]);
}
if (isset($attributes['maxWidth'][$breakpoint]) && !empty($attributes['maxWidth'][$breakpoint])) {
$styles[] = sprintf('max-width: %s', $attributes['maxWidth'][$breakpoint]);
}
// Border
if (isset($attributes['border'][$breakpoint])) {
$border = $attributes['border'][$breakpoint];
if (!empty($border['width']) && !empty($border['style']) && !empty($border['color'])) {
$styles[] = sprintf('border: %s %s %s', $border['width'], $border['style'], $border['color']);
}
if (!empty($border['radius'])) {
$styles[] = sprintf('border-radius: %s', $border['radius']);
}
}
return implode('; ', $styles);
}
// Enqueue block editor assets
add_action('enqueue_block_editor_assets', function() {
global $custom_section_block_attributes; // Access the global variable
// Ensure attributes are available
$attributes_json = json_encode($custom_section_block_attributes ?: []); // Fallback to empty array if not set
// Define a unique handle for our block's editor script
$script_handle = 'custom-section-block-editor-script';
// Register the script first with all necessary dependencies
wp_register_script(
$script_handle,
'', // No source file, as it's an inline script
['wp-blocks', 'wp-element', 'wp-components', 'wp-block-editor', 'wp-data', 'wp-viewport'],
filemtime(__FILE__), // Use filemtime for versioning
false // Enqueue in the header, not footer, for editor scripts
);
$script = "
(function(wp) {
// console.log('Custom Section Block: Script loaded and starting registration.'); // Debugging log
// console.log('wp.blocks object:', wp.blocks); // Check wp.blocks object
// Ensure wp.blocks and registerBlockType are available
if (typeof wp.blocks === 'undefined' || typeof wp.blocks.registerBlockType === 'undefined') {
console.error('Custom Section Block: wp.blocks or registerBlockType is not defined. Block will not be registered.');
return; // Exit if dependencies are not fully loaded
}
const { registerBlockType } = wp.blocks;
const { InspectorControls, InnerBlocks, useBlockProps } = wp.blockEditor;
const { PanelBody, SelectControl, TextControl, __experimentalBoxControl: BoxControl, ColorPicker, RangeControl, ToggleControl } = wp.components;
const { Fragment, useState, useEffect } = wp.element;
const { select, useSelect, subscribe } = wp.data;
const { store: viewportStore } = wp.viewport;
// Get current viewport
function getCurrentViewport() {
const viewport = select(viewportStore);
if (viewport.isViewportMatch('< small')) return 'mobile';
if (viewport.isViewportMatch('< medium')) return 'tablet';
return 'desktop';
}
registerBlockType('custom/section', {
title: 'Section',
icon: 'layout',
category: 'design',
supports: {
html: false,
anchor: true,
className: true
},
attributes: " . $attributes_json . ", // Directly embed the JSON string
edit: function(props) {
console.log('Custom Section Block: Edit function loaded and running!'); // Debugging log
const { attributes, setAttributes } = props;
const [currentViewport, setCurrentViewport] = useState(getCurrentViewport());
// Subscribe to viewport changes
useEffect(() => {
const unsubscribe = subscribe(() => {
const newViewport = getCurrentViewport();
if (newViewport !== currentViewport) {
setCurrentViewport(newViewport);
}
});
return unsubscribe;
}, [currentViewport]);
// Helper to update responsive attribute
const updateResponsiveAttribute = (attributeName, value) => {
setAttributes({
[attributeName]: {
...attributes[attributeName],
[currentViewport]: value
}
});
};
// Get current value for responsive attribute
const getResponsiveValue = (attributeName) => {
return attributes[attributeName]?.[currentViewport] || '';
};
const blockProps = useBlockProps({
style: {
display: getResponsiveValue('display'),
flexDirection: getResponsiveValue('flexDirection'),
justifyContent: getResponsiveValue('justifyContent'),
alignItems: getResponsiveValue('alignItems'),
flexWrap: getResponsiveValue('flexWrap'),
gap: getResponsiveValue('gap'),
gridTemplateColumns: getResponsiveValue('gridTemplateColumns'),
gridTemplateRows: getResponsiveValue('gridTemplateRows'),
gridGap: getResponsiveValue('gridGap'),
padding: getResponsiveValue('padding') ? Object.values(getResponsiveValue('padding')).join(' ') : undefined,
margin: getResponsiveValue('margin') ? Object.values(getResponsiveValue('margin')).join(' ') : undefined,
backgroundColor: getResponsiveValue('backgroundColor'),
color: getResponsiveValue('textColor'),
minHeight: getResponsiveValue('minHeight'),
maxWidth: getResponsiveValue('maxWidth'),
}
});
return wp.element.createElement(
Fragment,
null,
wp.element.createElement(
InspectorControls,
null,
wp.element.createElement(
'div',
{ style: { padding: '16px', backgroundColor: '#f0f0f0', marginBottom: '16px' } },
wp.element.createElement('strong', null, 'Current Viewport: ' + currentViewport.toUpperCase())
),
wp.element.createElement(
PanelBody,
{ title: 'Layout', initialOpen: true },
wp.element.createElement(
SelectControl,
{
label: 'Display',
value: getResponsiveValue('display'),
options: [
{ label: 'Flex', value: 'flex' },
{ label: 'Grid', value: 'grid' },
{ label: 'Block', value: 'block' }
],
onChange: (value) => updateResponsiveAttribute('display', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
getResponsiveValue('display') === 'flex' && wp.element.createElement(
Fragment,
null,
wp.element.createElement(
SelectControl,
{
label: 'Flex Direction',
value: getResponsiveValue('flexDirection'),
options: [
{ label: 'Row', value: 'row' },
{ label: 'Column', value: 'column' },
{ label: 'Row Reverse', value: 'row-reverse' },
{ label: 'Column Reverse', value: 'column-reverse' }
],
onChange: (value) => updateResponsiveAttribute('flexDirection', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
SelectControl,
{
label: 'Justify Content',
value: getResponsiveValue('justifyContent'),
options: [
{ label: 'Start', value: 'flex-start' },
{ label: 'Center', value: 'center' },
{ label: 'End', value: 'flex-end' },
{ label: 'Space Between', value: 'space-between' },
{ label: 'Space Around', value: 'space-around' },
{ label: 'Space Evenly', value: 'space-evenly' }
],
onChange: (value) => updateResponsiveAttribute('justifyContent', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
SelectControl,
{
label: 'Align Items',
value: getResponsiveValue('alignItems'),
options: [
{ label: 'Stretch', value: 'stretch' },
{ label: 'Start', value: 'flex-start' },
{ label: 'Center', value: 'center' },
{ label: 'End', value: 'flex-end' },
{ label: 'Baseline', value: 'baseline' }
],
onChange: (value) => updateResponsiveAttribute('alignItems', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
SelectControl,
{
label: 'Flex Wrap',
value: getResponsiveValue('flexWrap'),
options: [
{ label: 'No Wrap', value: 'nowrap' },
{ label: 'Wrap', value: 'wrap' },
{ label: 'Wrap Reverse', value: 'wrap-reverse' }
],
onChange: (value) => updateResponsiveAttribute('flexWrap', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
TextControl,
{
label: 'Gap',
value: getResponsiveValue('gap'),
onChange: (value) => updateResponsiveAttribute('gap', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
)
),
getResponsiveValue('display') === 'grid' && wp.element.createElement(
Fragment,
null,
wp.element.createElement(
TextControl,
{
label: 'Grid Template Columns',
value: getResponsiveValue('gridTemplateColumns'),
onChange: (value) => updateResponsiveAttribute('gridTemplateColumns', value),
help: 'e.g., repeat(3, 1fr) or 200px 1fr 200px',
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
TextControl,
{
label: 'Grid Template Rows',
value: getResponsiveValue('gridTemplateRows'),
onChange: (value) => updateResponsiveAttribute('gridTemplateRows', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
TextControl,
{
label: 'Grid Gap',
value: getResponsiveValue('gridGap'),
onChange: (value) => updateResponsiveAttribute('gridGap', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
)
)
),
wp.element.createElement(
PanelBody,
{ title: 'Spacing', initialOpen: false },
wp.element.createElement(
'div',
{ style: { marginBottom: '20px' } },
wp.element.createElement('label', null, 'Padding'),
wp.element.createElement(
BoxControl,
{
values: getResponsiveValue('padding'),
onChange: (value) => updateResponsiveAttribute('padding', value)
}
)
),
wp.element.createElement(
'div',
null,
wp.element.createElement('label', null, 'Margin'),
wp.element.createElement(
BoxControl,
{
values: getResponsiveValue('margin'),
onChange: (value) => updateResponsiveAttribute('margin', value)
}
)
)
),
wp.element.createElement(
PanelBody,
{ title: 'Colors', initialOpen: false },
wp.element.createElement(
'div',
{ style: { marginBottom: '20px' } },
wp.element.createElement('label', null, 'Background Color'),
wp.element.createElement(
ColorPicker,
{
color: getResponsiveValue('backgroundColor'),
onChangeComplete: (value) => updateResponsiveAttribute('backgroundColor', value.hex)
}
)
),
wp.element.createElement(
'div',
null,
wp.element.createElement('label', null, 'Text Color'),
wp.element.createElement(
ColorPicker,
{
color: getResponsiveValue('textColor'),
onChangeComplete: (value) => updateResponsiveAttribute('textColor', value.hex)
}
)
)
),
wp.element.createElement(
PanelBody,
{ title: 'Dimensions', initialOpen: false },
wp.element.createElement(
TextControl,
{
label: 'Min Height',
value: getResponsiveValue('minHeight'),
onChange: (value) => updateResponsiveAttribute('minHeight', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
TextControl,
{
label: 'Max Width',
value: getResponsiveValue('maxWidth'),
onChange: (value) => updateResponsiveAttribute('maxWidth', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
)
)
),
wp.element.createElement(
'div',
blockProps,
wp.element.createElement(InnerBlocks)
)
);
},
save: function() {
return wp.element.createElement(InnerBlocks.Content);
}
});
console.log('Custom Section Block: registerBlockType call completed.'); // Debugging log
})(window.wp);
";
// Add the inline script to our custom script handle
wp_add_inline_script($script_handle, $script);
// Enqueue the script
wp_enqueue_script($script_handle);
});
Container Blocks
<?php
/**
* container Block - Dynamic PHP Block with Responsive Controls
*/
// Register the block
add_action('init', function() {
// Define the attributes array separately to reuse it
$block_attributes = [
// Display attributes
'display' => [
'type' => 'object',
'default' => [
'desktop' => 'flex',
'tablet' => 'flex',
'mobile' => 'flex'
]
],
// Flex properties
'flexDirection' => [
'type' => 'object',
'default' => [
'desktop' => 'row',
'tablet' => 'row',
'mobile' => 'column'
]
],
'justifyContent' => [
'type' => 'object',
'default' => [
'desktop' => 'flex-start',
'tablet' => 'flex-start',
'mobile' => 'flex-start'
]
],
'alignItems' => [
'type' => 'object',
'default' => [
'desktop' => 'stretch',
'tablet' => 'stretch',
'mobile' => 'stretch'
]
],
'flexWrap' => [
'type' => 'object',
'default' => [
'desktop' => 'nowrap',
'tablet' => 'wrap',
'mobile' => 'wrap'
]
],
'gap' => [
'type' => 'object',
'default' => [
'desktop' => '20px',
'tablet' => '15px',
'mobile' => '10px'
]
],
// Grid properties
'gridTemplateColumns' => [
'type' => 'object',
'default' => [
'desktop' => 'repeat(3, 1fr)',
'tablet' => 'repeat(2, 1fr)',
'mobile' => '1fr'
]
],
'gridTemplateRows' => [
'type' => 'object',
'default' => [
'desktop' => 'auto',
'tablet' => 'auto',
'mobile' => 'auto'
]
],
'gridGap' => [
'type' => 'object',
'default' => [
'desktop' => '20px',
'tablet' => '15px',
'mobile' => '10px'
]
],
// Common style attributes
'padding' => [
'type' => 'object',
'default' => [
'desktop' => ['top' => '0', 'right' => '0', 'bottom' => '0', 'left' => '0'],
'tablet' => ['top' => '15px', 'right' => '15px', 'bottom' => '15px', 'left' => '15px'],
'mobile' => ['top' => '10px', 'right' => '10px', 'bottom' => '10px', 'left' => '10px']
]
],
'margin' => [
'type' => 'object',
'default' => [
'desktop' => ['top' => '0px', 'right' => 'auto', 'bottom' => '0px', 'left' => 'auto'],
'tablet' => ['top' => '0px', 'right' => 'auto', 'bottom' => '0px', 'left' => 'auto'],
'mobile' => ['top' => '0px', 'right' => 'auto', 'bottom' => '0px', 'left' => 'auto']
]
],
'backgroundColor' => [
'type' => 'object',
'default' => [
'desktop' => '',
'tablet' => '',
'mobile' => ''
]
],
'textColor' => [
'type' => 'object',
'default' => [
'desktop' => '',
'tablet' => '',
'mobile' => ''
]
],
'minHeight' => [
'type' => 'object',
'default' => [
'desktop' => '',
'tablet' => '',
'mobile' => ''
]
],
'maxWidth' => [
'type' => 'object',
'default' => [
'desktop' => '1200px',
'tablet' => '100%',
'mobile' => '100%'
]
],
// Border attributes
'border' => [
'type' => 'object',
'default' => [
'desktop' => ['width' => '', 'style' => '', 'color' => '', 'radius' => ''],
'tablet' => ['width' => '', 'style' => '', 'color' => '', 'radius' => ''],
'mobile' => ['width' => '', 'style' => '', 'color' => '', 'radius' => '']
]
],
// Additional attributes
'className' => [
'type' => 'string',
'default' => ''
],
'anchor' => [
'type' => 'string',
'default' => ''
]
];
register_block_type('custom/container', [
'render_callback' => 'render_container_block',
'attributes' => $block_attributes // Use the defined attributes array
]);
// Store attributes for later use in enqueue_block_editor_assets
// This is a common pattern to pass data from PHP to JS in WordPress blocks
global $custom_container_block_attributes;
$custom_container_block_attributes = $block_attributes;
});
// Render callback
function render_container_block($attributes, $content) {
$class_name = 'wp-block-custom-container';
if (!empty($attributes['className'])) {
$class_name .= ' ' . $attributes['className'];
}
$wrapper_attributes = [
'class' => $class_name
];
if (!empty($attributes['anchor'])) {
$wrapper_attributes['id'] = $attributes['anchor'];
}
// Generate unique ID for this block instance
$block_id = 'container-' . uniqid();
$wrapper_attributes['data-block-id'] = $block_id;
// Build wrapper attributes string
$wrapper_attrs_string = '';
foreach ($wrapper_attributes as $key => $value) {
$wrapper_attrs_string .= sprintf(' %s="%s"', $key, esc_attr($value));
}
// Generate responsive CSS
$css = container_generate_responsive_css($block_id, $attributes);
// Output the block
$output = sprintf(
'<style>%s</style><div%s>%s</div>',
$css,
$wrapper_attrs_string,
$content
);
return $output;
}
// Generate responsive CSS
function container_generate_responsive_css($block_id, $attributes) {
$css = '';
// Desktop styles (default)
$desktop_styles = container_generate_styles_for_breakpoint($attributes, 'desktop');
if (!empty($desktop_styles)) {
$css .= sprintf('[data-block-id="%s"] { %s }', $block_id, $desktop_styles);
}
// Tablet styles
$tablet_styles = container_generate_styles_for_breakpoint($attributes, 'tablet');
if (!empty($tablet_styles)) {
$css .= sprintf('@media (max-width: 781px) { [data-block-id="%s"] { %s } }', $block_id, $tablet_styles);
}
// Mobile styles
$mobile_styles = container_generate_styles_for_breakpoint($attributes, 'mobile');
if (!empty($mobile_styles)) {
$css .= sprintf('@media (max-width: 599px) { [data-block-id="%s"] { %s } }', $block_id, $mobile_styles);
}
return $css;
}
// Generate styles for specific breakpoint
function container_generate_styles_for_breakpoint($attributes, $breakpoint) {
$styles = [];
// Display
if (isset($attributes['display'][$breakpoint])) {
$styles[] = sprintf('display: %s', $attributes['display'][$breakpoint]);
// Flex properties
if ($attributes['display'][$breakpoint] === 'flex') {
if (isset($attributes['flexDirection'][$breakpoint])) {
$styles[] = sprintf('flex-direction: %s', $attributes['flexDirection'][$breakpoint]);
}
if (isset($attributes['justifyContent'][$breakpoint])) {
$styles[] = sprintf('justify-content: %s', $attributes['justifyContent'][$breakpoint]);
}
if (isset($attributes['alignItems'][$breakpoint])) {
$styles[] = sprintf('align-items: %s', $attributes['alignItems'][$breakpoint]);
}
if (isset($attributes['flexWrap'][$breakpoint])) {
$styles[] = sprintf('flex-wrap: %s', $attributes['flexWrap'][$breakpoint]);
}
if (isset($attributes['gap'][$breakpoint])) {
$styles[] = sprintf('gap: %s', $attributes['gap'][$breakpoint]);
}
}
// Grid properties
if ($attributes['display'][$breakpoint] === 'grid') {
if (isset($attributes['gridTemplateColumns'][$breakpoint])) {
$styles[] = sprintf('grid-template-columns: %s', $attributes['gridTemplateColumns'][$breakpoint]);
}
if (isset($attributes['gridTemplateRows'][$breakpoint])) {
$styles[] = sprintf('grid-template-rows: %s', $attributes['gridTemplateRows'][$breakpoint]);
}
if (isset($attributes['gridGap'][$breakpoint])) {
$styles[] = sprintf('grid-gap: %s', $attributes['gridGap'][$breakpoint]);
}
}
}
// Padding
if (isset($attributes['padding'][$breakpoint])) {
$padding = $attributes['padding'][$breakpoint];
if (is_array($padding)) {
$styles[] = sprintf(
'padding: %s %s %s %s',
$padding['top'] ?? '0',
$padding['right'] ?? '0',
$padding['bottom'] ?? '0',
$padding['left'] ?? '0'
);
}
}
// Margin
if (isset($attributes['margin'][$breakpoint])) {
$margin = $attributes['margin'][$breakpoint];
if (is_array($margin)) {
$styles[] = sprintf(
'margin: %s %s %s %s',
$margin['top'] ?? '0',
$margin['right'] ?? '0',
$margin['bottom'] ?? '0',
$margin['left'] ?? '0'
);
}
}
// Colors
if (isset($attributes['backgroundColor'][$breakpoint]) && !empty($attributes['backgroundColor'][$breakpoint])) {
$styles[] = sprintf('background-color: %s', $attributes['backgroundColor'][$breakpoint]);
}
if (isset($attributes['textColor'][$breakpoint]) && !empty($attributes['textColor'][$breakpoint])) {
$styles[] = sprintf('color: %s', $attributes['textColor'][$breakpoint]);
}
// Dimensions
if (isset($attributes['minHeight'][$breakpoint]) && !empty($attributes['minHeight'][$breakpoint])) {
$styles[] = sprintf('min-height: %s', $attributes['minHeight'][$breakpoint]);
}
if (isset($attributes['maxWidth'][$breakpoint]) && !empty($attributes['maxWidth'][$breakpoint])) {
$styles[] = sprintf('max-width: %s', $attributes['maxWidth'][$breakpoint]);
}
// Border
if (isset($attributes['border'][$breakpoint])) {
$border = $attributes['border'][$breakpoint];
if (!empty($border['width']) && !empty($border['style']) && !empty($border['color'])) {
$styles[] = sprintf('border: %s %s %s', $border['width'], $border['style'], $border['color']);
}
if (!empty($border['radius'])) {
$styles[] = sprintf('border-radius: %s', $border['radius']);
}
}
return implode('; ', $styles);
}
// Enqueue block editor assets
add_action('enqueue_block_editor_assets', function() {
global $custom_container_block_attributes; // Access the global variable
// Ensure attributes are available
$attributes_json = json_encode($custom_container_block_attributes ?: []); // Fallback to empty array if not set
// Define a unique handle for our block's editor script
$script_handle = 'custom-container-block-editor-script';
// Register the script first with all necessary dependencies
wp_register_script(
$script_handle,
'', // No source file, as it's an inline script
['wp-blocks', 'wp-element', 'wp-components', 'wp-block-editor', 'wp-data', 'wp-viewport'],
filemtime(__FILE__), // Use filemtime for versioning
false // Enqueue in the header, not footer, for editor scripts
);
$script = "
(function(wp) {
// console.log('Custom container Block: Script loaded and starting registration.'); // Debugging log
// console.log('wp.blocks object:', wp.blocks); // Check wp.blocks object
// Ensure wp.blocks and registerBlockType are available
if (typeof wp.blocks === 'undefined' || typeof wp.blocks.registerBlockType === 'undefined') {
console.error('Custom container Block: wp.blocks or registerBlockType is not defined. Block will not be registered.');
return; // Exit if dependencies are not fully loaded
}
const { registerBlockType } = wp.blocks;
const { InspectorControls, InnerBlocks, useBlockProps } = wp.blockEditor;
const { PanelBody, SelectControl, TextControl, __experimentalBoxControl: BoxControl, ColorPicker, RangeControl, ToggleControl } = wp.components;
const { Fragment, useState, useEffect } = wp.element;
const { select, useSelect, subscribe } = wp.data;
const { store: viewportStore } = wp.viewport;
// Get current viewport
function getCurrentViewport() {
const viewport = select(viewportStore);
if (viewport.isViewportMatch('< small')) return 'mobile';
if (viewport.isViewportMatch('< medium')) return 'tablet';
return 'desktop';
}
registerBlockType('custom/container', {
title: 'Container',
icon: 'layout',
category: 'design',
supports: {
html: false,
anchor: true,
className: true
},
attributes: " . $attributes_json . ", // Directly embed the JSON string
edit: function(props) {
console.log('Custom container Block: Edit function loaded and running!'); // Debugging log
const { attributes, setAttributes } = props;
const [currentViewport, setCurrentViewport] = useState(getCurrentViewport());
// Subscribe to viewport changes
useEffect(() => {
const unsubscribe = subscribe(() => {
const newViewport = getCurrentViewport();
if (newViewport !== currentViewport) {
setCurrentViewport(newViewport);
}
});
return unsubscribe;
}, [currentViewport]);
// Helper to update responsive attribute
const updateResponsiveAttribute = (attributeName, value) => {
setAttributes({
[attributeName]: {
...attributes[attributeName],
[currentViewport]: value
}
});
};
// Get current value for responsive attribute
const getResponsiveValue = (attributeName) => {
return attributes[attributeName]?.[currentViewport] || '';
};
const blockProps = useBlockProps({
style: {
display: getResponsiveValue('display'),
flexDirection: getResponsiveValue('flexDirection'),
justifyContent: getResponsiveValue('justifyContent'),
alignItems: getResponsiveValue('alignItems'),
flexWrap: getResponsiveValue('flexWrap'),
gap: getResponsiveValue('gap'),
gridTemplateColumns: getResponsiveValue('gridTemplateColumns'),
gridTemplateRows: getResponsiveValue('gridTemplateRows'),
gridGap: getResponsiveValue('gridGap'),
padding: getResponsiveValue('padding') ? Object.values(getResponsiveValue('padding')).join(' ') : undefined,
margin: getResponsiveValue('margin') ? Object.values(getResponsiveValue('margin')).join(' ') : undefined,
backgroundColor: getResponsiveValue('backgroundColor'),
color: getResponsiveValue('textColor'),
minHeight: getResponsiveValue('minHeight'),
maxWidth: getResponsiveValue('maxWidth'),
}
});
return wp.element.createElement(
Fragment,
null,
wp.element.createElement(
InspectorControls,
null,
wp.element.createElement(
'div',
{ style: { padding: '16px', backgroundColor: '#f0f0f0', marginBottom: '16px' } },
wp.element.createElement('strong', null, 'Current Viewport: ' + currentViewport.toUpperCase())
),
wp.element.createElement(
PanelBody,
{ title: 'Layout', initialOpen: true },
wp.element.createElement(
SelectControl,
{
label: 'Display',
value: getResponsiveValue('display'),
options: [
{ label: 'Flex', value: 'flex' },
{ label: 'Grid', value: 'grid' },
{ label: 'Block', value: 'block' }
],
onChange: (value) => updateResponsiveAttribute('display', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
getResponsiveValue('display') === 'flex' && wp.element.createElement(
Fragment,
null,
wp.element.createElement(
SelectControl,
{
label: 'Flex Direction',
value: getResponsiveValue('flexDirection'),
options: [
{ label: 'Row', value: 'row' },
{ label: 'Column', value: 'column' },
{ label: 'Row Reverse', value: 'row-reverse' },
{ label: 'Column Reverse', value: 'column-reverse' }
],
onChange: (value) => updateResponsiveAttribute('flexDirection', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
SelectControl,
{
label: 'Justify Content',
value: getResponsiveValue('justifyContent'),
options: [
{ label: 'Start', value: 'flex-start' },
{ label: 'Center', value: 'center' },
{ label: 'End', value: 'flex-end' },
{ label: 'Space Between', value: 'space-between' },
{ label: 'Space Around', value: 'space-around' },
{ label: 'Space Evenly', value: 'space-evenly' }
],
onChange: (value) => updateResponsiveAttribute('justifyContent', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
SelectControl,
{
label: 'Align Items',
value: getResponsiveValue('alignItems'),
options: [
{ label: 'Stretch', value: 'stretch' },
{ label: 'Start', value: 'flex-start' },
{ label: 'Center', value: 'center' },
{ label: 'End', value: 'flex-end' },
{ label: 'Baseline', value: 'baseline' }
],
onChange: (value) => updateResponsiveAttribute('alignItems', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
SelectControl,
{
label: 'Flex Wrap',
value: getResponsiveValue('flexWrap'),
options: [
{ label: 'No Wrap', value: 'nowrap' },
{ label: 'Wrap', value: 'wrap' },
{ label: 'Wrap Reverse', value: 'wrap-reverse' }
],
onChange: (value) => updateResponsiveAttribute('flexWrap', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
TextControl,
{
label: 'Gap',
value: getResponsiveValue('gap'),
onChange: (value) => updateResponsiveAttribute('gap', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
)
),
getResponsiveValue('display') === 'grid' && wp.element.createElement(
Fragment,
null,
wp.element.createElement(
TextControl,
{
label: 'Grid Template Columns',
value: getResponsiveValue('gridTemplateColumns'),
onChange: (value) => updateResponsiveAttribute('gridTemplateColumns', value),
help: 'e.g., repeat(3, 1fr) or 200px 1fr 200px',
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
TextControl,
{
label: 'Grid Template Rows',
value: getResponsiveValue('gridTemplateRows'),
onChange: (value) => updateResponsiveAttribute('gridTemplateRows', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
TextControl,
{
label: 'Grid Gap',
value: getResponsiveValue('gridGap'),
onChange: (value) => updateResponsiveAttribute('gridGap', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
)
)
),
wp.element.createElement(
PanelBody,
{ title: 'Spacing', initialOpen: false },
wp.element.createElement(
'div',
{ style: { marginBottom: '20px' } },
wp.element.createElement('label', null, 'Padding'),
wp.element.createElement(
BoxControl,
{
values: getResponsiveValue('padding'),
onChange: (value) => updateResponsiveAttribute('padding', value)
}
)
),
wp.element.createElement(
'div',
null,
wp.element.createElement('label', null, 'Margin'),
wp.element.createElement(
BoxControl,
{
values: getResponsiveValue('margin'),
onChange: (value) => updateResponsiveAttribute('margin', value)
}
)
)
),
wp.element.createElement(
PanelBody,
{ title: 'Colors', initialOpen: false },
wp.element.createElement(
'div',
{ style: { marginBottom: '20px' } },
wp.element.createElement('label', null, 'Background Color'),
wp.element.createElement(
ColorPicker,
{
color: getResponsiveValue('backgroundColor'),
onChangeComplete: (value) => updateResponsiveAttribute('backgroundColor', value.hex)
}
)
),
wp.element.createElement(
'div',
null,
wp.element.createElement('label', null, 'Text Color'),
wp.element.createElement(
ColorPicker,
{
color: getResponsiveValue('textColor'),
onChangeComplete: (value) => updateResponsiveAttribute('textColor', value.hex)
}
)
)
),
wp.element.createElement(
PanelBody,
{ title: 'Dimensions', initialOpen: false },
wp.element.createElement(
TextControl,
{
label: 'Min Height',
value: getResponsiveValue('minHeight'),
onChange: (value) => updateResponsiveAttribute('minHeight', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
),
wp.element.createElement(
TextControl,
{
label: 'Max Width',
value: getResponsiveValue('maxWidth'),
onChange: (value) => updateResponsiveAttribute('maxWidth', value),
// Opt-in to new default size and no bottom margin
__next40pxDefaultSize: true,
__nextHasNoMarginBottom: true
}
)
)
),
wp.element.createElement(
'div',
blockProps,
wp.element.createElement(InnerBlocks)
)
);
},
save: function() {
return wp.element.createElement(InnerBlocks.Content);
}
});
console.log('Custom container Block: registerBlockType call completed.'); // Debugging log
})(window.wp);
";
// Add the inline script to our custom script handle
wp_add_inline_script($script_handle, $script);
// Enqueue the script
wp_enqueue_script($script_handle);
});
Heading Block
<?php
/**
* Custom Heading Block
* Registers a simple heading block with typography controls.
* This file contains all the necessary PHP and JavaScript for the block.
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Register the block type.
*/
function custom_heading_block_init() {
// These attributes are used by the render_callback on the server.
$block_attributes = array(
'content' => array(
'type' => 'string',
'source' => 'html',
'selector'=> 'h1,h2,h3,h4,h5,h6',
),
'level' => array(
'type' => 'number',
'default' => 2,
),
'textAlign' => array(
'type' => 'string',
'default' => 'left',
),
'fontSize' => array(
'type' => 'string',
),
'lineHeight' => array(
'type' => 'string',
),
'textColor' => array(
'type' => 'string',
),
'backgroundColor' => array(
'type' => 'string',
),
'className' => array(
'type' => 'string',
),
);
// Register the block.
register_block_type( 'custom/heading', array(
'editor_script' => 'custom-heading-block-editor-script',
'render_callback' => 'custom_heading_render_callback',
'attributes' => $block_attributes,
) );
}
add_action( 'init', 'custom_heading_block_init' );
/**
* Enqueue block editor assets.
*/
function custom_heading_block_editor_assets() {
add_action( 'admin_head', 'custom_heading_block_editor_script' );
wp_enqueue_script(
'custom-heading-block-editor-script',
'', // No source file, as it's an inline script
array( 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n' ),
'1.0.4' // Updated version
);
}
add_action( 'enqueue_block_editor_assets', 'custom_heading_block_editor_assets' );
/**
* Prints the JavaScript for the block editor.
*/
function custom_heading_block_editor_script() {
?>
<script>
(function(wp) {
const { registerBlockType } = wp.blocks;
const { RichText, InspectorControls, PanelColorSettings, AlignmentToolbar, BlockControls } = wp.blockEditor;
const { PanelBody, SelectControl, TextControl } = wp.components;
const { Fragment } = wp.element;
registerBlockType('custom/heading', {
title: 'Custom Heading',
icon: 'heading',
category: 'common',
attributes: {
content: {
type: 'string',
source: 'html',
selector: 'h1,h2,h3,h4,h5,h6',
},
level: {
type: 'number',
default: 2,
},
textAlign: {
type: 'string',
default: 'left',
},
fontSize: {
type: 'string',
},
lineHeight: {
type: 'string',
},
textColor: {
type: 'string',
},
backgroundColor: {
type: 'string',
},
className: {
type: 'string',
},
},
edit: function({ attributes, setAttributes }) {
const { content, level, textAlign, fontSize, lineHeight, textColor, backgroundColor } = attributes;
const blockStyle = {
textAlign,
fontSize,
lineHeight,
color: textColor,
backgroundColor,
};
return wp.element.createElement(
Fragment,
null,
wp.element.createElement(
InspectorControls,
null,
wp.element.createElement(
PanelBody, { title: 'Heading Settings', initialOpen: true },
wp.element.createElement(SelectControl, {
label: 'Heading Level',
value: level,
options: [
{ label: 'H1', value: 1 }, { label: 'H2', value: 2 }, { label: 'H3', value: 3 },
{ label: 'H4', value: 4 }, { label: 'H5', value: 5 }, { label: 'H6', value: 6 },
],
onChange: (newLevel) => setAttributes({ level: parseInt(newLevel) }),
})
),
wp.element.createElement(
PanelBody, { title: 'Typography', initialOpen: false },
wp.element.createElement(TextControl, {
label: 'Font Size',
value: fontSize,
onChange: (newSize) => setAttributes({ fontSize: newSize }),
help: 'e.g., 24px, 2em, 1.5rem',
}),
wp.element.createElement(TextControl, {
label: 'Line Height',
value: lineHeight,
onChange: (newLineHeight) => setAttributes({ lineHeight: newLineHeight }),
help: 'e.g., 1.2, 1.5',
})
),
wp.element.createElement(PanelColorSettings, {
title: 'Color Settings',
initialOpen: false,
colorSettings: [
{ label: 'Text Color', onChange: (newColor) => setAttributes({ textColor: newColor }), value: textColor },
{ label: 'Background Color', onChange: (newColor) => setAttributes({ backgroundColor: newColor }), value: backgroundColor },
],
})
),
wp.element.createElement(
BlockControls,
null,
wp.element.createElement(AlignmentToolbar, {
value: textAlign,
onChange: (newAlign) => setAttributes({ textAlign: newAlign }),
})
),
wp.element.createElement(RichText, {
tagName: `h${level}`,
value: content,
onChange: (newContent) => setAttributes({ content: newContent }),
placeholder: 'Your Heading Here',
style: blockStyle,
allowedFormats: [ 'core/bold', 'core/italic', 'core/link' ]
})
);
},
save: function() {
return null;
},
});
})(window.wp);
</script>
<?php
}
/**
* Render the block dynamically on the server.
*/
function custom_heading_render_callback( $attributes, $content ) {
$level = ( isset( $attributes['level'] ) && $attributes['level'] >= 1 && $attributes['level'] <= 6 ) ? $attributes['level'] : 2;
$tag = 'h' . $level;
$heading_content = isset( $attributes['content'] ) ? $attributes['content'] : '';
$style = '';
$style_props = [];
if ( ! empty( $attributes['textAlign'] ) ) { $style_props[] = 'text-align:' . esc_attr( $attributes['textAlign'] ); }
if ( ! empty( $attributes['fontSize'] ) ) { $style_props[] = 'font-size:' . esc_attr( $attributes['fontSize'] ); }
if ( ! empty( $attributes['lineHeight'] ) ) { $style_props[] = 'line-height:' . esc_attr( $attributes['lineHeight'] ); }
if ( ! empty( $attributes['textColor'] ) ) { $style_props[] = 'color:' . esc_attr( $attributes['textColor'] ); }
if ( ! empty( $attributes['backgroundColor'] ) ) { $style_props[] = 'background-color:' . esc_attr( $attributes['backgroundColor'] ); }
if (!empty($style_props)) {
$style = implode(';', $style_props);
}
$class_name = 'wp-block-custom-heading';
if ( ! empty( $attributes['className'] ) ) {
$class_name .= ' ' . esc_attr( $attributes['className'] );
}
return sprintf(
'<%s class="%s" style="%s">%s</%s>',
$tag,
esc_attr( $class_name ),
esc_attr( $style ),
$heading_content,
$tag
);
}
functions.php
// Custom Blocks require_once SNN_PATH . '/blocks/section.php'; require_once SNN_PATH . '/blocks/container.php'; require_once SNN_PATH . '/blocks/heading.php';