Skip to content

Block Nesting Rules

Rules for what can be nested inside what in the block system.

Block categories

  • Groups (blockGroupProperties): Visual container with background, border, padding, shadow, animations
  • Containers (formProperties, sliderProperties, conversationProperties): Functional containers with their own sub-block systems (form inputs, slides, conversation messages)
  • Regular blocks (textProperties, graphicProperties, buttonProperties, htmlProperties, videoProperties): Visual elements
  • Structural blocks (baseProperties, closeProperties, expandableInitialProperties, expandableExpandedProperties, taglineProperties, iconProperties, removeProperties, labelProperties): Locked/structural, not user-movable

Nesting rules

Parent \ ChildRegular blockGroupContainerStructural
Top-levelyesyesyesyes
Groupyesyesyesno
Containernononono

In plain language

  1. Groups in groups: Allowed. Enables wrapper patterns like a shared background group containing sub-groups.
  2. Containers in groups: Allowed. A form or slider can live inside a group for shared styling. Containers use a dual-presence serialization approach: they are kept at top-level (so DataStore and other readers can find them) and also nested inside the group (for rendering). The engine uses the groupBlock prop to source block data from the group parent instead of DataStore when rendering containers inside groups.
  3. Regular blocks in groups: Allowed. The primary use case.
  4. Groups in containers: Not allowed. Containers have their own sub-block systems (form inputs, slides) and adding groups inside them would create conflicting parent hierarchies.
  5. Containers in containers: Not allowed. Same reason as above.
  6. Anything in containers via DnD: Not allowed. Container children (form inputs, slides, conversation messages) are managed by their own systems, not the general block list.
  7. Structural blocks in groups: Not allowed. These are locked to their position (base block, close button, expandable sections, etc.).

Nesting depth

Maximum group nesting depth is 3 levels (configurable via MAX_GROUP_DEPTH in utils.ts). The Creative Engine renders recursively via CreativeGroupBlock, which uses dynamic <component :is> to render children including other groups.

Depth is enforced at multiple points:

  • wouldExceedGroupDepth(): Called during DnD drops and Cmd+G grouping operations. Returns true if the operation would exceed MAX_GROUP_DEPTH.
  • getGroupDepth(block): Counts group ancestors upward from a given block.
  • getGroupSubtreeDepth(block): Counts nested group descendants downward from a given block.

Rendering containers inside groups

Containers (form, slider, conversation, AR) require special handling when placed inside groups because they rely on DataStore for their block data. The engine uses a two-layer approach:

  1. groupBlock prop: When a container is rendered inside a CreativeGroupBlock, it receives the block data directly via the groupBlock prop. The container component checks for this prop first and falls back to DataStore only when rendering at top-level.

  2. blockVisible() guard: Returns false for containers whose parent field points to a group. This prevents the top-level rendering loop from also rendering the container, which would cause double-rendering.

  3. Dual-presence serialization: When saving, containers inside groups are kept at both top-level (so DataStore and backend readers can find them) and nested inside the group's children. On load, promoteGroupChildren() flattens the nested copies back to the flat model the frontend uses internally.

  4. Z-index: Containers in groups receive proper z-index values via the order prop, just like regular blocks in groups.

  5. Template switching: Operations that replace block data (e.g. switching form templates) must preserve the parent field so the container stays associated with its group.

Implementation notes

Guards are enforced at three layers:

  • UI/DnD (BlocksList.vue): canDropHere() and canDropInGroup() check block types before allowing drops
  • Selection (BlocksList.vue): Multi-select and keyboard shortcuts (Cmd+G) check container context
  • Vuex (blocks.ts): moveBlockToGroup mutation validates block types as a safety net
  • Groupability (utils.ts): isGroupable() and NON_GROUPABLE_BLOCKS define which blocks can participate in grouping

Internal documentation