Container blocks hold other blocks inside them — sliders with slides, grids with columns, accordions with panels. Define them in your blockSchema using blocks_layout or object_list widgets.
Each child has its own @type and schema (from blocks). Children are stored in a shared blocks dict on the parent, with the field holding { items: [...] } for ordering:
All blocks_layout fields on the same block share the same blocks dict. So a block can have multiple container fields (e.g., header_blocks and footer_blocks) whose children all live in the parent's blocks.
All items share one inline schema, stored as an array with an ID field. Use dataPath when the data is nested within the block:
When allowedBlocks is set on an object_list, items can have different types (like blocks_layout) but are still stored as an array. Each item's type is stored in the field specified by typeField (defaults to '@type') and its schema is looked up from blocks:
Both blocks_layout and object_list look the same in the editing UI and blocks can be dragged between them — data is automatically adapted when moving between formats (ID fields added/stripped, type fields set appropriately).
Add data-block-uid to each child element. You don't need to mark the container element itself:
[data-block-selector~=...]), so one trigger can cover many descendants. Use it on a disclosure trigger (collapsed details, accordion header, hidden tab panel button) so that picking any block within from the sidebar opens / scrolls / activates the enclosing container. For <summary> triggers the bridge sets details.open = true directly (idempotent — won't toggle an already-open disclosure); for everything else it .click()s the trigger, skipping the click if aria-expanded="true". The contextNavigation <summary> and accordion panel buttons use this pattern; the carousel +1 / -1 / specific-slide-uid form above is a special case of the same attribute.Set addMode: 'table' for table-like structures (rows containing cells). This lets users add and remove columns as easily as rows:
A container can never be empty. When the last child is deleted, either the defaultBlockType is added, or a special block with @type: "empty" is inserted. Empty blocks are stripped before saving. Render them as empty space — Hydra puts a '+' button in the middle for the user to replace it.
You can override the look of the '+' button by rendering something inside the empty block and adding data-block-add="button" to it.
You can have one container type whose children are all kept the same @type, with the editor picking that type once on the parent. When the type changes, every child is converted (using each child's fieldMappings); when a new child is added it gets the selected type.
Declare itemTypeField on the blocks field — its value names a sibling field on the same schema whose value drives every child's @type. The sibling field is typically rendered with widget: 'blockTypeSelect', which computes its choices from the blocks field's allowedBlocks at render time:
The relationship is local: read the schema and you can see "the children of slides get their @type from variation" right next to the field declaration. Works the same for widget: 'blocks_layout' and widget: 'object_list' children.
On top of type syncing you can also have field _values_ centrally controlled at the parent — set once on the parent, applied to every child. Add ONE enhancer on the parent:
inheritSchemaFrom does two things automatically:
hideParentOwnedFields enhancer that's applied to every block at INIT — no per-child opt-in).The parent declares what it claims per child block type via parentControlled. If absent, the default is: parent claims everything _not_ listed in the child's fieldMappings['@default'] mapping. The default works for typical cases; set parentControlled only when you want a different split (e.g. keep a meta-toggle field editable per-child):
When parentControlled[childType] is set, it replaces the @default fallback for that child type. Both sides — the parent's "Item Defaults" fieldset and the child's hidden fields — are computed from the same single rule, so they can never get out of sync.
blocks_layout/object_list field; names the sibling field whose value drives every child's @type.inheritSchemaFrom. Use this when there is no blocks field to declare itemTypeField on (e.g. listings — see Listings).fieldMapping override is stored. Required for the FieldMappingWidget to appear.{ childType: [fieldName, ...] } per-child-type override. Replaces the fieldMappings['@default'] fallback.'itemDefaults').- `blocksField` — which sub-blocks field's allowedBlocks to use for the choices. Auto-discovers if omitted. Set to '..' when the choices should come from the enclosing parent's allowedSiblingTypes. - `filterConvertibleFrom` — only offer types whose fieldMappings accept the named source. Typically '@default' for listings (every item type must be populatable from canonical content fields).