Shopify Builds>Three Ships>Landing-page hero sections with a no-JS add-to-cart on page templates

Landing-Page Hero Sections With a No-JS Add-to-Cart on Page Templates

A hero kit for standalone campaign pages: per-breakpoint artwork and text placement in repeatable blocks, and a CTA that can be a real Liquid product form on a template where Shopify hands the theme no product at all.

A Shopify page template has no product in scope, so nothing on a marketing landing page can render a buy control the way a product page does. This hero pair takes the product from a section setting instead and renders a real Liquid product form with the variant in a hidden field, so the add works as a plain browser POST — and art-directs desktop and mobile as two separate images.

The Problem

This storefront runs a standing library of campaign pages: a skin quiz, a discovery-kit offer, a giveaway, a replenishment page, a mission page, a founder story. They are not product pages and they are not the homepage. Each one needs its own hero — art chosen for that campaign, text placed differently on a phone than on a laptop — and the offer pages need the hero itself to start the purchase of one specific product rather than hand the visitor off to a collection.

That last part is where Shopify stops helping. A page template has no product in scope. On a product template the theme gets a product object for free and every buy control is built from it. On a page template there is nothing to build from, and no variant picker to attach to.

The Constraint

So the buy control has to be told what it is selling, by someone who is not a developer. The only place that decision can live is a section setting a merchandiser picks from the theme editor, and whatever the section renders has to work from that single choice.

The second constraint is what the page can rely on at runtime. A campaign landing page is assembled from a handful of sections, and the theme's cart drawer script is not guaranteed to have initialised on it. A buy control that only works once a script has bound to it is a buy control that sometimes does not work — on exactly the pages where paid traffic lands.

The third is art direction. Text placement is set per breakpoint on these heroes, so the artwork has to be too: the desktop composition and the mobile crop are different images, not one image scaled twice.

What We Built

Two sections that work as a pair.

hero.liquid is the block-repeater. Each block carries a desktop image and a mobile image, independent desktop and mobile text position and alignment, a light or dark text pairing, an optional rotated subtitle set in an alternate font, and a CTA that is one of two things: a plain URL button, or a product-backed buy control.

The product-backed CTA is the interesting half. It renders a real {% form 'product' %} inside a <product-form> custom element, with hidden id and quantity inputs and a submit button that renders disabled when the chosen variant is unavailable. Because it is a genuine form rather than a scripted button, the add is a plain browser POST to Shopify's cart route. It does not wait for the theme's JavaScript to be ready, and it does not stop working if that script never runs on this page. Where the theme's cart script is present, the <product-form> wrapper upgrades the same markup into the drawer behaviour the rest of the store uses. One control, two behaviours, and the degraded one is the one that always works.

page-hero.liquid is the simpler sibling for editorial pages — a founder story, a mission statement — with a single <h1>, an optional caption panel, the same light and dark colour pairing, and one CTA. It exists so an editorial page does not have to be built out of a block repeater it will only ever use once.

Art direction works the same way in both. Each ships a desktop <img> and a mobile <img>, each with a hand-built srcset ladder, and switches between them with .hide-mobile and .hide-desktop. Alignment is applied by a per-block inline <style> scoped to .block-{{ block.id }} — a CSS rule that belongs to one block rather than a utility class shared across all of them.

Why This Way

A form is the platform's own contract for adding to a cart, and using it rather than replacing it is what makes the buy path independent of everything else on the page. The variant id travels as a hidden field, the browser knows how to submit it, and Shopify knows how to answer. The scripted alternative is faster when it works and silent when it does not, and a campaign page assembled section by section is precisely where "when it does not" happens.

What that buys, it also limits. The CTA sells one variant of one product, because a section setting is a single choice — the right shape for a single-offer page, and the wrong one for anything the visitor should be given options about.

Scoping the styles per block keeps a campaign's layout attached to the block that owns it: delete the block and its rules leave with it, which is what stops a hero library accumulating dead CSS across two years of campaigns. The price is that the section ships style tags in proportion to how many blocks a campaign uses, and none of that lands in a cacheable stylesheet.

Why Not an App

Landing-page builder apps — Shogun, PageFly and their peers — were already in use elsewhere on this store, so this was not a question of whether the category works. These sections are the native-theme alternative for the pages that did not need one.

The technical reason is ownership of the markup. A page-builder app renders the page's content, which means the buy control on an offer page belongs to the app too: it is configured in the app's canvas, it renders through the app's runtime, and it is not visible in the theme's own code when something goes wrong. Here the hero is a Liquid section and the CTA is a Liquid form. Both are in the theme, both are in version control, and a developer answering "what does this button do" reads one file.

Implementation Notes

  • The product-backed CTA renders {% form 'product' %} with hidden id and quantity inputs, so the section setting a merchandiser picked becomes a real cart POST without any client-side assembly of the request.
  • <product-form> wraps that form rather than replacing it, so the same markup behaves as a drawer add where the theme's cart script is present and as a full-page POST where it is not.
  • Alignment is applied through a per-block inline <style> block scoped by .block-{{ block.id }}, giving each campaign block its own rule instead of a shared utility class in the stylesheet.
  • Desktop and mobile art are two separate <img> elements with hand-built srcset ladders, switched with .hide-mobile and .hide-desktop rather than one image and an object-fit rule.
  • The light and dark text pairing is a single setting that flips the copy colour and the caption panel together, so a campaign cannot end up with dark text on a dark panel through two separate choices.
  • The optional rotated subtitle sets its own font family per block, which is how a campaign borrows a display face for one hero without introducing it to the rest of the theme.
  • The pair is placed on eleven landing-page templates in the library, seven of them currently enabled — the two sections are the standing hero kit rather than a one-campaign section.

Edge Cases

  • A sold-out variant renders the submit button disabled and drops the primary-button styling, so the control reads as unavailable rather than looking clickable and failing.
  • With no button link and no product selected, no CTA renders at all — the hero degrades to an image-and-copy composition rather than an empty button.
  • The subtitle, title and body text are each guarded independently, so a hero can be pure artwork with no copy over it.
  • The mobile image is a required input, not an optional one: a blank mobile setting renders an empty <img> rather than falling back to the desktop crop, so the art-direction decision stays explicit.

Platform Primitives Used

  • liquid-schema-settings — the product choice, the two images, the per-breakpoint placement and the colour pairing are all section and block settings resolved in Liquid.
  • cart-ajax-api — the CTA posts to Shopify's cart route through the theme's <product-form> element, so the add joins the store's existing cart behaviour rather than a second one.

Where It Runs

Landing pages, and the home page where the same hero is reused. The pair is placed on eleven page templates and enabled on seven of them, including page.discovery-kit-landing, page.replenish-lp, page.tdmgiveaway-lp, page.mission, page.founder and page.results — one hero kit serving the whole campaign library rather than a section per campaign.

What This Demonstrates

How We Know

Two section files read from the theme, roughly 400 lines across them. The client record here is thin, and it is worth being precise about that: one roadmap entry covers the landing-page programme these sections were built for, and it names the campaign, not the mechanism. Nothing on the client side documents how the CTA was meant to behave. The section pair is documented from the code — the settings it exposes, the markup it emits and the templates it is placed on — and the reasoning in this page is reconstructed from those files rather than from a decision anyone wrote down.

Related Builds

The Buy-vs-Build Question

A page-builder app buys a campaign library with a visual editor and a runtime on every page it renders. Building these two sections bought heroes in the theme's own code with a buy control made from Shopify's own form tag, and cost a fixed set of layout controls: anything the schema does not expose is still a developer's job. That trade is the question on the landing page builder decision page.

Provenance & Evidence

  • Client: Three Ships — threeshipsbeauty.ca
  • Surfaces: Landing pages, home page
  • Templates served: eleven placements across the landing-page library, seven currently enabled
  • Complexity: Medium
  • Attribution: Deploi-authored. Both sections are ours. The storefront around them is Palo Alto 5.8.0 by Presidio Creative — a paid premium theme, not our code — and the CTA deliberately posts through that theme's existing cart rather than a cart of ours.
  • Status: Live, verified 2026-09-06
  • Evidence: Two section files from the theme, plus a single client roadmap entry covering the landing-page programme
  • Confidence: Strong on the code, thin on the client record — the mechanism is documented from the files, and no client-side record of the decision survives
  • Primary capability: Art-directed hero and banner system

Ready to Build Campaign Pages in the Theme You Already Own?

You dream it. We build it. If your landing pages live in an app because your theme could not do them, Contact us today and we'll show you which of them your theme could take back.

More builds