NorthJS

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 Form Builder 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);
    })();