docs
/
AI skills

Appmint components on a page

Drawers, dialogs, tabs, accordions, sliders, grids, mega menus and the product control as wm-* elements on a site page — every attribute, event and method, how buttons and page code drive them, how they take the site's look — plus what Build Studio's blocks and old data-react presets really store, and how to pick between a component, a block, plain HTML and the runtime.

Appmint ships a small set of web components, the wm-* elements. They are plain HTML tags: write the tag with the children it expects and it builds itself on the published page. This skill covers each one completely, the buttons and code that drive them, and the blocks Build Studio inserts.

It sits on top of Build a website page, which covers the page record, its screens and its URL. For what the page runtime itself can do (data-source, data-bind, data-action, window.appmint), see The Appmint JS engine on a page.

Ask first

  • Which page, on which site? You edit that page's active screen. If it's a new page, make it first with Build a website page.
  • What should the visitor be able to do? For example, open a menu, read an FAQ, flip through photos, filter cards, or pick a size and add to cart. Each need maps to one row of the table in step 1.
  • What goes inside it? Use the person's own words, links and images. Don't invent menu items, prices or answers.
  • Is the site dark or light? Drawers, dialogs and the product control default to a white panel (step 3).
  • Header or page? A menu that should be on every page belongs in the site's root page; see Shared header and footer. Read the warning in step 4 first, because the header renders differently from a page.

Steps

1. Choose: a component, a block, plain HTML, or the runtime

The visitor needsUseWhy
A panel that slides in (menu, filters, mini cart)wm-drawerHas an overlay, a scroll lock and open/close methods, and ui.drawer drives it
A popup (sign-up, notice, video)wm-dialogCentred panel with an X button, and ui.dialog drives it
Questions that open one at a timewm-accordion + wm-accordion-itemSets ARIA on header and content and rotates the icon
Switch between panelswm-tabsMatches labels to panels by order
A carouselwm-sliderArrows, dots, loop, autoplay, fade, several slides per view
A card grid, optionally filled from codewm-gridResponsive columns. Its search and filters are not reliable (see the reference)
A header menu with big panelswm-mega-menuHover or click panels and a built-in mobile menu
Size/colour buttons, quantity, add to cartwm-storefront-product-controlIt only emits events. Your code adds to the cart (step 3)
A list or record from data, a cart count, a value shown or hiddenRuntime attributes (data-source, data-bind, data-show)See The Appmint JS engine on a page
A button that saves, adds to cart, signs in, notifiesdata-actionThe same engine
Headings, text, images, columns, sectionsPlain HTML + CSS in the screenNo component is needed. Build Studio's blocks are only starting HTML (step 5)
A top navigation barPlain links (+ wm-mega-menu or wm-drawer)wm-navigation is not in the published bundle
Blog, booking, store, formsSite Features at their own URLsdata-react presets are gone (step 5)

Use a component only when it does something plain HTML can't: opening, switching, sliding. Static layout is plain HTML with the page's own CSS. Every component builds its own DOM when it starts, and that DOM is what you style and debug.

Hands on: for each live part, the element (or runtime attribute) to use. Check: nothing in the list needs wm-navigation or a data-react block.

2. Build Studio: write the element with its slots

Put the markup in the page's active screen, data.screens[<active>].html, and in its data.html mirror. Use page_write with html, or repo_update with both. The page shape, the URL rules and the footguns (id="root", a literal </body>, size limits) are in Build a website page, step 3.

Every component finds its children by slot="…" attribute. They are not Shadow DOM slots: the children stay in the page DOM, and the component moves them into wrappers it builds. A missing slot means the component does nothing, or does it half-way, so copy the slot names exactly.

The four you will use most, all tested on a live local site:

<!-- A drawer opened from anywhere on the page. The trigger slot may be hidden, but it must exist. -->
<wm-drawer id="menu" direction="right" width="320px">
  <span slot="trigger" hidden></span>
  <nav slot="content" class="menu-panel">
    <a href="/about">About</a>
    <a href="/contact">Contact</a>
    <button data-action="ui.drawer" data-payload='{"id":"menu","action":"close"}'>Close</button>
  </nav>
</wm-drawer>
<button data-action="ui.drawer" data-payload='{"id":"menu","action":"open"}'>Menu</button>

<!-- A dialog. Same rule: both slots, even if the trigger is hidden. -->
<wm-dialog id="signup" size="small">
  <span slot="trigger" hidden></span>
  <div slot="content" class="signup-panel">
    <h3>Join the list</h3>
    <button data-action="ui.dialog" data-payload='{"id":"signup","action":"close"}'>Done</button>
  </div>
</wm-dialog>
<button data-action="ui.dialog" data-payload='{"id":"signup","action":"open"}'>Sign up</button>

<!-- An FAQ: the first answer open; the icon class rotates on open. -->
<wm-accordion default-open="0">
  <wm-accordion-item>
    <button slot="header">Shipping <span class="accordion-icon">&#9662;</span></button>
    <div slot="content">Ships in 2 days.</div>
  </wm-accordion-item>
  <wm-accordion-item>
    <button slot="header">Returns <span class="accordion-icon">&#9662;</span></button>
    <div slot="content">30 days.</div>
  </wm-accordion-item>
</wm-accordion>

<!-- Tabs: labels and panels pair up by order; default-active is 0-based. -->
<wm-tabs default-active="0">
  <button slot="label">Overview</button>
  <button slot="label">Specs</button>
  <div slot="content">Overview text</div>
  <div slot="content">Specs text</div>
</wm-tabs>

A slider, static or filled from the catalog. Both forms were tested; the runtime rebuilds the slider when its rows arrive:

<wm-slider slides-per-view="1" speed="400" autoplay="true" autoplay-delay="5000">
  <div slot="slide"><img src="https://…/1.jpg" alt=""></div>
  <div slot="slide"><img src="https://…/2.jpg" alt=""></div>
</wm-slider>

<wm-slider data-source="product.list" data-source-payload='{"limit":6}' show-dots="true">
  <div slot="slide" data-bind-each>
    <img data-bind-attr-src="data.image" alt="">
    <span data-bind="data.name"></span>
  </div>
</wm-slider>

Every attribute, event and method of each element is listed under The wm-* elements below.

A wm-drawer or wm-dialog without both slot="trigger" and slot="content" children never builds. Its children sit in the page as ordinary visible content, for example a row of menu links. A ui.drawer "open" on it still runs open(), which sets open and locks page scrolling (body overflow: hidden) with nothing on screen. Tested: the links stayed inline, the drawer had no panel, and the page could not scroll.

Hands on: the screen HTML with the elements in it. Check: read the page back with repo_find_one. Each wm-drawer and wm-dialog has one slot="trigger" and one slot="content" child. Each wm-accordion-item has slot="header" and slot="content". Each wm-slider has at least one slot="slide". Each wm-grid has a slot="item". Each id is unique on the page.

3. Build Studio: drive them, and make them look like the site

From buttons. These runtime actions find the element by id and call its method:

ActionPayloadWhat it does
ui.drawer{"id":"<id>","action":"open"|"close"|"toggle"} (default toggle), or just "<id>"Calls that wm-drawer's open() / close() / toggle(). If no element has that id, it opens or closes the site's own sidebar instead.
ui.dialogthe sameCalls the wm-dialog's method. If no element has that id, it opens the storefront product quick view for a product with that id.
ui.modalthe sameSame as ui.dialog, but the fallback quick view is always modal
ui.navthe sameCalls open/close/toggle on a wm-navigation or wm-mega-menu with that id. Neither has those methods on a published page (wm-navigation isn't loaded, wm-mega-menu has none), so it only sets a navOpen state value. Don't use it on these.

The id is matched with document.getElementById(id), then <tag>[data-id="<id>"]. Write it without a #.

Don't use the open-dialog / close-dialog effects (data-on-success="open-dialog:#id"). They only set or remove the open attribute, and the elements read it once, at start. Tested: open-dialog left the dialog hidden. close-dialog removed open but left the dialog showing, and the next ui.dialog "open" then did nothing, because the dialog still believed it was open. Chain a second button, or call the method from page code.

Don't put data-action="ui.drawer" on the element's own slot="trigger". The trigger already toggles on click, so the action toggles it a second time. Use the trigger slot alone, or a hidden trigger and a separate button.

From an onclick. onclick="document.getElementById('signup').open()" works on an ordinary page. It does not work in the shared header and footer, or on a page with a data-react block (step 4).

From page code. For anything conditional, call the element's methods from Page settings → Style → JavaScript (page_write javascript), guarded as in Build a website page, step 4. The elements are defined by /custom-elements.js, which the site loads after the page. Wait for them first:

(function boot() {
  if (document.body && document.body.getAttribute('data-design-mode') === 'true') return;
  var app = document.getElementById('shop-app');
  if (!app || !window.appmint || !window.customElements.get('wm-storefront-product-control')) return void setTimeout(boot, 30);
  if (app.dataset.bound) return;
  app.dataset.bound = '1';

  // The product control only announces the click. Adding to the cart is ours to do.
  document.getElementById('pdp-control').addEventListener('add-to-cart', function (e) {
    var d = e.detail; // { product, quantity, attributes: { size: 'M', color: 'black' } }
    window.appmint.cart.add({ sk: d.product.sk, data: d.product.data, quantity: d.quantity, attributes: d.attributes });
  });

  // React to a drawer opening, e.g. for analytics or focus.
  document.addEventListener('drawer-open', function (e) { /* e.target.id */ });
})();

Tested: the selected size and colour reached the cart line as {"size":"M","color":"black"}. appmint.cart.add takes one object: a product record (sk + data) or a cart line, plus quantity and the chosen attributes.

Their look. None of them use Tailwind. Each element injects one stylesheet into <head> the first time it is used (<style id="wm-drawer-style"> and so on), and everything it generates has a wm-* class you can style. Some styles they set inline:

  • tab wrapper borders;
  • tab and mega-menu panel visibility;
  • the mega menu's mobile button and full-screen menu, which are white with fixed styles.

Override those in your CSS with !important, or restyle the classes. Your slotted content keeps the site's CSS: fonts, colours and link styles reach it as usual.

On a dark site, set the panel colours. wm-drawer, wm-dialog and the product control use a white panel, but text colour is inherited. On a dark site, light text lands on white. Tested on a dark site: the drawer and dialog panels were rgb(255,255,255) with text rgb(238,234,226), so the content was unreadable. The reverse also happens: a visitor whose device is in dark mode gets a near-black #111827 panel on a light site, because the defaults change under prefers-color-scheme: dark. Set the variables to the site's own colours, which fixes both cases:

wm-drawer, wm-dialog {
  --wm-drawer-bg: #14161c; --wm-drawer-fg: #eeeae2;
  --wm-dialog-bg: #14161c; --wm-dialog-fg: #eeeae2;
}

Hands on: nothing new. Check: repo_find_one shows style.javascript at the top level, and the script parses.

4. Build Studio: where the element lives decides how it renders

How a component renders depends on where it sits:

WhereHow it reaches the browserEffect on components
A page's screen, with no data-react in itThe body HTML is written as isEverything works: wm-*, onclick, <style> in the body
A page's screen with a data-react placeholder anywhereRebuilt element by element through Reactwm-* elements still build (tested). onclick and other on… attributes are removed. A <style> in the body is sent from the server as the text [object Object]; the browser only repairs it after a hydration error. <link>, <script>, <meta> and <title> are dropped. Every element gains a data-node-id.
The root page's header/footer screens, and a parent page's screensAlways rebuilt the same wayThe same losses as the row above, on every page of the site

Tested on local, on a page with one dead data-react block:

  • an onclick button lost its onclick;
  • a body <link> disappeared;
  • a body <style> arrived as [object Object] and React threw a hydration error before repainting it;
  • a wm-accordion on the same page still built and opened.

In the shared header and footer, drive components only with data-action (ui.drawer) and the trigger slot, and put CSS in root's Page settings → Style. Never use onclick. See Shared header and footer.

/custom-elements.js is loaded by the page shell on every site page, and on the built-in login, account and checkout pages, which use the same shell. You never add a <script> tag for it.

Stop: a component in root is on every page of the site, and on a site that serves drafts a new page is live from its first save. Show the person what will appear and where, and wait for yes.

Hands on: nothing new. Check: the page holds no data-react (unless that is intended), and the header holds no onclick.

5. Build Studio: blocks and presets (what Studio inserts)

The page's component tree is the active screen's HTML. Build Studio parses the body into its tree of nodes and stamps each element with a data-node-id. On save it writes the body back into screens[<active>].html, keeps the stored <head> as it was, and mirrors the active screen into data.html. Animations and variants are stored per node in screens[].config.animations and screens[].config.variants, keyed by data-node-id. Keep those ids when editing HTML by hand, or the animation loses its element. See Animate elements on a page.

The insert palette (Web tab) inserts plain HTML. Every item is a template string, added as the last child of the drop target. The groups:

  • Interactive: Accordion, Tabs, Drawer, Dialog, Slider (variants cards, fade, vertical), Grid (minimal, blog, team), MegaMenu, Navigation.
  • Plain HTML:
    • Blank, Headings, Paragraph, Content, Div, TextBoxes, HR, Link, Image, SVG, Video;
    • HTMLForm, Input, Button, Select, Textarea, HTMLTable, HTMLList;
    • Gallery, Navigation (a plain <nav>), ImageAndText, Columns, Accents;
    • Blog, Contact, Ecommerce (including a static CartIcon), Feature, Footer, Header, Hero (including CTA), Pricing, Statistic, Step, Team, Testimonial, Card.

When an agent writes a page through the API, it writes the HTML itself. These blocks are just a starting point for a person on the canvas. Know what they carry:

What a palette block bringsWhat to do
Tailwind utility classes (px-6 py-3 bg-blue-600 dark:bg-gray-900, theme names like bg-primary)They work on the published page too: every site page loads the Tailwind v4 browser build (tested: md:grid-cols-3, lg:px-12, bg-primary). But the theme names are Build Studio's sample palette (bg-primary is #0cd6a0), not the site's colours, and dark: classes switch on for visitors whose device is in dark mode. Replace the colour classes with the site's own; layout classes can stay. See Make a page work on phones and tablets.
Sample text and picsum/w3schools sample mediaReplace all of it with the person's content
Hard-coded ids (the Navigation block)Rename them; ids must be unique on the page
The Interactive Slider cards variant has a debug attributeRemove it (it logs to the console)
The Interactive Navigation block is a wm-navigationDon't use it. It stays an inert tag on the published site
The property panel's settings for the wm-* elements don't all reach them.
  • The panel writes each setting as an attribute named after its camelCase key. The browser lowercases it, but the element reads the kebab-case name. These settings therefore do nothing: Dialog Close on overlay and Show close button; Drawer Close on overlay; Grid Columns sm/md/lg/xl, Show search, Search placeholder, Filters position, Empty message, Sort options.
  • Setting Open to off writes open="false". The element only checks whether open is present, so it opens on load.
  • After a reload, the panel shows defaults, not the saved values.
  • Slider and Mega menu settings do work.

To change any of these, edit the attribute in code view with its kebab-case name: close-on-overlay="false", columns-lg="3".

data-react presets. A preset placeholder looks like this:

<div class="wm-react-block" data-react="preset,web,Basic,VideoPlayer" data-react-config='{"url":"…"}'></div>

The site resolves the name through its preset registry, and that registry now returns nothing for every name. So:

  • the placeholder renders nothing, and it switches the whole body to the React path described in step 4;
  • today they come only from converting legacy pages: no palette item writes one;
  • on the canvas, an unresolved one shows an amber "Component not found" notice and is saved with data-preset-missing.

A page whose only content is a dead placeholder has no body. The site treats that page as empty. On a site with a usable root page, it renders root's header and footer with nothing between them (tested: 200, header and footer only). With no root or parent that draws anything, the URL is a 404. A <title> text or any other visible element counts as content. Replace the placeholder with HTML: for a booking, store, blog or form, link to that Site Feature's URL instead. See Site page routing.

Hands on: nothing new. Check: search the screen HTML for data-react= and wm-navigation; none should be left. No Tailwind colour class (bg-primary, text-secondary, bg-blue-600) should stand in for the site's own colours.

6. Check it on the site

Open the page in a real browser. The page source carries the HTML in a script payload, so grepping it proves nothing. Each element should have built its wrappers:

ElementBuilt when
wm-drawerhas a .wm-drawer-container child
wm-dialoghas a .wm-dialog-overlay child
wm-tabshas .wm-tabs-labels and .wm-tabs-contents
wm-accordion-itemthe header has aria-expanded
wm-sliderhas .wm-slider-track and .wm-slider-dots
wm-gridhas .wm-grid-grid
wm-mega-menuhas .wm-mega-menu-triggers holding the triggers
wm-storefront-product-controlhas .wm-storefront-product-control and an "Add to Cart" button

Then click once:

  • every opener opens, every close button closes;
  • after closing a drawer, the page scrolls again;
  • the tab changes, the slide moves;
  • one add to cart makes one cart line.

In the console, window.__WEBSITEMINT_CUSTOM_ELEMENTS_VERSION shows the bundle version (1.2.0 today), and customElements.get('wm-drawer') shows the element is defined. A "wm-drawer requires both…" or "wm-slider requires at least one slide" warning names the broken element.

Hands on: nothing new; the page is done. Check: the list above, at phone width (390 px) as well as desktop.

The wm-* elements

These are the elements in the bundle the site loads (/custom-elements.js, version 1.2.0). The source is in websitemint packages/ui, under preset/web/custom-elements/. A change to them ships only when base-app is rebuilt and redeployed.

Rules shared by all of them:

  • They read their attributes once, two animation frames after they connect. wm-slider, wm-grid and wm-storefront-product-control rebuild when a watched attribute changes later. wm-drawer, wm-dialog, wm-tabs, wm-accordion and wm-mega-menu don't.
  • They move your slotted children into wrappers they build. Don't style a slot child by its position under the tag (wm-tabs > button). Style it by its own class.
  • Rows from data-source are fine. When rows arrive inside a wm-* element, the runtime takes the wrappers apart and re-attaches the element so it builds again. wm-slider and wm-grid have a public rebuild. Tested with wm-slider and with wm-tabs (data-source="product.list" on the tag, a data-bind-each label template and a data-bind-each content template: one tab per product). Lists inside a drawer or dialog are a different case; see What it can't do yet.
  • Events bubble, so you can listen on document.
  • Booleans are "true"/"false" strings, except where the table says "present".

wm-drawer

A panel that slides in from an edge, with an optional dimmed backdrop.

AttributeValuesDefaultNotes
directionleft right top bottomright
widthCSS length400pxleft/right only; capped at 100vw
heightCSS length400pxtop/bottom only
overlay"false" hides itshownNo backdrop: the page stays clickable (tested)
close-on-overlay"false"closesBackdrop click closes
openpresentclosedOpens at start without animating
SlotRequiredRole
triggeryes (may be hidden)Click toggles the drawer
contentyesMoved into the sliding panel
Method / event
open() close() toggle() isOpen()open() also sets document.body.style.overflow = 'hidden'; close() clears it
setDirection(dir)Rebuilds in a new position
drawer-open / drawer-closedetail: { drawer }
CSS variableDefault
--wm-drawer-bg#fff (dark scheme: #111827)
--wm-drawer-fginherit (dark scheme: #f9fafb)
--wm-drawer-overlay-bgrgba(0,0,0,.5)
--wm-drawer-shadowsoft shadow
--wm-drawer-z50; the backdrop is z - 1
--wm-drawer-duration300ms

Classes it builds: .wm-drawer-overlay, .wm-drawer-container, .wm-drawer--<direction>, .is-open.

  • There is no Escape-key close, no focus trap and no close button. Add your own close button with ui.drawer "close".
  • The default z-index is 50. A site header with a higher z-index shows above the panel, so raise --wm-drawer-z.

wm-dialog

A centred modal panel with an X button.

AttributeValuesDefaultNotes
sizesmall (24rem) medium (42rem) large (64rem) fullmediumNot fullscreen (the Studio block's comment says so, wrongly)
overlay"false"shownNo dimming; clicks pass through to the page
close-on-overlay"false"closesBackdrop click closes
show-close-button"false"shownThe X in the corner
openpresentclosed
SlotRequiredRole
triggeryes (may be hidden)Click toggles
contentyesMoved into the panel
Method / event
open() close() toggle() isOpen()No scroll lock (unlike the drawer)
setSize(size)
dialog-open / dialog-closedetail: { dialog }

CSS variables: --wm-dialog-bg (#fff, or #111827 in a dark scheme), --wm-dialog-fg (inherit), --wm-dialog-overlay-bg, --wm-dialog-shadow, --wm-dialog-radius (.5rem), --wm-dialog-z (50), --wm-dialog-duration (300ms), --wm-dialog-close-color, --wm-dialog-close-hover.

Classes it builds: .wm-dialog-overlay (.wm-dialog--no-overlay), .wm-dialog-content, .wm-dialog--<size>, .wm-dialog-close, .is-open. The panel has no padding of its own, so pad your content.

wm-accordion and wm-accordion-item

OnAttributeDefaultNotes
wm-accordionallow-multiple (present)one at a time
wm-accordiondefault-opennone0-based index of the item to open
wm-accordion-itemopen (present)closed
Slot (on each item)RequiredRole
headeryesClick toggles; gets role="button", aria-expanded, aria-controls
contentyesShown with display:block, hidden with display:none; gets role="region" and an id
Method / event
item: open() close() toggle() isOpen()
accordion-open / accordion-closeFired on the item; detail: { item }. default-open fires one at start.
  • An element with class accordion-icon inside the header gets is-rotated (rotated 180°) when the item is open. --wm-accordion-icon-duration sets the speed (200ms).
  • An open item gets the class active; style it yourself.
  • An item works on its own without a wm-accordion around it, but then nothing closes the others.

wm-tabs

AttributeDefaultNotes
default-active00-based
SlotRole
label (one per tab)Click activates; gets role="tab", aria-selected, class active when active
content (one per tab)Matched to labels by order; display:block or none; role="tabpanel"
Method / event
getActiveIndex()There is no method to switch tabs from code. Click the label: el.querySelectorAll('[slot=label]')[i].click().
tab-changedetail: { index, label, content }. Fires at start too.

It builds .wm-tabs-labels (inline display:flex, border-bottom: 1px solid #e5e7eb) and .wm-tabs-contents. It injects no stylesheet, so all other styling is yours.

One tab per record works by putting the source on the tag itself and giving it two row templates. Tested: three products gave three tabs, and clicking the second label showed the second panel.

<wm-tabs data-source="product.list" data-source-payload='{"limit":3}'>
  <button slot="label" data-bind-each data-bind="data.name"></button>
  <div slot="content" data-bind-each>
    <p data-bind="data.name"></p>
    <p data-bind="data.price" data-format="currency"></p>
  </div>
</wm-tabs>

wm-slider

AttributeValuesDefault
effectslide fadeslide
directionhorizontal verticalhorizontal
slides-per-viewnumber, fractions allowed (1.2)1
space-betweenpx0
centered-slidestrue falsefalse
speedms500
autoplay"true" turns it onoff
autoplay-delayms3000
show-arrows / show-dots"false" hidesshown
loop"false" stops at the endson
debugpresentlogs its config

The camelCase spellings that Build Studio writes (slidesperview, autoplaydelay, showarrows, and so on) are read too. All of these attributes are watched, and a change rebuilds the slider after 100 ms.

SlotRole
slide (one or more)Each slide. With loop on, copies of the first and last slides are added (class wm-slider-clone, without the slot)
Method / event
next() prev()
goToSlide(i)i counts the copies. With loop on, goToSlide(1) is the first real slide (tested: goToSlide(3) showed slide 3 of 3).
setAutoplay(bool) setAutoplayDelay(ms) rebuild()
slide-changedetail: { index }, the same internal index
  • CSS variables: --wm-slider-arrow-bg, --wm-slider-arrow-bg-hover, --wm-slider-arrow-fg, --wm-slider-arrow-shadow, --wm-slider-dot-bg, --wm-slider-dot-bg-hover, --wm-slider-dot-bg-active.
  • Classes: .wm-slider-container, .wm-slider-track, .wm-slider-slide, .wm-slider-arrow--prev/--next, .wm-slider-dots, .wm-slider-dot.is-active.
  • Give the slides (or the slider) a height. Arrows and dots sit over the slide.
  • There is no swipe, keyboard control or pause-on-hover.
  • In Build Studio's canvas it does not build at all (data-design-mode), so the slides show stacked for editing.

wm-grid

A responsive grid of slot="item" children. Search and filters are built in, but only half work (warning below).

AttributeDefaultNotes
columns1below 640px; 1–6
columns-sm / -md / -lg / -xl2 / 3 / 4 / 4from 640 / 768 / 1024 / 1280px
gap16px
show-searchshown"false" hides the search box
filters-positiontopnone removes the filter area
search-placeholderSearch items...
empty-messageNo items found
animationfadeslide, none
sort-options—parsed, but no sort control is ever drawn
SlotRole
itemThe cards, or the template for setItems (data-field="path" children are filled: img gets src, input gets value, others get text)
searchYour own search UI; its first <input> is used
filtersYour own filter UI
Method / event
setItems(rows)Builds one card per row from the first slot="item" (tested)
setFilters(configs)Filter controls (display: select, checkbox, radio, color, range-input, range-slider) built from row data
clearFilters() getFilteredItems()
items-filtereddetail: { total, filtered }. Fires at start too.

CSS variables: --wm-grid-accent, --wm-grid-border, --wm-grid-input-bg, --wm-grid-label, --wm-grid-muted. Classes: .wm-grid-grid, .wm-grid-search-input, .wm-grid-empty, .wm-grid-filter-*.

  • Set columns-xl as well as columns-lg. Above 1280 px the grid uses columns-xl, which defaults to 4. Tested at 1440 px: columns-lg="3" gave four columns.
  • Its search box is broken. With static cards it filters nothing; tested, typing "beta" left all three cards. With setItems rows, typing throws Cannot read properties of undefined (reading 'length') (an undeclared searchFields), and nothing filters. Set show-search="false". For a searchable list, use a data-source list with a {{searchTerm}} payload (see The Appmint JS engine on a page).

wm-mega-menu

AttributeDefaultNotes
trigger-typehoverclick
hover-delay / hide-delay100 / 200 ms
animationfadeslide, none
mobile-breakpoint768below it, a hamburger button replaces the triggers
SlotRole
trigger (one per panel)Moved into .wm-mega-menu-triggers. On hover (or click) it shows the panel at the same index and gets class active.
panelposition:absolute; top:0 (inline), shown with visibility/opacity. You place it: left, width, and a top below the triggers with !important, and position:relative on the menu.
Method / event
getActiveIndex()No open or close method
mega-menu-changedetail: { index, trigger, panel }
  • You may write the .wm-mega-menu-triggers and .wm-mega-menu-panels wrappers yourself; it uses them if present.
  • It injects no stylesheet.
  • Below the breakpoint it adds a .wm-mega-menu-mobile-button. Tapping it opens a white, full-screen .wm-mega-menu-mobile appended to <body>, with copies of the triggers and panels and a close X (tested at 390 px).

trigger-type="click" never closes a panel. Tested: clicking the same trigger again, or clicking elsewhere, left it open. Only another trigger replaces it. Use hover (the panel hides after hide-delay), or close it from page code.

wm-storefront-product-control

Size and colour buttons, a quantity stepper, and Add to Cart / Buy Now buttons. It never touches the cart itself.

AttributeDefaultNotes
data-product—The product as JSON, {"sk":"…","data":{…}}. The way to give it a product on a published page (tested).
product-id—Loads the product only where window.requestQueueInstance exists (Build Studio), not on the site
show-quantity / show-buy-now / show-wishlist / show-shareshown"false" hides
sizemdsm, lg
actions-layoutstackedinline, grid

Where its choices come from, in product.data:

FieldWhat it draws
sizesstrings, or {value,label,available}. Required.
colorsstrings, or {value,label,param|color,available} swatches. Required.
variants[{name,label,options,required}]
stock, else availableshown as "<n> available"

It does not read the catalog's own variant model (data.attributes). For a real product you must map that model into sizes, colors or variants yourself (see Catalog pages from your products). Also, an available: true value prints as "true available".

Method / event
setProduct(product) setProductAttributes(attrs) getSelection()
attribute-changeddetail: { attribute, value, selectedAttributes }
quantity-changeddetail: { quantity } (1–999)
add-to-cart / buy-nowdetail: { product, quantity, attributes }. Add to Cart stays disabled until every required choice is made.
add-to-wishlist / sharedetail: { product }
  • It clears its own children each time it renders, so don't put markup inside it.
  • CSS variables: --wm-spc-accent, --wm-spc-accent-hover, --wm-spc-accent-soft, --wm-spc-fg, --wm-spc-muted, --wm-spc-border, --wm-spc-surface, --wm-spc-surface-hover, --wm-spc-buy-now-bg, --wm-spc-buy-now-hover, --wm-spc-danger, --wm-spc-radius, --wm-spc-duration. All have dark-scheme defaults.
  • Classes: .wm-spc-option.is-selected, .wm-spc-swatch, .wm-spc-btn--cart, .wm-spc-validation.
<wm-storefront-product-control id="pdp-control" show-wishlist="false" show-share="false"
  data-product='{"sk":"<product sk>","data":{"name":"Field Jacket","price":120,"sizes":["S","M","L"],"colors":[{"value":"black","label":"Black","param":"#000"}]}}'>
</wm-storefront-product-control>

Wire add-to-cart in page code as in step 3. Apostrophes in the JSON must be escaped as &#39; inside the single-quoted attribute.

wm-navigation (not available)

Build Studio offers it, and it has a schema and a source file, but it is not in the published bundle. On the site it is an unknown tag that shows its raw slot="nav-item" links. Build menus from plain links, wm-mega-menu and wm-drawer.

When it's done

Tell the person:

  • each component on the page, what opens or changes it, and what it shows;
  • any page code, and which events it listens to;
  • which colours you set for drawers and dialogs;
  • anything left out because it can't work yet (grid search, wm-navigation, data-react blocks).

Open the page on the site at desktop and phone width.

What it can't do yet

  • No lists inside a drawer or dialog. A data-loop or data-source inside a wm-drawer or wm-dialog makes the runtime rebuild the element around its rows. A loop of cart items inside a drawer froze the page, and inside a dialog it stripped the content (Custom elements). Put the list beside the element, or use the site's own cart drawer (cart.toggle).
  • wm-grid search and sort don't work (see the warning in its section), and its filters only work on rows added by setItems.
  • wm-tabs can't be switched from code except by clicking a label. wm-mega-menu has no open or close method.
  • wm-drawer and wm-dialog have no Escape-key close and no focus trap. wm-slider has no swipe.
  • wm-storefront-product-control doesn't read the catalog's variant model, doesn't load a product by product-id on the site, and doesn't add to the cart by itself.
  • wm-navigation and every data-react preset render nothing on the published site.
  • Build Studio's property panel doesn't apply several wm-* settings, and its "Open: off" opens the element (step 5).
  • Appmint's own AI instructions describe some of these wrongly. Build Studio's skill describes wm-drawer with position and trigger-text and children without slots, and calls the product control a "product listing/filter widget". The pages skill says drawers and dialogs default to overlay="false". The tables on this page are what the elements read.

Don'ts

  • Don't write a wm-drawer or wm-dialog without both a trigger and a content slot.
  • Don't open or close them with open-dialog / close-dialog effects or by setting the open attribute. Use ui.drawer / ui.dialog or the methods.
  • Don't put ui.drawer on the drawer's own trigger.
  • Don't use onclick, a body <style> or <link> in the shared header and footer, or on a page with a data-react block.
  • Don't leave a white panel on a dark site. Set --wm-drawer-bg/fg and --wm-dialog-bg/fg.
  • Don't rely on wm-grid search, sort-options, wm-navigation, product-id, or any data-react block.
  • Don't publish Build Studio's sample colours (Tailwind theme classes), sample text or sample media as they are.
  • Don't add a <script src="/custom-elements.js">. The site loads it.