-
Notifications
You must be signed in to change notification settings - Fork 5
Patterns Width Layout Patterns
Purpose: Prevent conflicts with nested items when handling width, contentSize, and layout across blocks.
Last Updated: 2025-11-11 Status: 🟢 Phase 1 Complete - 4 critical issues resolved, 2 deferred
- Resolved Issues (2025-11-11)
- Core Architecture
- Current Patterns
- Known Conflicts
- Best Practices
- Migration Guide
Status: RESOLVED Fix: Added class application in section/save.js:31-37, row/save.js, grid/save.js Commit: 2765159 (2025-11-11)
Status: RESOLVED
Fix: Added blockGap priority in grid/edit.js:137 and grid/save.js:67
Priority: blockGap → rowGap/columnGap → preset fallback
Status: RESOLVED Fix: Excluded container blocks from max-width extension Impact: No duplicate controls, extension preserved for future blocks
Status: RESOLVED Fix: Standardized contentSize usage across all edit.js files:
- section/edit.js:180
- row/edit.js:228
-
grid/edit.js:151
Pattern:
contentWidth || themeContentSize || '1140px'
Status: DESIGN DECISION (Not a bug) Decision: Always full-width by default Rationale: Interactive blocks work best at full container width User Control: Nest in Section/Row/Grid for layout constraints
Added: Comprehensive child block width rules in utilities.scss:112-234 Features:
- Full-width default for most blocks
- Inline sizing for Icon, Pill, Icon Button
- WordPress alignment class support
- Nested container handling
- Accordion/Tabs forced full-width
Issue #2: Row Block Manual Flex Issue #7: CSS Specificity Wars Reason: Lower priority, requires larger refactor
All container blocks use this structure:
<div className="dsgo-{block}"> // Outer wrapper (full-width, alignfull)
<div className="dsgo-{block}__inner"> // Inner container (constrained width)
<InnerBlocks /> // Children
</div>
</div>Why?
- Outer div: Handles full-width backgrounds, borders, padding
- Inner div: Handles content width constraints (max-width, auto margins)
- Separation: Background effects can be full-width while content is constrained
Implementation:
Standard Pattern (block.json):
{
"attributes": {
"constrainWidth": {
"type": "boolean",
"default": true // OR false, depends on block purpose
},
"contentWidth": {
"type": "string",
"default": ""
}
},
"supports": {
"align": ["wide", "full"]
}
}Current Defaults:
| Block |
constrainWidth Default |
Rationale |
|---|---|---|
| Section | true |
Content sections should constrain by default |
| Row | false |
Full-width layouts more common |
| Grid | false |
Full-width layouts more common |
Implementation:
- Section block.json (lines 82-89)
- Row block.json (lines 85-92)
- Grid block.json (lines 138-145)
All container blocks retrieve theme contentSize:
import { useSettings } from '@wordpress/block-editor';
export default function Edit({ attributes, setAttributes }) {
const [themeContentSize] = useSettings('layout.contentSize');
const { constrainWidth, contentWidth } = attributes;
// Editor preview
const innerStyle = {};
if (constrainWidth) {
const effectiveWidth = contentWidth || themeContentSize;
if (effectiveWidth) {
innerStyle.maxWidth = effectiveWidth;
innerStyle.marginLeft = 'auto';
innerStyle.marginRight = 'auto';
}
}
return (
<div {...blockProps}>
<div className="dsgo-block__inner" style={innerStyle}>
{/* children */}
</div>
</div>
);
}Frontend (save.js):
const innerStyle = {};
if (constrainWidth) {
innerStyle.maxWidth = contentWidth || 'var(--wp--style--global--content-size, 1140px)';
innerStyle.marginLeft = 'auto';
innerStyle.marginRight = 'auto';
}-
Editor: Uses actual theme value from
useSettings(e.g., "1200px") - Frontend: Uses CSS variable with fallback (e.g., "var(--wp--style--global--content-size, 1140px)")
Implementation:
- Section edit.js (line 86, 169-176)
- Section save.js (lines 54-59)
CSS Pattern for Alignment Classes:
.dsgo-block {
&.alignfull {
max-width: none;
width: 100%;
margin-left: 0;
margin-right: 0;
}
&.alignwide {
max-width: var(--wp--custom--layout--wide-size, 1200px);
width: 100%;
margin-left: auto;
margin-right: auto;
}
}Implementation:
- Grid style.scss (lines 36-48)
Critical Pattern: Force full-width for nested containers
.dsgo-section {
// When this block is inside another container's inner div
.dsgo-stack__inner > &,
.dsgo-flex__inner > &,
.dsgo-grid__inner > & {
width: 100% !important;
// And force its own inner to be unconstrained
.dsgo-section__inner {
max-width: none !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
}
}Why? When containers nest, the inner container should:
- Take full width of its parent's constrained area
- Ignore its own width constraints
- Let its parent handle the width constraint
Implementation:
- Section style.scss (lines 25-35)
- Row style.scss (lines 29-39)
- Grid style.scss (lines 86-96)
Different Blocks, Different Approaches:
{
"layout": {
"allowSwitching": false,
"allowInheriting": false,
"allowEditing": true,
"allowSizingOnChildren": true,
"default": {
"type": "flex",
"orientation": "vertical",
"justifyContent": "center"
}
}
}{
"layout": {
"allowSwitching": false,
"allowInheriting": false,
"allowEditing": true,
"allowSizingOnChildren": true,
"allowVerticalAlignment": true,
"default": {
"type": "flex",
"orientation": "horizontal",
"justifyContent": "left",
"flexWrap": "wrap"
}
}
}{
"layout": {
"allowSwitching": false,
"allowInheriting": false,
"allowEditing": false,
"allowSizingOnChildren": true,
"allowContentEditing": false,
"default": {
"type": "constrained"
}
}
}Implementation:
- Section block.json (lines 16-26)
- Row block.json (lines 16-28)
- Grid block.json (lines 16-25)
Problem: CSS references a class that's never applied.
History:
-
Deprecated version: Applied
dsgo-no-width-constraintwhenconstrainWidth: false- section/deprecated.js (lines 52-57)
-
Current version: Only uses base class
dsgo-stack- section/save.js (line 30)
CSS Still Expects It:
// This selector never matches!
.dsgo-stack.alignfull.dsgo-no-width-constraint > .dsgo-stack__inner[class*="wp-container-"] {
> :not(.alignleft):not(.alignright) {
max-width: none !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
}Impact: When constrainWidth: false, children still get WordPress's default max-width constraint instead of being truly full-width.
Location: section/style.scss (lines 148-156)
Fix Required:
// In save.js
const className = [
'dsgo-stack',
!constrainWidth && 'dsgo-no-width-constraint',
].filter(Boolean).join(' ');✅ RESOLUTION (2025-11-11): Fixed in commit 2765159. Class now properly applied in all three container blocks (Section, Row, Grid).
Problem: Row duplicates WordPress's flex layout system.
Current Implementation:
// row/save.js lines 79-99
const rawGapValue = attributes.style?.spacing?.blockGap;
const gapValue = convertPresetToCSSVar(rawGapValue);
if (blockProps.style?.gap) {
delete blockProps.style.gap; // Remove WordPress's gap
}
const innerStyle = {
display: 'flex', // Manual
justifyContent: layout?.justifyContent || 'left', // Manual
flexWrap: layout?.flexWrap || 'wrap', // Manual
...(gapValue && { gap: gapValue }), // Manual conversion
};Why It's Problematic:
- Duplicates WordPress functionality
- Requires manual preset-to-CSS-var conversion
- Different from Section (which uses WordPress layout classes)
- More maintenance overhead
- May break when WordPress updates its layout system
Location: row/save.js (lines 79-99)
Should Be: Let WordPress handle flex via layout classes (like Section does).
Problem: Hard-coded spacing preset instead of respecting WordPress blockGap.
// grid/save.js
const innerStyles = {
display: 'grid',
gridTemplateColumns: `repeat(${desktopColumns || 3}, 1fr)`,
alignItems: alignItems || 'start',
rowGap: rowGap || 'var(--wp--preset--spacing--50)', // ❌ Hard-coded
columnGap: columnGap || 'var(--wp--preset--spacing--50)', // ❌ Hard-coded
};Should Use:
const blockGap = attributes.style?.spacing?.blockGap || 'var(--wp--preset--spacing--50)';
rowGap: rowGap || blockGap,
columnGap: columnGap || blockGap,Location: grid/save.js (lines 55-61)
✅ RESOLUTION (2025-11-11): Fixed in both grid/edit.js:137 and grid/save.js:67. Now uses priority: blockGap → rowGap → columnGap → preset fallback.
Problem: Max-width extension adds attributes that blocks already have.
Extension Code:
// extensions/max-width/index.js lines 54-75
const isContainerBlock = [
'designsetgo/section',
'designsetgo/row',
'designsetgo/grid',
].includes(name);
if (isContainerBlock) {
return {
...settings,
attributes: {
...settings.attributes,
constrainWidth: { // ❌ Already in block.json
type: 'boolean',
default: true,
},
contentWidth: { // ❌ Already in block.json
type: 'string',
default: '',
},
},
};
}Result: Duplicate controls in two places:
- Native panel from block's own code (Section Settings, Row Settings, Grid Settings)
- Extension-created "Width" panel
Location: extensions/max-width/index.js (lines 54-203)
Fix Required: Remove extension entirely or refactor to only add controls to blocks that DON'T have them (e.g., Accordion, Tabs).
✅ RESOLUTION (2025-11-11): Container blocks excluded from extension via EXCLUDED_BLOCKS array. Extension preserved for future non-container blocks. See max-width/index.js:37-39.
Problem: Different contentSize sources in editor vs frontend.
Editor:
const [themeContentSize] = useSettings('layout.contentSize'); // Actual value: "1200px"
innerStyle.maxWidth = contentWidth || themeContentSize; // Uses: "1200px"Frontend:
innerStyle.maxWidth = contentWidth || 'var(--wp--style--global--content-size, 1140px)';
// Uses CSS var (might not be set) with different fallbackImpact:
- Theme sets contentSize to "800px"
- Editor shows 800px width
- Frontend might show 1140px if CSS variable isn't available
Fix Required: Use consistent approach or ensure CSS variable is always available.
✅ RESOLUTION (2025-11-11): Standardized all edit.js files to use contentWidth || themeContentSize || '1140px'. Consistent fallback ensures editor/frontend parity. Changed files:
Current State:
Accordion & Tabs:
- No
constrainWidthattribute - No
contentWidthattribute - No width controls in inspector
- Always 100% width
Inconsistency: Container blocks have width controls, but Accordion/Tabs (which also contain content) don't.
Is This a Problem?
- Maybe not - they might intentionally always be full-width
- But users might want to constrain tab panels or accordion content
Files:
- accordion/edit.js
-
accordion/style.scss (line 18:
width: 100%;) - tabs/edit.js
-
tabs/style.scss (line 21:
width: 100%;)
✅ DECISION (2025-11-11): This is intentional, not a bug. Accordion and Tabs blocks will ALWAYS be full-width of their parent container. Rationale:
- Interactive UI elements work best at full width
- Simpler UX (fewer controls)
- Users can nest in Section/Row/Grid for layout control
- Enforced via CSS in utilities.scss:211-215
Problem: Fighting WordPress's generated classes with !important.
WordPress Generates:
.wp-container-designsetgo-stack-is-layout-abc123 > :where(:not(.alignleft):not(.alignright):not(.alignfull)) {
max-width: 1200px;
}Plugin Fights Back:
.dsgo-stack.alignfull.dsgo-no-width-constraint > .dsgo-stack__inner[class*="wp-container-"] {
> :not(.alignleft):not(.alignright) {
max-width: none !important; // ⚔️ Specificity war
margin-left: 0 !important;
margin-right: 0 !important;
}
}Better Approach: Work with WordPress's system instead of against it.
Location: section/style.scss (lines 148-156)
// ✅ GOOD
<div {...useBlockProps({ className: 'dsgo-block' })}>
<div className="dsgo-block__inner" style={innerStyle}>
{children}
</div>
</div>// ❌ BAD - Single div can't separate background from content width
<div {...useBlockProps({ className: 'dsgo-block', style: innerStyle })}>
{children}
</div>// ✅ GOOD - Use WordPress layout system
{
"layout": {
"type": "flex",
"orientation": "vertical"
}
}// ❌ BAD - Manual implementation
const innerStyle = {
display: 'flex',
flexDirection: 'column',
gap: '20px'
};Why?
- WordPress handles responsive behavior
- Automatic gap support
- Theme integration
- Less code to maintain
// ✅ GOOD
const [themeContentSize] = useSettings('layout.contentSize');
const effectiveWidth = contentWidth || themeContentSize;// ❌ BAD - Hard-coded fallback
const effectiveWidth = contentWidth || '1140px';// ✅ GOOD - Explicit nesting rules
.dsgo-my-block {
// When nested inside other containers
.dsgo-stack__inner > &,
.dsgo-flex__inner > &,
.dsgo-grid__inner > & {
width: 100% !important;
.dsgo-my-block__inner {
max-width: none !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
}
}// ✅ GOOD - Frontend
innerStyle.maxWidth = contentWidth || 'var(--wp--style--global--content-size, 1140px)';// ❌ BAD - No fallback
innerStyle.maxWidth = 'var(--wp--style--global--content-size)';// ✅ GOOD - Apply classes based on attributes
const className = [
'dsgo-block',
!constrainWidth && 'dsgo-no-width-constraint',
isNested && 'dsgo-nested',
].filter(Boolean).join(' ');Why? CSS can target specific states without !important wars.
// ❌ BAD - Extension adds attributes that block already has
addFilter('blocks.registerBlockType', 'dsg/add-width', (settings, name) => {
if (name === 'designsetgo/section') {
return {
...settings,
attributes: {
...settings.attributes,
constrainWidth: { ... } // Already in block.json!
}
};
}
return settings;
});Check First:
grep -r "constrainWidth" src/blocks/section/block.json// ❌ BAD - Block.json says "flex" but save.js manually implements
// block.json
{
"layout": {
"type": "flex"
}
}
// save.js
const innerStyle = {
display: 'flex', // Duplicates WordPress's layout classes
gap: '20px'
};Pick one approach and stick with it.
// ❌ BAD
rowGap: 'var(--wp--preset--spacing--50)'// ✅ GOOD
const blockGap = attributes.style?.spacing?.blockGap || 'var(--wp--preset--spacing--50)';
rowGap: blockGapFile: src/blocks/section/save.js
Change:
// Before
const className = 'dsgo-stack';
// After
const className = [
'dsgo-stack',
!constrainWidth && 'dsgo-no-width-constraint',
].filter(Boolean).join(' ');Also Fix: Row and Grid blocks (apply same pattern)
Test:
- Create Section with
constrainWidth: false - Add child blocks
- Verify children are full-width (no max-width constraint)
Option A: Remove Extension Entirely
If Section, Row, and Grid already have width controls, delete:
-
src/extensions/max-width/(entire directory) - Remove from
src/extensions/index.js
Option B: Refactor to Only Target Blocks Without Native Controls
// extensions/max-width/index.js
const blocksNeedingWidth = [
'designsetgo/accordion',
'designsetgo/tabs',
// NOT section/row/grid - they already have it
];Test:
- Open Section block → Should only see ONE width control panel
- Open Accordion block → Should see width controls (if keeping extension)
File: src/blocks/row/save.js
Goal: Let WordPress handle flex layout via useInnerBlocksProps.
Current:
const innerStyle = {
display: 'flex',
justifyContent: layout?.justifyContent || 'left',
flexWrap: layout?.flexWrap || 'wrap',
gap: gapValue,
};Refactor To:
// Let WordPress handle flex via layout classes
const innerBlocksProps = useInnerBlocksProps.save({
className: 'dsgo-flex__inner',
style: innerStyle, // Only width constraint, not flex properties
});Test:
- Create Row with various justify/wrap settings
- Verify editor matches frontend
- Check gap spacing with theme presets
File: src/blocks/grid/save.js
Change:
// Before
const innerStyles = {
rowGap: rowGap || 'var(--wp--preset--spacing--50)',
columnGap: columnGap || 'var(--wp--preset--spacing--50)',
};
// After
const blockGap = attributes.style?.spacing?.blockGap || 'var(--wp--preset--spacing--50)';
const innerStyles = {
rowGap: rowGap || blockGap,
columnGap: columnGap || blockGap,
};Test:
- Create Grid without custom gaps → Should use theme blockGap
- Set custom blockGap in Dimensions → Should apply
Option A: Use CSS Variable in Both
// edit.js - Change to match frontend
const effectiveWidth = contentWidth || 'var(--wp--style--global--content-size, 1140px)';Option B: Ensure CSS Variable is Always Set
// includes/class-designsetgo-public.php
public function add_inline_styles() {
$content_size = get_theme_support('editor-settings')[0]['contentSize'] ?? '1140px';
$inline_css = "
:root {
--wp--style--global--content-size: {$content_size};
}
";
wp_add_inline_style('designsetgo-style', $inline_css);
}Test:
- Switch themes
- Verify editor width matches frontend width
Decision Required:
Option A: Add Width Controls
- Consistent with Section/Row/Grid
- Users can constrain tab panels or accordion content
Option B: Keep Full-Width
- Simpler for users
- Less configuration needed
- Rely on parent containers for width
If Adding Width Controls:
- Add
constrainWidthandcontentWidthto block.json - Add useSettings('layout.contentSize') to edit.js
- Apply two-div pattern in save.js
- Add width constraint styles
- Add inspector controls
When creating a new container block with width constraints:
-
Attributes in block.json:
{ "constrainWidth": { "type": "boolean", "default": true/false }, "contentWidth": { "type": "string", "default": "" }, "align": { "type": "string", "default": "full" } } -
Supports in block.json:
{ "align": ["wide", "full"] } -
useSettings in edit.js:
const [themeContentSize] = useSettings('layout.contentSize');
-
Two-div structure in save.js:
<div className="dsgo-block"> <div className="dsgo-block__inner" style={innerStyle}> {children} </div> </div>
-
Conditional class in save.js:
const className = [ 'dsgo-block', !constrainWidth && 'dsgo-no-width-constraint', ].filter(Boolean).join(' ');
-
CSS nesting rules in style.scss:
.dsgo-block { .dsgo-stack__inner > &, .dsgo-flex__inner > &, .dsgo-grid__inner > & { width: 100% !important; .dsgo-block__inner { max-width: none !important; margin-left: 0 !important; margin-right: 0 !important; } } }
-
Inspector controls for width (if not using block supports)
-
Test nesting: Place inside Section, Row, and Grid
-
Test alignfull/alignwide
-
Test with different themes (different contentSize values)
Section (constrainWidth: true, 1140px)
└─ Section__inner (max-width: 1140px)
└─ Row (constrainWidth: false)
└─ Row__inner (width: 100% of 1140px = 1140px)
└─ Grid (constrainWidth: false)
└─ Grid__inner (width: 100% of 1140px = 1140px)
└─ Content (width: 100% of grid cell)
Expected Behavior:
- Section constrains to 1140px
- Row and Grid are full-width within that constraint
- Content respects grid columns
Section (alignfull, constrainWidth: true, 1140px)
└─ Section__inner (max-width: 1140px, centered)
└─ Section (nested, constrainWidth: true, 800px)
└─ Section__inner (should be FULL WIDTH of parent, not 800px!)
Expected Behavior:
- Outer Section: constrained to 1140px
- Nested Section: IGNORES its 800px constraint, becomes 1140px
- Nested Section__inner: 1140px (not 800px)
Why? Nested containers inherit their parent's constraint and can't be narrower.
Row (alignfull, constrainWidth: false)
└─ Row__inner (full viewport width)
└─ Section (constrainWidth: true, 1140px)
└─ Section__inner (should be 1140px, centered)
Expected Behavior:
- Row: Full viewport width
- Section: Can apply its own constraint because parent is unconstrained
Why? When parent is full-width, children can apply their own constraints.
| Test | Setup | Expected Result | Check |
|---|---|---|---|
| Constrained Section | Section with default contentSize | Inner div: max-width = theme contentSize | ✅ |
| Custom Width | Section with contentWidth = "900px" | Inner div: max-width = 900px | ✅ |
| No Constraint | Section with constrainWidth = false | Inner div: no max-width, full width | ❌ Issue #1 |
| Nested Containers | Section > Row > Grid | All full width of outermost constraint | ✅ |
| AlignFull | Section with alignfull | Outer div: full viewport, inner: constrained | ✅ |
| AlignWide | Section with alignwide | Outer div: wide size, inner: constrained | ✅ |
| Parent | Child | Expected Child Width | Status |
|---|---|---|---|
| Section (1140px) | Row | 1140px | ✅ |
| Section (1140px) | Grid | 1140px | ✅ |
| Section (1140px) | Section (800px) | 1140px (ignores 800px) | ✅ |
| Row (full-width) | Section (1140px) | 1140px (applies own) | ? |
| Grid (full-width) | Section (1140px) | 1140px (applies own) | ? |
| Section | Accordion | Full width of section | ? |
| Section | Tabs | Full width of section | ? |
Legend:
- ✅ Working correctly
- ❌ Known issue
- ? Needs testing
Related Documentation:
- BLOCK-SUPPORTS-AUDIT.md - Comprehensive audit of block supports usage
- BLOCK-CONTROLS-ORGANIZATION.md - Inspector controls organization
- CLAUDE.md - Main development guide
WordPress Resources:
- Initial documentation based on codebase analysis
- Identified 7 major issues
- Created migration guide
- Established best practices
Questions? See CLAUDE.md or BLOCK-DEVELOPMENT-BEST-PRACTICES-COMPREHENSIVE.md
Auto-generated from
docs/patterns/WIDTH-LAYOUT-PATTERNS.md. To update, edit the source file and changes will sync on next push to main.
- Accordion
- Blobs
- Breadcrumbs
- Card
- Comparison Table
- Countdown Timer
- Counter Group
- Divider
- Flip Card
- Form Builder
- Grid
- Icon
- Icon Button
- Icon List
- Image Accordion
- Map
- Modal
- Modal Api Reference
- Modal Auto Triggers
- Modal Fse Compatibility
- Modal Gallery Navigation
- Modal Next Phase
- Modal Performance Fixes
- Modal Security Audit
- Modal Security Fixes Summary
- Modal Trigger
- Pill
- Progress Bar
- Reveal
- Row
- Scroll Accordion
- Scroll Gallery
- Section
- Slider
- Table Of Contents
- Tabs
- Timeline
- Animation
- Background Video
- Block Animations
- Clickable Group
- Custom Css
- Expanding Background
- Grid Mobile Order
- Grid Span
- Max Width
- Responsive Visibility
- Reveal Control
- Scroll Parallax
- Sticky Header
- Text Alignment Inheritance
- Text Reveal
- Ai Assisted Development
- Best Practices Summary
- Block Controls Organization
- Block Development Best Practices Comprehensive
- Block Exclusion Guide
- Control Reorganization
- Design System
- Wordpress Block Editor Best Practices
- Color Controls Pattern
- Custom Css Filters
- Performance Css Strategy
- Width Css Strategy Implementation
- Width Layout Patterns
- Antigravity Audit
- Card Block Audit
- Claude Audit
- Comprehensive Audit
- Cursor Audit
- Scroll Accordion Stacking Notes
- Security Review 1.2.1
- 2026 02 11 Icon Search Aliases Design
- 2026 02 14 Overlay Header Design
- 2026 02 15 Deactivation Block Migrator Design