Overview

Open the Layers panel from the left sidebar by switching the left-sidebar status to Layers. The panel renders every root section as a collapsible row, with the section's element tree nested underneath. Each row shows a drag handle, an expand chevron, the element's tag label (e.g. P, Img, Pdp Gallery), and either its inline text snippet or its class name as a secondary label. Dialog sections are tagged with a Dialog badge and global blocks are tagged with a Global badge.

Layers is the safest way to manipulate nested elements — clicking inside a deep DOM tree on the canvas can be fiddly when elements overlap, but every node is one click away in the tree. It is also the only place where you can promote a section or a sub-tree into a reusable block via the row's Create Block action.

How it works

The panel is implemented in leftsidebar/Layers.tsx and reads children (the array of root section BaseNodes), activeChildId, and selectedElementIds from the main editor store useCommmerceEditor. Every action mutates that same store, so the canvas re-renders the moment a layer is reordered, hidden, or deleted.

Element CRUD goes through store/slices/createElementSlice.ts (deleteElement, duplicateElement, reorderElements), cross-parent moves go through store/slices/createMovementSlice.ts (moveElementToSection), and visibility toggles go through store/slices/createVisibilitySlice.ts (toggleElementVisibility, toggleElementVisibilityAllDevices, plus the matching section toggles). Every mutating action calls saveToHistory() before and after the change so the delta-based undo/redo system from createHistorySlice.ts can rewind it.

Visibility is breakpoint-aware: hiding a layer in mobile writes display: none into that node's responsiveStyles.mobile (or customResponsiveStyles[breakpointId] for custom breakpoints), never to the base style. The Layers row reads back the effective style for the active device via getResponsiveStylesForBreakpoint and dims the row when the element is hidden in the current breakpoint.

Selection is broadcast through createSelectionSlice.ts (setSelectedElementIds, addSelectedElementId, toggleSelectedElementId, clearSelectedElementIds). The Layers panel calls these same actions on click, Ctrl/Cmd-click, and Shift-click, so the canvas overlay, right-side property panel and Layers row always show the same set of selected ids.

Key features

  • Nested tree with chevrons — every container expands/collapses independently and the panel auto-expands to reveal the currently selected element when selection changes on the canvas. Use case: jump from the canvas to a deeply nested span (e.g. a price label inside a product card) without manually expanding three levels first.
  • Drag-to-reorder sections — grab the section row's drag handle and drop it on another section to call reorderSections(fromIndex, toIndex). A dashed drop zone appears under the last section so you can drop at the end. Use case: rearrange page layout (e.g. push a flashbar above the header) without re-cutting the markup.
  • Drag-to-reorder elements (with nesting) — drop near the top of a row to insert before, near the bottom to insert after, or on the middle 50% of a row to nest inside. The middle "inside" drop is suppressed for self-closing tags such as img or input. Use case: move an existing button into a freshly added container without cut-paste — drop it on the container row to nest it.
  • Multi-select — Ctrl/Cmd-click toggles a layer in the selection, Shift-click extends it. The selection state is shared with the canvas, so styling operations apply to every selected layer. Use case: select all three hero CTAs and apply the same border-radius from the right-side property panel in one shot.
  • Per-row context menu (EllipsisVertical) — every row has a menu with Duplicate, Hide/Show in current device, Hide/Show in all device modes, Create Block, and Delete. Use case: duplicate a finished card and tweak the copy to produce a sibling card instantly.
  • Per-device visibility — the visibility toggle stores display: none only on the current breakpoint's responsiveStyles, so a layer can be desktop-only or mobile-only without conditional logic. Use case: hide a long secondary headline on mobile while keeping it visible on desktop.
  • Hide-in-all-device-modes — wipes display: none from style, responsiveStyles.xl, desktop, tablet and mobile simultaneously (or sets all four). Use case: permanently retire a seasonal promo block without deleting it, so you can restore it next season.
  • Create Block from selection — picks the current row's id and opens the Create Block dialog via openCreateBlock([id]), promoting that sub-tree into a reusable block in the Blocks library. Use case: turn a finished testimonial card into a reusable block you can drop on other pages.
  • Global block recognition — sections that originate from a global block show a Global badge and expose an Edit Block action that opens the block in its own editor scope (Dev Portal or Store Editor URL). Use case: jump from a page-level row into the global header block to fix typo in the menu and have the change propagate everywhere.
  • Dialog block recognition — sections whose block_type is 'dialog' show a Dialog badge and a purple message-icon glyph so you can tell modal sections apart from in-flow sections. Use case: identify the size-guide dialog among ten in-flow sections at a glance.
  • Selection auto-expand — selecting an element anywhere (canvas, context menu, AI flows) automatically expands every ancestor section and container so the highlighted row is always visible. Use case: click a deep image on the canvas and the Layers tree opens to that row without scrolling/searching.
  • Empty-state quick add — when there are no sections, the panel renders an "Add your first section" link that calls addSection(). Use case: bootstrap a brand-new page with a single click instead of dragging in from the Elements panel.
  • Section root protectiondeleteElement refuses to delete a root section (it logs "Cannot delete root section element. Delete the section instead."); section deletion goes through deleteSection from the Section slice. Use case: prevents accidental loss of an entire section when you intended to delete its inner content.
  • Move-into-descendant protectionmoveElementToSection refuses to move an element into one of its own descendants, logging "Cannot move element into its own descendant". Use case: the tree never becomes a circular reference, so reorders are always safe to undo.
  • Smart section naming — name fallback order is block_nameblock_version_slug → the section's text → its classSection N. Use case: naming a block "Promo Banner" once makes every page where it's used immediately readable in Layers, with no extra naming step.

Layer row — actions reference

Every layer row in leftsidebar/Layers.tsx exposes the same set of controls, plus a section-only Edit Block action when the row originates from a global block. The table below maps each control to the slice that handles it and the use case it covers.

ControlScopeSlice / ActionUse case
GripVertical drag handleRowreorderSections / reorderElementsReorder sections at the page level, or reorder/nest elements inside a section without retyping markup.
ChevronRight / ChevronDownRowtoggleSectionExpansion / toggleElementExpansion (local state)Collapse a noisy section to focus on the rest of the page, or expand a container to reach a deep child.
Click on row bodySection / ElementsetActiveSectionId + setSelectedElementIdsMake a layer the active selection so right-side property panels (styles, attributes) bind to it.
Ctrl/Cmd + ClickElementtoggleSelectedElementIdAdd or remove an individual layer from a multi-selection — handy when batch-styling cards with non-adjacent layout.
Shift + ClickElementaddSelectedElementIdExtend the current selection to include another layer; used to batch-edit a group of related elements (e.g. all column headings).
Tag label chip (e.g. P, Img)ElementgetTagDisplayLabel (read-only)Identify the underlying HTML tag at a glance — note div/section render as container.
Text / class snippetElementjsHelper.getDottedText(element.text, 15)Distinguish two same-tag siblings (e.g. two P elements) by their first 15 characters of text or their class.
Duplicate menu itemSection / ElementduplicateSection / duplicateElementCreate a sibling copy with regenerated ids and classes — use when you want a near-identical card or section variant.
Hide/Show in current deviceSection / ElementtoggleSectionVisibility / toggleElementVisibility (with deviceMode)Set display: none on the current breakpoint only — desktop-only or mobile-only layers without conditional logic.
Hide/Show in all device modesSection / ElementtoggleSectionVisibilityAllDevices / toggleElementVisibilityAllDevicesTemporarily disable a finished block (e.g. a holiday banner) without deleting it.
Create Block menu itemSection / ElementopenCreateBlock([id])Promote any sub-tree (not just a section) into a reusable block in the Blocks library.
Edit Block menu itemGlobal sectionNavigate to block editor URL (Dev Portal or Store Editor scope)Jump into the source of a global block to edit it where all pages will pick up the change.
Delete menu itemSection / ElementdeleteSection / deleteElementRemove a layer; root sections are protected via deleteElement, so use deleteSection for top-level removal.
Dialog badgeSection (read-only)block_type === 'dialog'Quickly tell a modal section apart from in-flow sections so you don't accidentally style modal markup as inline.
Global badgeSection (read-only)is_global === trueWarn you that editing this section locally is not the right place — switch to Edit Block to update the source.
Dimmed row (opacity 50%)Section / Element (read-only)isSectionHidden / isElementHidden for current deviceModeVisual signal that the layer is hidden in the active device mode — avoids confusion when something is missing on canvas.

Workflows

1. Reorder a section

  1. Open the Layers panel from the left sidebar.
  2. Grab the grip handle on the left of a section row and drag it up or down.
  3. A black top-border on a target row marks the insertion point; release to drop.
  4. To drop at the very end, release on the dashed "Drop after last section" zone.

2. Nest one element inside another

  1. Expand the source section so the element row is visible.
  2. Drag the element's row over the target container row.
  3. Hover the middle 50% of the target row — the row gets a black inset ring, meaning "drop inside as the first child".
  4. Release to call reorderElements(elementId, newParentId, 0, sectionId).

3. Hide an element on mobile only

  1. Switch the canvas device mode to mobile (from the top control bar).
  2. In Layers, open the row's three-dot menu and click Hide in mobile.
  3. The element's responsiveStyles.mobile gains display: none — desktop, tablet and xl remain untouched.
  4. To reveal it again, return to mobile mode and click Show in mobile. To reset across all breakpoints, use Show in all device modes.

4. Promote a sub-tree into a reusable block

  1. Click the layer row whose sub-tree you want to extract — it does not have to be a root section, any container works.
  2. Open the row's three-dot menu and choose Create Block.
  3. The Create Block dialog opens via openCreateBlock([elementId]); pick a name and block type and save.
  4. The new block appears in the Blocks panel and can be dropped onto any other page.

5. Multi-select and batch-delete

  1. Click the first layer row to set the initial selection.
  2. Ctrl/Cmd-click each additional row to toggle it in via toggleSelectedElementId.
  3. Press Delete to call deleteElement for every id in the selection. Root sections are skipped silently with a warning.
  4. Press Ctrl/Cmd + Z to undo — the delta-based history slice rewinds every delete as a single step.

6. Jump into a global block to edit its source

  1. Find the section in Layers showing a Global badge.
  2. Open the row's three-dot menu and click Edit Block.
  3. The router redirects to {devApps}/{app_type}/{app_id}/{app_version_code}/{block_id}/{block_version_code} in Dev Portal, or the equivalent Store Editor URL.
  4. Edit the block in its own scope and save — every page that includes the block picks up the change.

Keyboard shortcuts

Layers itself is mouse-driven, but the canvas keyboard handlers from drag-drop/PageSections.tsx and control/index.tsx work against whatever is selected in the panel.

ShortcutActionNotes
ClickSelect single layerClears any previous multi-selection.
Ctrl/Cmd + ClickToggle layer in selectionMulti-select for batch operations.
Shift + ClickAdd layer to selectionAdds the clicked layer to the current set.
DeleteDelete selectionCalls deleteElement for every selected id; root sections are protected.
EscapeClear selectionAlso cancels in-progress drag operations.
Ctrl/Cmd + ZUndoReplays the last delta from the history slice.
Ctrl/Cmd + Y / Ctrl/Cmd + Shift + ZRedoRe-applies the last undone delta.
Arrow keysNudge by 1pxOnly on layers inside a free-flow container.
Shift + ArrowNudge by 10pxSame free-flow container constraint.

Tips & gotchas

  • The visibility eye in the row menu is breakpoint-scoped. If a layer looks hidden in the canvas, check whether the active device mode has display: none on it before assuming a CSS bug.
  • Dropping inside a self-closing tag (img, input, br, etc.) is silently disallowed — the panel collapses the "inside" zone to before/after only for those tags.
  • You cannot drag an element into one of its own descendants; moveElementToSection rejects the move and logs "Cannot move element into its own descendant".
  • The Layers panel hides its row-level action menu when the URL contains /apps/component or /apps/widget — in those Dev Portal contexts the section bar is intentionally minimal.
  • Selecting a section from the Layers panel also runs setActiveSectionId, which is what the right-side property panels key off of — so the Selection Info panel switches to that section the moment you click its row.
  • Section names are derived in priority order: block_nameblock_version_slug → the section's text → its class name → Section N. Naming a block well makes the Layers tree much easier to scan.
  • Custom breakpoints are stored on customResponsiveStyles[breakpointId] rather than responsiveStyles.{xl|desktop|tablet|mobile}. The Hide/Show menu detects which kind to write via isDefaultBreakpoint(breakpointId).
  • Duplicate runs updateUniqueIdsAndClasses on the cloned subtree — every nested id and CSS class is regenerated, with the commmerce_ prefix preserved only on root sections (other tags fall back to container_, p_, button_, etc.).