Template
App block container
A theme app extension block with a heading, a body slot and section-level padding controls. Use it as the wrapper for any storefront UI the app injects.
Save the Liquid as a block in your theme app extension's blocks/ folder, then add the CSS and JavaScript to the extension's assets/ folder. Merchants place the block from the theme editor, so every setting below shows up as an editable control.
Where the files go
- template.liquid → extensions/<name>/blocks/container.liquid
- template.css → extensions/<name>/assets/container.css
- template.js → extensions/<name>/assets/container.js
Rename the block handle and the CSS class prefix before shipping, so Rich Cart Drawer & Upsell cannot collide with another app that used the same starting point.
Code
Copy a single file, or download all 3 as an archive. Duplicate your theme before pasting into a live store.
template.liquidLiquid {% comment %} App block container — theme app extension block. Renders a titled container the app can mount storefront UI into. {% endcomment %} <div class="northjs-block" style="--northjs-block-padding: {{ block.settings.padding }}px;" data-northjs-block {{ block.shopify_attributes }} > {% if block.settings.heading != blank %} <h2 class="northjs-block__heading">{{ block.settings.heading | escape }}</h2> {% endif %} {% if block.settings.body != blank %} <div class="northjs-block__body">{{ block.settings.body }}</div> {% endif %} <div class="northjs-block__mount" data-northjs-mount></div> </div> {{ 'container.css' | asset_url | stylesheet_tag }} <script src="{{ 'container.js' | asset_url }}" defer="defer"></script> {% schema %} { "name": "App block", "target": "section", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "Build your bundle" }, { "type": "richtext", "id": "body", "label": "Body" }, { "type": "range", "id": "padding", "label": "Vertical padding", "min": 0, "max": 96, "step": 4, "unit": "px", "default": 32 } ] } {% endschema %}template.cssCSS .northjs-block { padding-block: var(--northjs-block-padding, 32px); container-type: inline-size; } .northjs-block__heading { margin: 0 0 0.5rem; font-size: 1.5rem; line-height: 1.2; } .northjs-block__body { margin-bottom: 1.5rem; color: rgb(0 0 0 / 0.68); } .northjs-block__mount { display: grid; gap: 1rem; } @container (min-width: 40rem) { .northjs-block__mount { grid-template-columns: repeat(2, minmax(0, 1fr)); } }template.jsJavaScript (() => { const MOUNT_SELECTOR = '[data-northjs-mount]'; function mount(root) { if (root.dataset.northjsMounted === 'true') return; root.dataset.northjsMounted = 'true'; // Replace this with the app's own render call. root.dispatchEvent( new CustomEvent('northjs:block:ready', { bubbles: true, detail: { root } }), ); } function init() { document.querySelectorAll(MOUNT_SELECTOR).forEach(mount); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } // The theme editor re-renders blocks without a page load. document.addEventListener('shopify:block:select', init); document.addEventListener('shopify:section:load', init); })();