Commmerce Editor
Elements
The Elements panel is the left-sidebar tile grid of primitives you drop onto the canvas to build a page — blocks like text, image, button, and container, ready-made CSS Grid layouts, and a wide library of shapes, decoratives, vector arts, and stickers. Drop one anywhere on the canvas or click it to insert at a precise position next to the currently selected element.
Overview
Open the Elements panel from the left sidebar by switching its status to Elements. Items are organised into six collapsible categories (Blocks, Layouts, Shapes, Decoratives, Vector Arts, Decorative Shapes) and rendered as a 3-column thumbnail grid. A search box at the top of the panel filters every category live by item label.
The panel exposes two interaction modes. Without a selection it acts as a pure drag source: grab an item and drop it on the canvas using the standard drag-drop flow. With a selection the panel becomes a click-to-place picker — click a tile and the element is inserted next to the selected node, at the N/E/S/W position you chose on the canvas's "+" plus-button overlay.
How it works
Implemented in leftsidebar/Elements.tsx, the panel statically defines every category and item, then runs each item through a createLibraryItem factory that pulls the tag's default attributes from TAG_ATTRIBUTES in types/types.ts and the tag's default sizing/spacing/typography from helpers in utils/ (getDefaultWidth, getDefaultHeight, getDefaultPadding, getDefaultFontSize, etc.). The result is a complete LibraryItem with tag, style, empty responsiveStyles per breakpoint, attributes, children, and a thumbnail.
Drag insertion serialises the LibraryItem onto the drag event as application/json with type: 'library-element'; the drag-from-sidebar drop handler in drag-drop/hooks/useElementDragDrop.ts reads it, computes the drop coordinates, and commits via addLibraryElementToSection on the element slice.
Click insertion calls handleElementClick in the same component, which inspects targetNodeInfo (which element on the canvas you are inserting next to, and on which side: n, e, s, w). For north/south positions it calls addElementAsSibling; for east/west it calls addLibraryElementToSection with the target as the new parent. Before committing, the handler adapts the element's style based on the parent's layout mode (free-flow vs stack vs grid), constrains the width to the container's effective boundary via getEffectiveContainerBoundary, and applies typography and media defaults.
On the drop handler side (useElementDragDrop.ts) the panel respects self-closing tags: dropping into img, input, br, hr, meta, link, area, base, col, embed, param, source, track or wbr (see SELF_CLOSING_TAGS in types/types.ts) sets dropEffect = 'none' so the drop is silently cancelled rather than producing an invalid tree.
Key features
- Six categories with collapse state — Blocks defaults to open, the others collapse. Search auto-opens any category that has a matching item. Use case: keep the panel scan-friendly during normal use, then expand everything by typing a query like "wave" to find a specific decorative shape.
- Blocks category — the everyday primitives:
p(Text, Paragraph),img(Image),button(Button),video(Video),div(Container),mmm-pdp-gallery(Product),mmm-form(Form), plus a "Line" image item used as a divider. Use case: bootstrap a section with a headline, image and CTA without leaving the panel. - Layouts category — 16 pre-shaped CSS Grid containers, from "Layout 1" (single full-width cell) through complex spans like Layout 15 (5-cell with col/row spans). Each one drops as a real
display: gridcontainer withlayoutConfig.mode = 'grid'and seeded grid cell children created viacreateGridCellItem. Use case: structure a product-feature section with three equal columns by dropping Layout 4 instead of hand-tuninggrid-template-columns. - Shapes, Decoratives, Vector Arts, Decorative Shapes — graphical assets dropped as
<img>elements whosesrcis the same asset as the panel thumbnail (built by theimgItemhelper). Use case: add a star burst behind a "Sale" badge or a wave divider between hero sections without sourcing your own SVGs. - Live search — the search box does a case-insensitive label match across every category. Categories with no matches collapse to nothing while the search is active. Use case: locate "Layout 9" or "Sticker 14" without hunting through six collapsed categories.
- Drag-from-sidebar — items are
draggableand emitapplication/jsonwith the fullLibraryItemondragstartwitheffectAllowed = 'copy'. Use case: drop a Container anywhere on the canvas to start sketching layout shapes by eye before refining the structure. - Click-to-place — when a
targetNodeInfois provided (the canvas surfaces a "+" overlay on the selected element with N/E/S/W choices), clicking a tile inserts the element at that exact position rather than dropping onto canvas coordinates. Use case: add a sibling Text node right above a selected button without playing with cursor pixels. - Auto-attribute defaults —
TAG_ATTRIBUTESdrives sensible attribute seeds:img.srcfalls back to a dummy image,a.hrefseeds to'#',input.typeseeds to'text',video/audiogetcontrols=true,preload=metadata,muted=true,autoplay=false. Use case: a freshly dropped Image renders the dummy preview immediately so you can position it before attaching a real asset. - Web component defaults — dropping an
mmm-product-cardseedsproduct-handle,show-variants, andquick-view-enabled; dropping anmmm-formseedsform-code,submit-button-text,success-messageandshow-title. Use case: a dropped Form preview renders the sample "contact-form" right away — swap theform-codeon the right-side property panel to bind your own form. - Layout-aware insertion — when the click target is the root section with a free-flow parent, the new element's width is clamped to the effective boundary returned by
getEffectiveContainerBoundary, so elements never overflow the contained content area;max-widthis also applied when the section is in contained mode. Use case: dropping an oversized image onto a contained-mode section auto-clamps its width to the section's content area instead of breaking out of the gutter. - Typography zeroing — typography tags (
h1-h6,p,span,label,button,a,strong,em,small,code,mark) getmargin: 0; padding: 0by default so they sit cleanly inside custom containers. Use case: dropping multiplePelements into a stack container stays tight by default — no surprise UA-style margins. - Media constraints — when an image, video, iframe, canvas, or svg is dropped into a nested container (not the root section), it gets
max-width: 100%; max-height: 100%; object-fit: containapplied automatically. Use case: placing a hero image inside a fixed-size card container fits the image inside without distorting the layout. - Auto-fill empty containers — dropping into a container with no children sets
width: 100%(non-media) orwidth: 100%; height: 100%(media) so the new element fills the empty parent rather than appearing as a tiny default-sized blob. Use case: the first element you drop into an empty box looks usable immediately — no need to manually set 100% width on the new child. - Free-flow absolute media positioning — when dropping a media element into a nested container with
layoutConfig.mode = 'free-flow', the element getsposition: absolute; left: 0; top: 0so it pins at the origin and is repositionable by drag. Use case: drop an image into a free-flow background container, then move it by drag like a sticker.
Element catalog
The full list of items exposed by elementCategories in leftsidebar/Elements.tsx. Each row lists the user-facing label, the category, the underlying HTML tag (or web component), and the use case it covers. Counts in the Decoratives, Vector Arts and Decorative Shapes categories are generated arrays so the library scales by adding assets to R.media.
TextBlockspDrop a paragraph headline or short description — gets margin: 0; padding: 0 typography defaults.ParagraphBlockspSame tag as Text but with longer "Paragraph text" default content — use when you want a body paragraph rather than a heading.ImageBlocksimgInsert an image — defaults to R.url.dummyImage so you see a preview immediately.ButtonBlocksbuttonDrop a styled CTA — gets background-color: #3b82f6 and white text by default.LineBlocksimgAdd a divider line image — uses R.media.blockLine as both thumbnail and rendered src.VideoBlocksvideoAdd an HTML5 video — seeds controls=true, preload=metadata, muted=true, autoplay=false.ProductBlocksmmm-pdp-galleryEmbed the product gallery web component — runtime-bound to the active product on PDP pages.ContainerBlocksdivDrop a generic block container — rendered as "container" in Layers (display tag swap from div).FormBlocksmmm-formEmbed a server-driven form — seeds form-code=contact-form, submit-button-text=Submit and a success message.Layout 1Layoutsdiv + grid (1fr / 1fr, 1 cell)Full-width single cell — use as a base for a single hero block with a known grid baseline.Layout 2Layoutsdiv + grid (repeat(2, 1fr) / 1fr, 2 cells)Two equal columns — split a section into a 50/50 image+text pair.Layout 3Layoutsdiv + grid (1fr / repeat(2, 1fr), 2 cells)Two equal rows — stack a header band above a content band.Layout 4Layoutsdiv + grid (repeat(3, 1fr) / 1fr, 3 cells)Three equal columns — product-feature trio or three-up testimonials.Layout 5Layoutsdiv + grid (1fr / repeat(3, 1fr), 3 cells)Three equal rows — stacked feature list with consistent height.Layout 6Layoutsdiv + grid (repeat(2, 1fr) / repeat(2, 1fr), spans)2 stacked left + 1 tall right — banner + side image composition.Layout 7Layoutsdiv + grid (repeat(2, 1fr) / repeat(2, 1fr), spans)2 top + 1 wide bottom — paired media on top with a footer CTA strip.Layout 8Layoutsdiv + grid (repeat(2, 1fr) / repeat(2, 1fr), spans)1 wide top + 2 bottom — feature headline above two supporting columns.Layout 9Layoutsdiv + grid (repeat(2, 1fr) / repeat(2, 1fr), 4 cells)2x2 grid — square category tiles or 4-up lookbook.Layout 10Layoutsdiv + grid (repeat(4, 1fr) / 1fr, 4 cells)Four equal columns — 4-up product grid for medium-density category pages.Layout 11Layoutsdiv + grid (repeat(3, 1fr) / repeat(2, 1fr), spans)3 top + 1 wide bottom — three small features above a wide call-to-action.Layout 12Layoutsdiv + grid (repeat(3, 1fr) / repeat(2, 1fr), 6 cells)3x2 grid — 6-up product showcase or icon grid.Layout 13Layoutsdiv + grid (repeat(2, 1fr) / repeat(3, 1fr), spans)Left tall + 3 stacked right — magazine-style hero with side list.Layout 14Layoutsdiv + grid (1fr 1fr 1fr / repeat(2, 1fr), spans)1 tall left + 4 right cells — premium product showcase with thumbnail strip.Layout 15Layoutsdiv + grid (repeat(3, 1fr) / repeat(3, 1fr), complex spans)Complex 5-cell with col/row spans — editorial collage for storytelling sections.Layout 16Layoutsdiv + grid (1fr / 1fr 2fr, 2 cells)Header + content — small header band over a taller content area.RectangleShapesimgDrop a flat rectangle behind a section for color blocking.CircleShapesimgDrop a circle backdrop for round avatars or category tiles.OvalShapesimgUse as an off-center accent shape behind a product image.TriangleShapesimgCompose pointer-style chevrons or directional accents.Triangle AltShapesimgAlternate triangle shape for variety in geometric compositions.PolygonShapesimgPolygonal frame for premium product showcases.HalfcutShapesimgHalf-cut circle for "section divider" looks.ArrowShapesimgDirectional arrow next to a CTA, "Shop now →".Star / Star Alt / Star 7 / Star 8ShapesimgStar variants for sale badges, ratings, or decorative accents.Sun / Sun LinesShapesimgSun-burst graphics for summer / seasonal promos.Sticker 1 … Sticker 22Decorativesimg (22 items)Generated array of decorative stickers — drop next to CTAs or product cards for visual interest.Wave 1 … Wave 11Vector Artsimg (11 items)Generated array of wave dividers — sit between two adjacent sections for a soft transition.Decorative 1 … Decorative 65Decorative Shapesimg (65 items)Generated array of decorative graphic shapes — use as overlays, backgrounds, or accent illustrations.Per-tag attribute defaults
createLibraryItem in Elements.tsx reads TAG_ATTRIBUTES from types/types.ts and seeds sensible values per tag. The most common defaults are listed below — anything not listed is set to an empty string so the right-side property panel surfaces the attribute without a pre-existing value.
imgsrcR.url.dummyImageShow a preview thumbnail immediately so layout can be designed before sourcing real art.videosrc, controls, preload, muted, autoplaydummyVideo, true, metadata, true, falseDrop a playable video — controls are visible by default, muted to avoid surprise audio.audiosrc, controls, preload, muted, autoplaydummyAudio, true, metadata, true, falseDrop a playable audio clip with controls visible.iframesrcR.url.dummyIframeDrop an iframe with a known-safe sample URL so the preview renders.ahref#Link is non-navigating by default until the merchant fills in a real URL.inputtypetextDrop a text input by default — switch to email/number/password in the property panel.mmm-product-cardproduct-handle, show-variants, quick-view-enabledsample-product, true, falseDrop a product card with variants visible to preview the layout immediately.mmm-formform-code, submit-button-text, success-message, show-titlecontact-form, Submit, Form submitted successfully!, trueDrop a working form bound to the sample contact-form until you bind a real one.Workflows
1. Drag a primitive onto the canvas
- Open the Elements panel in the left sidebar.
- Pick a Blocks tile — Text, Image, Button, Container, Video, Product, or Form.
- Drag it from the tile onto the canvas. The drop indicator shows where it will land within the section's layout mode.
- Release. The element is appended to that section with default styles and attributes (e.g. an
<img>drops with the dummysrc).
2. Click-to-place next to a selected element
- Select any element on the canvas — its "+" plus overlay appears on each side.
- Click one of the N/E/S/W plus buttons; the left sidebar opens with
targetNodeInfopopulated. - Switch to the Elements tab if not already there, then click a tile.
- The element is inserted as a sibling (for N/S) or as a child of the selected element (for E/W). The container closes via
onElementAdd.
3. Start a section from a pre-built grid layout
- Open the Layouts category in the Elements panel.
- Pick a layout that matches your structure (Layout 4 for three columns, Layout 6 for a 2-stacked + 1-tall combo, etc.).
- Drop it inside an empty section or click to insert it. It drops as a
divwithdisplay: grid, the chosengrid-template-columns/rows, gap0.625rem, and one seeded grid cell per defined slot. - Fill each cell by dragging more primitives or blocks into the cells.
4. Filter the library with search
- Type into the "Search something here" input at the top of the panel.
- Matching labels appear across all categories; non-matching categories collapse.
- Clear the search to return to the normal collapsed-by-default layout.
5. Add a shape as a background accent
- Open the Shapes (or Decoratives / Vector Arts / Decorative Shapes) category.
- Drag a shape tile onto a free-flow container — the panel writes the shape's thumbnail URL as both the
img.srcand the drag thumbnail (via theimgItemhelper). - The dropped
<img>getsposition: absolute; left: 0; top: 0automatically when the parent is free-flow mode, so you can drag it freely to position the accent. - Style it like any image — set opacity, rotation, or z-index from the right property panel.
6. Fill an empty container with its first child
- Drop a Container from Blocks onto the canvas.
- Select the empty container and use its "+" plus overlay to open Elements with
targetNodeInfopopulated. - Click any non-auto-size tile (Text, Button, Image, etc.) and the new child is inserted with
width: 100%(orwidth: 100%; height: 100%for media) because the parent has no other children. - Subsequent inserts into the same container keep their predefined dimensions.
Tips & gotchas
- The Elements panel is presentation-only — it never mutates the store directly. All inserts go through
addLibraryElementToSectionoraddElementAsSiblingon the editor store, which run through the normal delta-history pipeline, so every insert is undoable. - Layout grid items always take the full parent on insert (
width: 100%; height: 100%; position: static) — they are designed to be the structure of a section, not a floating widget. - Shapes, Decoratives, Vector Arts and Decorative Shapes are image tiles, not SVG primitives. They drop as
<img>elements with the asset URL, so you style them as images (size, opacity, transform) rather than tweaking SVG paths. - Product and Form blocks are web components (
mmm-pdp-gallery,mmm-form) — they need a Commmerce store context to render their real content at runtime, but they preview as the component element on canvas. - If a click-to-place tile does nothing, check that the canvas actually has a selection — the panel logs
"No target node information available"whentargetNodeInfois missing. - The drag thumbnail and the runtime
srcare intentionally the same asset for image-based items (see theimgItemhelper), so what you see in the sidebar is exactly what lands on the canvas. - Dropping into a self-closing tag (
img,input,br,hr,meta,link,area,base,col,embed,param,source,track,wbr) is silently rejected byuseElementDragDrop.handleDrop— pick a container instead. - Custom elements such as
mmm-*are NOT self-closing inSELF_CLOSING_TAGS; the parser always emits open/close tags for them so attributes survive serialization without the trailing-quote bug noted inisSelfClosingTag. divandsectionrender as "container" in Layers viagetTagDisplayLabeland use thecontainer_class prefix viagetClassPrefixForTag. The stored tag value staysdivorsection.