↑↓ move↵ open⌘K toggle

    templates/page.lab.json

    Lab.

    The components that decide AOV and conversion, rebuilt live. Each one runs the same rules as the source beside it, and each one is something I've shipped in some form for a real store.

    What every component here handles

    • AccessibleKeyboard first, real form controls, live regions for every price change
    • Merchant-ownedTiers, goals and copy live in blocks, settings or metafields, not in code
    • Works without JSPlain forms post to /cart/add; JavaScript only makes it smoother
    • Markets-awareMoney through Liquid filters and currency rates, never string math
    • No layout shiftThe server renders the same markup the script updates

    01 · sections/bundle-progress.liquid

    Tiered bundle bar

    Shipped on Winona: 2 items unlock 10% off, 3 unlock 15%, 4 unlock 20%. The bar sits above the header on every page, so the deal is visible before the cart opens.

    • Always shows the next reward, not just the current one
    • Tiers live in section blocks, editable without a deploy
    • Same markup on first paint and after every cart update

    Press + and − to change the cart

    Build your own bundle and save

    1 item10% off2 items15% off3 items20% off4 items

    Add 1 more item to unlock 10% off

    HA + Peptide Repair Serumwith Ceramides + Resveratrol · $401 item$40.00

    Demo data, not the live store

    {%- liquid
      assign count = cart.item_count
      assign discount = 0
      assign next_tier = nil
    
      for block in section.blocks
        if count >= block.settings.min_items
          assign discount = block.settings.percent_off
        elsif next_tier == nil
          assign next_tier = block
        endif
      endfor
    
      assign fill = count | minus: 1 | at_least: 0 | times: 100 | divided_by: 3
    -%}
    
    <bundle-progress
      data-section-id="{{ section.id }}"
      data-discount="{{ discount }}"
      style="--fill: {{ fill | at_most: 100 }}%"
    >
      {%- if next_tier -%}
        {%- assign left = next_tier.settings.min_items | minus: count -%}
        {{ 'bundle.unlock' | t: count: left, percent: next_tier.settings.percent_off }}
      {%- else -%}
        {{ 'bundle.best_price' | t }}
      {%- endif -%}
    </bundle-progress>

    02 · sections/cart-drawer.liquid

    Cart drawer with goals

    Two rewards on one bar, and a gap closer that suggests the cheapest product that actually reaches the next one.

    • The upsell is chosen by the gap, never a $40 item to close $3
    • One request per change: the drawer HTML comes back in the cart response
    • Thresholds convert per market, so the gap is right in every currency

    Add the suggestion, change quantities, or reset

    Your cart1 item

    You're $12.00 away from free shipping

    Free shipping$50Free gift$90
    • Acne-Prone Skin System3-step routine · $381$38.00
    Arctic Cleanser unlocks free shipping
    Subtotal$38.00

    Demo data, not the live store

    {%- comment -%}
      Goals are blocks the merchant orders and edits in the theme editor. Amounts are in
      the store currency; on Markets stores the drawer converts them with
      Shopify.currency.rate before comparing (see cart-drawer.js).
    {%- endcomment -%}
    {%- liquid
      assign goals = section.blocks | where: 'type', 'goal'
      assign last = goals | last
      assign max = last.settings.amount | times: 100
      assign fill = cart.total_price | times: 100.0 | divided_by: max | at_most: 100
    -%}
    
    <cart-goals style="--fill: {{ fill | round: 1 }}%">
      <p class="goal__msg" aria-live="polite">
        {%- for goal in goals -%}
          {%- assign at = goal.settings.amount | times: 100 -%}
          {%- if cart.total_price < at -%}
            {%- assign gap = at | minus: cart.total_price | money -%}
            {{- 'cart.goal_gap' | t: gap: gap, reward: goal.settings.label -}}
            {%- break -%}
          {%- endif -%}
          {%- if forloop.last -%}{{ 'cart.goals_done' | t }}{%- endif -%}
        {%- endfor -%}
      </p>
    
      <div class="goal__track" role="presentation">
        <div class="goal__fill"></div>
        {%- for goal in goals -%}
          {%- assign at = goal.settings.amount | times: 100 -%}
          <span
            class="goal__tick{% if cart.total_price >= at %} is-hit{% endif %}"
            data-amount="{{ at }}"
            style="--p: {{ at | times: 100.0 | divided_by: max }}%"
            {{ goal.shopify_attributes }}
          >
            {{- goal.settings.label -}}
          </span>
        {%- endfor -%}
      </div>
    </cart-goals>

    03 · snippets/selling-plan-picker.liquid

    Subscribe & Save picker

    Subscription first, priced in what shoppers compare: per serving and per delivery. Under the button is the exact payload the form posts to the cart.

    • Every price comes from the variant's selling plan allocations
    • One-time removes the field, so the form never posts an empty plan
    • Works with Recharge, Skio or native subscriptions

    Switch between subscription and one-time, then change the frequency

    Burn2OFat-burning liquid shot · 30 servings

    POST /cart/add

    Demo data, not the live store

    {%- liquid
      assign variant = product.selected_or_first_available_variant
      assign group = product.selling_plan_groups | first
      assign first_plan = variant.selling_plan_allocations | first
    -%}
    
    <selling-plan-picker data-variant-id="{{ variant.id }}">
      {%- unless product.requires_selling_plan -%}
        <label class="plan__opt">
          <input type="radio" name="purchase" value="once">
          {{ 'products.one_time' | t }} <strong>{{ variant.price | money }}</strong>
        </label>
      {%- endunless -%}
    
      {%- if first_plan -%}
        {%- assign adj = first_plan.selling_plan.price_adjustments | first -%}
        <label class="plan__opt">
          <input type="radio" name="purchase" value="sub" checked>
          {%- if adj.value_type == 'percentage' -%}
            {{ 'products.subscribe_save' | t: percent: adj.value }}
          {%- endif -%}
          <strong>{{ first_plan.price | money }}</strong>
          <s>{{ first_plan.compare_at_price | money }}</s>
        </label>
    
        <fieldset class="plan__freq">
          {%- for plan in group.selling_plans -%}
            <label>
              <input type="radio" name="selling_plan" value="{{ plan.id }}"
                {% if plan.id == first_plan.selling_plan.id %}checked{% endif %}>
              {{ plan.name }}
            </label>
          {%- endfor -%}
        </fieldset>
      {%- endif -%}
    
      {%- comment -%} Price per serving from a product metafield {%- endcomment -%}
      {%- assign servings = product.metafields.custom.servings.value | default: 1 -%}
      <p>{{ first_plan.price | divided_by: servings | money }} / {{ 'products.serving' | t }}</p>
    </selling-plan-picker>

    04 · extensions/qty-breaks · Shopify Function

    Quantity breaks, priced by a Function

    The PDP shows the tiers; a Discount Function applies them in cart and checkout, so the price can't drift from what the page promised.

    • Runs on Shopify's servers: no script tags, no draft orders
    • Tiers are a JSON metafield on the discount, editable in the admin
    • Only tagged products qualify, and tests cover every tier

    Pick a pack and watch the Function output change

    Smart Amino BlendSmart protein · 30 servings

    cart_lines_discounts_generate_run → output

    Demo data, not the live store

    // @ts-check
    import { DiscountClass, ProductDiscountSelectionStrategy } from '../generated/api';
    
    /**
     * Quantity breaks. Tiers live in a JSON metafield on the discount, so the team
     * changes them in the admin without a deploy: { "tiers": [{ "min": 2, "percent": 10 }] }
     *
     * @param {import('../generated/api').CartInput} input
     * @returns {import('../generated/api').CartLinesDiscountsGenerateRunResult}
     */
    export function cartLinesDiscountsGenerateRun(input) {
      if (!input.discount.discountClasses.includes(DiscountClass.Product)) {
        return { operations: [] };
      }
    
      /** @type {{ tiers: Array<{ min: number, percent: number }> }} */
      const config = input.discount.metafield?.jsonValue ?? { tiers: [] };
      const tiers = [...config.tiers].sort((a, b) => b.min - a.min);
    
      const candidates = input.cart.lines.flatMap((line) => {
        const eligible = line.merchandise.__typename === 'ProductVariant' && line.merchandise.product.breaks;
        const tier = eligible && tiers.find((t) => line.quantity >= t.min);
        if (!tier) return [];
        return [{
          message: `${tier.percent}% off ${tier.min}+`,
          targets: [{ cartLine: { id: line.id } }],
          value: { percentage: { value: tier.percent } },
        }];
      });
    
      if (!candidates.length) return { operations: [] };
      return {
        operations: [
          { productDiscountsAdd: { candidates, selectionStrategy: ProductDiscountSelectionStrategy.All } },
        ],
      };
    }

    05 · snippets/sticky-atc.liquid

    Sticky Add to Cart

    The buy button follows the shopper once the real one scrolls away, and steps aside when that button or the footer is back on screen. It submits the same product form, so nothing can drift.

    • One form: the bar points at it with form="…", no duplicated inputs
    • Shows only after the button is passed, hides at the footer
    • Variant, price and stock stay in sync on every change

    Scroll the product page, then press Add to cart in the bar

    Daily Balance Blend

    60 capsules · 30 days

    $49.00

    1
    Daily Balance Blend1 jar · $49.00

    Button not passed · bar hidden

    POST /cart/add Press either Add to cart button

    Demo data, not the live store

    {%- doc -%}
      Sticky Add to Cart bar for the PDP. Render it once, right after the product form.
    
      @param {product} product - The product on the page
      @param {string} form_id - The id of the product form the bar submits
    
      @example
      {% render 'sticky-atc', product: product, form_id: product_form_id %}
    {%- enddoc -%}
    
    {%- comment -%}
      The button sits outside the product form and points at it with form="…".
      The browser submits that one form: same variant id, quantity, selling plan and
      line item properties as the main button, with no copied hidden inputs to keep
      in sync. Starts inert, so without JavaScript there is no bar and nothing breaks.
    {%- endcomment -%}
    {%- liquid
      assign variant = product.selected_or_first_available_variant
      assign image = variant.featured_image | default: product.featured_image
    -%}
    
    <sticky-atc
      data-form-id="{{ form_id }}"
      data-add-label="{{ 'products.product.add_to_cart' | t }}"
      data-sold-out-label="{{ 'products.product.sold_out' | t }}"
      inert
    >
      <div class="sticky-atc" role="region" aria-label="{{ 'products.sticky_atc.label' | t }}">
        {%- if image -%}
          {{ image | image_url: width: 96, height: 96, crop: 'center' | image_tag: class: 'sticky-atc__img', loading: 'lazy', alt: '' }}
        {%- endif -%}
        <div class="sticky-atc__info">
          <p class="sticky-atc__title">{{ product.title }}</p>
          <p class="sticky-atc__meta">
            {%- unless product.has_only_default_variant -%}
              <span data-variant>{{ variant.title }}</span> ·
            {% endunless -%}
            <span data-price>{{ variant.price | money }}</span>
          </p>
        </div>
        <button
          type="submit"
          form="{{ form_id }}"
          name="add"
          class="button sticky-atc__button"
          {% unless variant.available %}
            disabled
          {% endunless %}
        >
          {%- if variant.available -%}
            {{ 'products.product.add_to_cart' | t }}
          {%- else -%}
            {{ 'products.product.sold_out' | t }}
          {%- endif -%}
        </button>
      </div>
    </sticky-atc>
    
    {% stylesheet %}
      sticky-atc { position: fixed; inset: auto 0 0; z-index: 20; }
      sticky-atc[inert] .sticky-atc { transform: translateY(110%); }
      .sticky-atc {
        display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 12px; align-items: center;
        padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
        background: var(--color-background); box-shadow: 0 -8px 24px rgb(0 0 0 / 0.08);
      }
      .sticky-atc__title { margin: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
      .sticky-atc__meta { margin: 0; }
    {% endstylesheet %}

    07 · extensions/bundle-expand · Shopify Function

    Bundles expanded by a Cart Transform

    The shopper buys one kit at one price. A Cart Transform Function splits it into the real products for checkout, with the price allocated across them, so inventory, the order and the warehouse see actual SKUs.

    • Component prices always add up to the bundle price, to the cent
    • Components live in a product metafield, editable without a deploy
    • Uses the line cost in the buyer currency, so Markets needs no conversion

    Flip the switch, then change the quantity

    Your cart
    Calming Starter KitBundle · 4 items1$64.00$82.96
    Subtotal$64.00

    One line: what the theme and the cart drawer see

    run.graphql → input

    Demo data, not the live store

    // @ts-check
    
    /**
     * Expands a bundle line into its components, so checkout, the order, inventory and
     * the warehouse see real SKUs while the shopper still pays the bundle price.
     * The components live in a product metafield the app writes when the merchant
     * builds the bundle:
     *   [{ "variantId": "gid://shopify/ProductVariant/7001", "quantity": 1, "listPrice": "39.99" }]
     *
     * @typedef {import("../generated/api").RunInput} RunInput
     * @typedef {import("../generated/api").CartTransformRunResult} CartTransformRunResult
     * @typedef {{ variantId: string, quantity: number, listPrice: string }} Component
     */
    
    /** @type {CartTransformRunResult} */
    const NO_CHANGES = { operations: [] };
    
    /**
     * @param {RunInput} input
     * @returns {CartTransformRunResult}
     */
    export function cartTransformRun(input) {
      const operations = input.cart.lines.flatMap((line) => {
        if (line.merchandise.__typename !== 'ProductVariant') return [];
        /** @type {Component[] | undefined} */
        const components = line.merchandise.product.components?.jsonValue;
        if (!components?.length) return [];
    
        const prices = allocate(line.cost.amountPerQuantity.amount, components);
        return [{
          lineExpand: {
            cartLineId: line.id,
            title: line.merchandise.product.title,
            expandedCartItems: components.map((c, i) => ({
              merchandiseId: c.variantId,
              quantity: c.quantity, // per bundle: Shopify multiplies it by the line quantity
              price: { adjustment: { fixedPricePerUnit: { amount: prices[i] } } },
            })),
          },
        }];
      });
    
      return operations.length ? { operations } : NO_CHANGES;
    }
    
    /**
     * Splits one bundle's price across its components by their list prices, in cents,
     * so the parts add up to exactly what the shopper saw. The line cost is already in
     * the buyer's currency and the split is a ratio, so Markets needs no rate here.
     *
     * @param {string} bundlePrice
     * @param {Component[]} components
     * @returns {string[]} unit price per component
     */
    export function allocate(bundlePrice, components) {
      const total = Math.round(Number(bundlePrice) * 100);
      const weight = components.map((c) => Number(c.listPrice) * c.quantity);
      const sum = weight.reduce((a, b) => a + b, 0) || 1;
      const units = components.map((c, i) => Math.floor((total * weight[i]) / sum / c.quantity));
      // Leftover cents go to a component sold as one unit, where they land exactly
      const spent = units.reduce((s, u, i) => s + u * components[i].quantity, 0);
      const single = components.findIndex((c) => c.quantity === 1);
      if (single > -1) units[single] += total - spent;
      return units.map((cents) => (cents / 100).toFixed(2));
    }

    snippets/workbench.liquid

    On the workbench.

    Next in the Lab. Same rules: a live demo, the source and the reason it earns its place on the page.

    1. 08Checkout UI extension: a one-click upsell under the order summary
    2. 09Product quiz that writes its result to a customer metafield
    3. 10Variant picker with combined listings and swatches from metaobjects

    sections/contact.liquid

    Let's buildthe next one.

    Hiring for a senior Shopify role, need a storefront rebuilt, an app built or a workflow automated? Send a line.