Customizing blocks
All Commerce blocks in the boilerplate work out of the box, but you can customize them to match your business requirements. This guide covers the main customization approaches.
Ways to customize
Section titled “Ways to customize”Creating DOM elements
Section titled “Creating DOM elements”The boilerplate uses two approaches for creating DOM elements. The key distinction: use createContextualFragment for multiple elements, createElement() for single elements.
Template literals with createContextualFragment
Section titled “Template literals with createContextualFragment”Use this approach when creating two or more elements or complex nested structures.
// Best for: Complex layout structures with multiple nested elementsconst fragment = document.createRange().createContextualFragment(` <div class="cart__notification"></div> <div class="cart__wrapper"> <div class="cart__left-column"> <div class="cart__list"></div> </div> <div class="cart__right-column"> <div class="cart__order-summary"></div> </div> </div>`);
const $list = fragment.querySelector('.cart__list');block.appendChild(fragment);When to use
Section titled “When to use”- Creating two or more elements at once.
- Setting up initial block structure with nested elements.
- HTML hierarchy visualization improves code readability.
Why this approach
Section titled “Why this approach”- More efficient than multiple
createElement()calls. - Clear visual representation of HTML structure.
- Easier to maintain complex layouts.
Boilerplate examples
Section titled “Boilerplate examples”document.createElement()
Section titled “document.createElement()”Use this approach when creating a single element. This is cleaner and more explicit than template literals for one element.
// Best for: Single elements created dynamically// Inside a drop-in slot functionconst $wishlistToggle = document.createElement('div');$wishlistToggle.classList.add('cart__action--wishlist-toggle');
wishlistRender.render(WishlistToggle, { product: ctx.item, size: 'medium',})($wishlistToggle);
ctx.appendChild($wishlistToggle);When to use
Section titled “When to use”- Creating a single element (most common case).
- Adding elements inside drop-in slots.
- Building elements in event handlers or callbacks.
- Creating elements conditionally.
Why this approach
Section titled “Why this approach”- More explicit and type-safe than template literals.
- Cleaner for single elements (no unnecessary parsing).
- Easier to set properties programmatically.
Boilerplate examples
Section titled “Boilerplate examples”- commerce-cart.js (lines 221-232) - Creating wishlist container
- commerce-cart.js (lines 206-217) - Creating edit link container
Common customization patterns
Section titled “Common customization patterns”Adding block configuration
Section titled “Adding block configuration”Enable merchants to configure blocks through document authoring by reading configuration values:
import { readBlockConfig } from '../../scripts/aem.js';
export default async function decorate(block) { const { 'custom-option': customOption = 'default', 'max-items': maxItems = '10', 'enable-feature': enableFeature = 'false', } = readBlockConfig(block);
// Use configuration values if (enableFeature === 'true') { // Enable the feature }}Merchants can then configure the block in their documents:
| Commerce Cart |
|---|
| custom-option |
| max-items |
| enable-feature |
Customizing empty states
Section titled “Customizing empty states”Customize what users see when a block has no content:
// Inside the decorate function for your blockconst $emptyCart = document.querySelector('.cart__empty-cart');
// Create custom empty stateconst emptyState = document.createElement('div');emptyState.className = 'cart__empty-message';emptyState.innerHTML = ` <h3>Your cart is empty</h3> <p>Start shopping to add items to your cart.</p> <a href="/products" class="button">Browse Products</a>`;
$emptyCart.appendChild(emptyState);Adding custom analytics
Section titled “Adding custom analytics”Track custom events for analytics:
// Add to your block file or scripts/analytics.jsimport { events } from '@dropins/tools/event-bus.js';
events.on('cart/data', (cartData) => { // Custom analytics tracking if (window.dataLayer) { window.dataLayer.push({ event: 'cart_updated', cart_total: cartData.totalQuantity, cart_value: cartData.total.includingTax.value, currency: cartData.total.includingTax.currency, }); }});Using multiple drop-ins
Section titled “Using multiple drop-ins”Combine multiple drop-ins for complex functionality:
import { render as provider } from '@dropins/storefront-cart/render.js';import { render as wishlistRender } from '@dropins/storefront-wishlist/render.js';import CartSummaryList from '@dropins/storefront-cart/containers/CartSummaryList.js';import { WishlistToggle } from '@dropins/storefront-wishlist/containers/WishlistToggle.js';
export default async function decorate(block) { // Create container element const $list = document.createElement('div'); $list.className = 'cart__list'; block.appendChild($list);
// Render cart with wishlist integration await provider.render(CartSummaryList, { slots: { Footer: (ctx) => { // Add wishlist toggle to each cart item const wishlistContainer = document.createElement('div'); wishlistContainer.className = 'cart__action--wishlist-toggle';
wishlistRender.render(WishlistToggle, { product: ctx.item, size: 'medium', })(wishlistContainer);
ctx.appendChild(wishlistContainer); }, }, })($list);}Block-specific configuration
Section titled “Block-specific configuration”Some blocks accept configuration options that change their behavior. These are defined in the block README and can be set through document authoring.
Commerce Cart
Section titled “Commerce Cart”| Option | Type | Default | Description |
|---|---|---|---|
hide-heading | string | false | Controls whether the cart heading is hidden |
max-items | string | — | Maximum number of items to display in cart |
hide-attributes | string | '' | Comma-separated list of product attributes to hide |
enable-item-quantity-update | string | false | Enables quantity update controls for cart items |
enable-item-remove | string | true | Enables remove item functionality |
enable-estimate-shipping | string | false | Enables shipping estimation functionality |
start-shopping-url | string | '' | URL for “Start Shopping” button when cart is empty |
checkout-url | string | '' | URL for checkout button |
enable-updating-product | string | false | Enables product editing via mini-PDP modal |
undo-remove-item | string | false | Enables undo functionality when removing items |
Commerce Checkout
Section titled “Commerce Checkout”The Checkout block uses events for customization rather than configuration options.
Example: Add custom validation before checkout
Section titled “Example: Add custom validation before checkout”// Add to the decorate function for your checkout blockimport { events } from '@dropins/tools/event-bus.js';
events.on('checkout/values', (values) => { // Custom validation logic if (values.email && !values.email.includes('@')) { console.error('Invalid email format'); }
// Log when payment method is selected if (values.selectedPaymentMethod) { console.log('Payment method:', values.selectedPaymentMethod.code); }});Simple blocks
Section titled “Simple blocks”Many blocks (Login, Create Account, Forgot Password, and other account management blocks) are thin wrappers around drop-ins with no document-based configuration options. These blocks do not use readBlockConfig() and cannot be configured through document authoring. Customize these blocks by modifying their JavaScript to change drop-in options, or by using CSS and drop-in slots.
Real-world examples
Section titled “Real-world examples”Next steps
Section titled “Next steps”-
Browse the Commerce blocks source code to see implementation patterns.
-
Review the drop-in documentation for slots, events, and API functions.
-
Check individual block README files for configuration options and behavior details.
-
Test customizations locally before deploying to production.