Product Page
Peregrine's product page is fully block-based — every element is a block you can reorder, remove or configure independently — with a media gallery that offers eight desktop layouts rather than one.
Product information blocks
The main product section is built from blocks. Drag to reorder, remove what you don't need, and add as many of the general-purpose blocks as you like.
| Block | What it does |
|---|---|
| Vendor | The product's brand line, optionally linked to that vendor's collection |
| Title | Product title, with a heading-tag control (H1–H6) and its own size setting — the tag is semantic only, so changing it never changes how the title looks |
| Price | Sale-aware pricing: compare-at strikethrough, savings percentage and unit pricing where it applies |
| SKU and barcode | The selected variant's SKU, its barcode (the value from Shopify's Barcode / ISBN / UPC / GTIN field), or both — stacked or side by side, labels optional. Each value hides itself when the variant has none, so the block can be on store-wide and only the products that carry the data show it. Updates as the shopper changes variant |
| Variant picker | Swatches, buttons or a dropdown per option, driving price, availability, SKU, gallery and the page URL — see The variant picker |
| Quantity | Stepper input |
| Buy buttons | Add to cart plus optional dynamic checkout (Shop Pay, PayPal and so on), with full button styling — see below |
| Description | The product description |
Nothing here is one-way. Every block above can be added, removed and added back from Add block in the product section — remove the price block to try a layout, and it's waiting in the picker when you want it back. Product blocks appear in that picker only inside the product section, so there's no way to strand a Buy buttons block on a page that has no product to sell.
The description can also run full width under the gallery — the layout to use when your descriptions are long or carry their own images. Add the Product description section below the product: it has an optional heading (with the usual H2–H6 tag control), text alignment per device, its own padding and colours, and a "narrow content width" switch that keeps long text at a comfortable reading measure. It shows nothing on a product with an empty description. The same content can equally go in a Custom section holding a Description block, which is the route to take when you want the description sharing a section with other blocks; both read the product's one description, so they can never disagree.
Plus the shared block library: heading, text, image, button, icon, group and Custom Liquid blocks can all be added to the product information column, exactly as they can elsewhere in the theme. Use them for shipping notes, guarantees or sizing advice. The group block nests, so you can build a row of two icon-and-text pairs under the add-to-cart button.
App blocks are supported too — review widgets and similar apps can be dropped straight into the information column and positioned wherever you want them.
Subscriptions do not need an app block. The theme has its own Subscription options block: install Shopify Subscriptions (or any app that uses Shopify's native selling plans), attach a plan to a product, and the block renders the delivery choices itself — each option priced, with the saving against the one-time price, and the price shown per delivery where that differs from the amount charged today. Products with no plans render nothing at all. See Subscriptions & selling plans for the full chapter.
The media gallery
The gallery is the part of a product page that does the selling, so it gets eight desktop layouts and four mobile ones. They all show the same images — only the arrangement changes, so you can switch at any time without redoing anything.
The dashed column is where the product details sit. Every layout shows the same images — only the arrangement changes — so you can switch between them at any time without touching your photography.
Desktop layouts
| Layout | What you get | Best for |
|---|---|---|
| Thumbnails left (default) | A vertical strip of thumbnails beside a large main image | The safe choice for most stores |
| Thumbnails right | The same, mirrored | Design preference, or right-to-left languages |
| Thumbnails above | A horizontal strip over the main image | Wide imagery where a side strip would crowd the stage |
| Thumbnails below | A horizontal strip under the main image | The classic arrangement; good with many images |
| Carousel | One image with arrows, no thumbnails | Products with only two or three images |
| Stacked | Every image full width, one after another, with the product details pinned beside them | Editorial, furniture, considered purchases |
| Mosaic | A two-column grid with the first image spanning both | Rich catalogues with lifestyle photography |
| Featured + grid | One large hero image with the rest in a grid beneath | Products with one obviously strongest shot |
The last three show every image at once, so they suit products with plenty of photography — and they use no JavaScript at all.
Mobile layouts
Set separately, because what works on a desktop rarely works on a phone.
- Swipe carousel with dots (default) — swipe through images, dots showing position.
- Swipe carousel with counter — a "3 / 8" counter instead of dots. Better past about six images, where dots become noise.
- Thumbnails below — matches the desktop arrangement.
- Stacked scroll — every image stacked, no swiping.
The swipe carousel uses your phone's own scrolling rather than a slider library, so it feels native, works with accessibility settings, and adds almost nothing to page weight.
Image shape and size
- Image shape — Natural (default), square, two portrait ratios or two landscape ratios. Natural is the only one that never crops your photography, so it's the default; the fixed shapes are there for when your product images vary in size and you want a tidy grid.
- Image fit — appears once you pick a fixed shape. Contain shows the whole image with space around it; Cover fills the shape and crops. Contain is the default so nothing is silently cut off.
- Gallery width — how much of the row the gallery takes on desktop, 40% to 60%. The product details take the rest.
- Thumbnail size and overflow arrows for the thumbnail layouts.
Video and 3D
Product videos, YouTube and Vimeo embeds, and 3D models all display properly in every layout.
Augmented reality — "View in your space"
Upload a 3D model to a product in Shopify admin and Peregrine does the rest. On the 3D model itself, a View in your space button appears in the lower-left corner, and tapping it drops the product into the shopper's own room at real-world scale. It works from the full-screen zoom viewer too, which is where most people will want to look at a model closely.
There is nothing to switch on and no setting to find — if the product has a 3D model, the button is there.
The button only appears on devices that can actually run AR: iPhones and iPads on iOS 13 or later, and Android 7 or later. On a desktop browser there is no AR to launch, so Peregrine doesn't show a button that would do nothing — the model is still fully rotatable in the gallery. If you're checking your own store and can't see the button, check on a phone before assuming something is wrong.
If a product has more than one 3D model, each one carries its own button, so the shopper always previews the model they're looking at.
Videos never autoplay unless you switch it on, and an embedded YouTube or Vimeo video isn't loaded until the shopper presses play — until then it's just the poster image and a play button. That keeps a third-party video player and its tracking off your product pages entirely for the majority of visitors who never press play, which is better for speed and for your customers' privacy.
Zoom
Three choices under Image zoom:
- Off — no zoom at all.
- Lightbox on click (default) — clicking an image opens it full-screen at full resolution, with real magnification once it's open. Videos and 3D models open in it too rather than being skipped.
- Lightbox + hover magnify — adds a magnifier that follows the cursor over the image on the product page itself, before any click. On touch devices there's no cursor to follow, so it simply isn't there and the lightbox does the job.
A zoom button sits in the corner of the image as well as the image being clickable, so zoom is reachable by keyboard and obvious on a phone.
Inside the full-screen viewer
| Mouse and trackpad | Touch | |
|---|---|---|
| Zoom | Scroll wheel, or click the image to jump to 2× | Pinch, or double-tap to jump to 2× |
| Move around | Just move the mouse — the image follows the pointer, nothing to drag or hold | Drag with one finger, or move two fingers while pinching |
| Change image | The thumbnail rail, the on-screen arrows, or the left/right keyboard arrows | Swipe |
| Reset | Click again, press 0, or use the percentage button | Double-tap again, or use the percentage button |
Once magnified, the pointer's position chooses what's shown — move toward the left of the image and the left of it comes into view. It's the behaviour shoppers already know from the large retailers, and it crosses a big image in one small movement rather than a series of grabs and releases.
Products with more than one image get a thumbnail rail down the side of the viewer for jumping straight to a shot. On a single-image product the rail, the arrows and the counter are all absent rather than present-but-useless.
A zoom cluster sits at the bottom of the viewer with plus, minus and a live zoom percentage, so magnification is reachable without knowing any gesture at all — including by keyboard, where + and − zoom, 0 resets, and the arrow keys pan while zoomed in and change image while zoomed out.
Zoom goes to 4×, panning is bounded by the edges of the image so it can never be dragged off into empty space, and every image starts back at 100% — so a shopper who zooms into one photo isn't dropped into the middle of the next one.
The zoom viewer is not downloaded until someone actually zooms. Full-resolution images are only requested at that moment too. A shopper who never zooms never pays for the feature — which is why it can be on by default without slowing your product pages down.
Variant images
If you assign images to variants in your product admin, selecting a variant moves the gallery to that variant's image. You can switch this off if you'd rather the gallery stayed put.
To assign one: open the product in admin, click the variant in the variants list, and click the image square beside its name to pick from the product's media (or upload). Assign each colour its photo and the gallery follows the shopper's choice; variants without an image leave the gallery where it is.
Badges
The Sale, Sold out, New and custom tag badges from your product cards appear on the gallery too, with their own position (top left or top right, per device) and their own per-device maximum — so the label that earned the click is still there after it. They can be switched off for the product page alone in the gallery settings. Full details, including the custom badge1:–badge4: tag convention, are in Product badges.
The variant picker
Each product option — Colour, Size, Material — gets its own control, and the control type is chosen per option, not per product. A t-shirt can have colour swatches and size buttons side by side.
Three control types, picked automatically
| Type | What you get | Auto picks it when |
|---|---|---|
| Swatches | Round colour or image chips | The option's values have swatches set in your admin |
| Buttons | Pill buttons showing every value at once, sold-out ones visibly marked | The values fit as buttons — the best-converting pattern, because shoppers see the whole range without opening anything |
| Dropdown | A standard select | There are too many values to work as buttons |
Every option defaults to Auto, and Auto is usually right — but each of the first three options has its own override in the block settings if you want to pin a type.
Swatch setup: swatches come from Shopify's native option-value swatches — no apps, no image-naming conventions, and nothing under Settings → Custom data. They arrive through the product's category:
- Open the product in admin and, under Product organization, set its Category — pick the closest match from Shopify's standard list (e.g. Apparel & Accessories → Clothing). Categories power more than swatches, so this is worth doing on every product anyway.
- With a category set, Shopify links your Colour option to its standard colour list. In the product's variant options, edit the Colour values — each one now carries a swatch, and where Shopify's guess at "Sage" isn't your sage, click the value to adjust the colour or attach an image swatch.
- Save. Peregrine's Auto picker shows swatch chips for that option from the next page load — no theme setting needed.
These swatches are the product page's. The colour swatches in your filters are configured separately, in the Search & Discovery app — see Collection Filters. Shopify's own reference: Product categories.
The buttons-to-dropdown threshold
By default the theme judges for itself when an option has too many values for buttons — from the longest value name and the width the picker actually has. Twelve values of "S / M / L" fit comfortably where five values of "Extra Large Tall" would not, so a raw count alone can't make this call. If you'd rather pin the number, untick Auto dropdown threshold and a slider appears (an option with more values than the slider becomes a dropdown). The per-option overrides above always win over both.
Button layout
Four width modes for button-type options, set separately for desktop and mobile:
- Fit to label — each pill sized to its own text, so a row of mixed-length values has mixed-width pills.
- Equal width (desktop default) — one button width for the whole picker, not just within a row. See below.
- Fill row (mobile default) — every row stretches edge to edge, each row dividing the width by its own number of values; large thumb targets.
- Single scrolling row — never wraps; the row scrolls sideways with snap alignment.
All four show the same product — Colour with four values, Size with three, Flex with two. Watch the third row: under Fill row two values stretch to half the card each, while Equal width keeps them the same size as every other button and simply leaves the space.
Equal width is the one worth understanding, because it looks at the whole picker rather than one row. A product with Colour (4 values), Size (3) and Flex (2) would, under Fill row, give you three different button sizes stacked down the card — each row splitting the column by its own count. Equal width instead gives every button in every row the same size, and a row with fewer values simply leaves space at the end rather than stretching to fill it. The result reads as one control instead of three.
The width itself is worked out from the longest value name anywhere in the picker, so it adapts to your catalogue: a product whose values are "S / M / L" gets compact pills, while one with "Midnight Black" gets wider ones. You don't set a number.
Rows wrap onto as many lines as they need. If an option has more values than fit across the column, it continues on a second and third line, staying on the same grid so the buttons still line up with the rows above. Nothing is squeezed down to an unreadable sliver to keep everything on one line.
Alignment (Left, Center, Right) appears for Fit to label only. The other three modes all consume the full width of the row — Equal width included, since its buttons sit on a grid that spans the column — so there is no free space to align and the control simply doesn't appear.
A picker width slider (desktop and mobile) narrows the whole picker from the right if you don't want it spanning the full column — and the automatic threshold takes the narrower width into account.
Option labels
The label above each option — "Size", "Colour" — has its own styling controls, all set separately for desktop and mobile:
- Alignment — Left, Center or Right.
- Bold and Underline.
The styling covers the selected value read-out as well as the option name, so "Colour: Sage" stays one consistent line rather than two differently-styled halves.
Sold-out and impossible combinations
Values that can't be combined with the current selection are computed server-side, so they're always accurate. Three ways to show them, under Unavailable values:
- Cross out (default) — greyed and struck through, so shoppers can see the full range and what's gone.
- Hide — removed entirely. Cleaner, but shoppers can't see what normally exists.
- Show as available — everything selectable; the add-to-cart button reports the truth.
Switching is server-rendered
When a shopper picks a variant, the product information column is re-rendered by Shopify itself rather than patched together in the browser — so price, unit price, availability, SKU and every app block are always exactly what your store would serve on a fresh page load. The page URL updates too, so any variant can be copied, shared and bookmarked, and the back button behaves. With JavaScript off there's a plain variant form, so shoppers can always buy.
Quantity
The quantity stepper is its own block, so you can place it wherever it suits the layout. It follows whatever rules the variant actually carries:
- Minimum, maximum and increment from your product's quantity rules, where you've set them.
- Stock — if Shopify tracks the variant's inventory and you don't allow overselling, the shopper can't ask for more than you have.
When a rule is in force the theme states it under the stepper ("Increments of 6", "Maximum 50") and the + and − buttons stop at the boundary. A stepper that silently refuses to go further reads as broken, so it always says why. Products with no rules show nothing extra.
Rules are read fresh for each variant, so a size that sells in packs of six and a size that doesn't can sit on the same product.
Buy buttons
The Buy buttons block holds the add-to-cart button and everything that travels with it.
Adding to the cart
Adding happens in place — no page reload, and the cart count in the header updates immediately. If the add fails, the reason appears directly under the button in your store's own words ("Only 50 items were added to your cart due to availability"), rather than the button doing nothing and leaving the shopper guessing. With JavaScript off the form posts normally and still works.
Payment buttons and instalments
- Dynamic checkout buttons — the Shop Pay / PayPal / Google Pay express buttons.
- Show buttons side by side — puts Add to cart and the dynamic checkout button on one row instead of stacked. Off by default. Where the column is too narrow for a readable pair — most phones — they stack as before, automatically. The switch appears only while dynamic checkout buttons are on, since without them there is nothing to pair.
- Shop Pay Instalments — the "pay in 4" message. Safe to leave on: it only appears where your store and the order value qualify, and renders nothing at all otherwise.
The dynamic checkout buttons — Shop Pay, PayPal, Google Pay, Apple Pay and the plain "Buy it now" — keep Shopify's own colours. The theme sizes and places them but never recolours them: they are marks shoppers recognise, Shopify's brand rules require the branded ones untouched, and a Shop Pay button that is not purple reads as less trustworthy, not more on-brand. Your Add to cart button is fully yours to style; the pair sit level whether stacked or side by side.
Gift cards: sending to a recipient
On a gift-card product the block adds a "I want to send this as a gift" tickbox. Ticking it reveals fields for the recipient's email and name, a personal message, and an optional send date — Shopify then emails the gift card to that person instead of the buyer.
Nothing to configure. It appears automatically on gift-card products and never on anything else. The send date is limited to 90 days ahead, which is Shopify's own limit.
Pre-order
Sell something before you have it. Shopify has one native way to do this and it needs no app: let a variant keep selling when it is out of stock. Peregrine turns that into a proper pre-order — the button reads Pre-order, the stock line says so, and the order itself is marked so you can pick pre-orders out later.
The free way, in two steps
- On the variant, under Inventory, tick Continue selling when out of stock. (If you are not tracking inventory on that product at all, there is nothing to tick — skip this step.)
- Tag the product
preorder. That is the whole setup.
To show a ship date as well, put it in the tag: preorder:2027-03-12. Dates are shown in your store's own format. Anything the date reader cannot make sense of is shown exactly as you typed it, so preorder:Spring 2027 works too.
pre-order is accepted everywhere preorder is, and tags are matched whatever the capitalisation and whether or not there is a space after the colon.
The metafield way, when one date is not enough
A tag belongs to the whole product, so it can only ever state one date. If your medium ships in March and your large in May, that is what a metafield is for — it can sit on the variant.
- Go to Settings → Custom data. Choose Variants for a per-variant date, or Products for one date across the product.
- Add definition. Name it whatever you like; set the namespace and key to
custom.preorder_ship_dateand the type to Date. Save. - Fill the date in on the product or variant. Still tag the product
preorder— see the note below.
If your store already uses a different key, tell the theme where to look: Theme settings → Pre-order → Ship date metafield.
The tag is the switch. The metafield is only the date. A ship date on its own never turns pre-order on. That is deliberate — it lets you fill dates in ahead of a launch without anything appearing in your store early, and it means one place to look when you want to know why something is or is not on pre-order.
Where more than one is set, the most specific wins: a variant date beats a product date, which beats a date in the tag. Every layer is optional and each one quietly falls back to the one below it.
What the shopper sees
Per variant, not per product. Pick a size that is in stock and it reads Add to cart and In stock; pick one that is not and it becomes Pre-order and Pre-order — ships 12 March 2027. The sticky add-to-cart bar follows along, so the two never disagree.
The date goes on the stock line rather than in the button on purpose: with Add to cart and Buy it now side by side the button is half width, and a date would wrap on a phone. The button carries the action, the line above it carries the detail.
Tagging a product does not let it claim to be a pre-order when it is not. If Shopify is counting stock and there is some on the shelf, the shopper can have it today, so it reads In stock whatever the tag says.
On collection grids, in search results and in cart recommendations, a pre-order product shows a Pre-order button that takes the shopper to the product page rather than adding straight to the cart, and the quick-shop popup is not offered for it. That is deliberate: a pre-order is a decision, and the one fact it turns on — when it ships — only exists on the product page. The tile still says plainly what it is, so nothing looks broken or missing.
Finding pre-orders in your orders
Every pre-order line carries two pieces of information into the order:
| Property | What it does |
|---|---|
_preorder | A hidden marker. Shoppers never see it, in the cart or anywhere else. It is there for you and for automations. |
| Ships | The ship date, shown to the shopper in the cart, at checkout, on their confirmation email and on the order — so the expectation is set at every step, not just on the product page. |
Being straight with you about a Shopify limit: the Orders list filters on tags, status and fulfilment — not on line item properties. So the marker above shows on each order and in exports, but it cannot by itself give you a "Pre-orders" view. A theme has no way to tag an order.
The fix is a one-off rule in Shopify Flow, Shopify's own free automation app:
- Install Shopify Flow from the App Store and create a workflow.
- Trigger: Order created.
- Condition: order line items — properties — where name is
_preorder. - Action: Add order tags →
Pre-order.
From then on, Orders → filter by tag → Pre-order gives you the queue, and you can sort and export it like any other. Do check Flow is available on your plan before you rely on it.
Button styling
Two places, and only two:
- Your whole store — Theme settings → Add to cart buttons. Style, colour, text colour and border, applying to this button and to the Add buttons on product cards together, so a shopper meets the same button wherever they decide to buy. Hover comes from Buttons → Hover.
- One product — metafields. Colour, text colour, hover colour, hover text colour, border width, border colour and a background image can each be overridden for a single product, leaving everything else following your theme. Step-by-step setup.
The block itself now carries just the Metafield namespace those overrides are read from (custom unless your store keeps metafields elsewhere) and the background image settings. Its old per-template colour and border settings were removed — they were a third place to set one button colour, and a template is shared by every product assigned to it, so they were never really per-product in the first place.
A button using a background image still responds on hover: because the photo would cover any hover colour, the tint over the image shifts instead.
What the shopper typed is kept
Changing variant re-renders the product information from Shopify, which is what keeps the price, stock and every app block honest. The quantity the shopper chose and any gift-card details they've filled in are carried across that change rather than being reset — and the quantity is re-checked against the new variant's rules, so switching to a size that sells in fours won't leave an invalid number in the box.
Information blocks
Ten more blocks for the product column, each addable, removable and reorderable like the rest.
| Block | What it does |
|---|---|
| Collapsible group | An accordion — add rows inside it for Description, Delivery, Returns and Care, exactly as trust icons work. The group holds the settings every row should share (heading size, icon position, row spacing, rule colour) and can keep one row open at a time, so opening Delivery closes Description. Each row holds its own content: a heading plus your own text, a page, one of your store policies, the product's own description, or any mix — ticking Show product description turns a row into a per-product Description tab. |
| Inventory status | "In stock", "Only 3 left", "Out of stock" for the selected variant, with a status dot, coloured as a traffic light: green in stock, amber at your low-stock number (default 10), red at your very-low number (default 3) and when sold out. Green and red follow your theme's success and error colours; each level can be overridden on the block. You also choose whether the exact number shows. |
| Pickup availability | "Pickup available at…" for the selected variant, with a pop-up listing every pickup location. See below. |
| Sales point | An icon and one line — "Free UK delivery over £50". Add as many as you need. |
| Trust badges | A row of up to six images with optional captions, for payment marks, guarantees or certifications. Height, spacing and alignment are yours. |
| Size guide | A button that opens one of your pages in a pop-up. One page can serve every product. |
| Rating | Star rating from your review app. See below. |
| Goes well with | The complementary products you picked in Search & Discovery, which shoppers can add together as a bundle. See "Goes well with". |
| Share | Opens the device's own share sheet, or copies the link on desktop. |
| Separator | A rule, with thickness, colour and spacing, for grouping blocks visually. |
Inventory status shows only what's real
The theme will never display "12 people are viewing this" or "43 sold today". Those numbers are invented, they breach the UK's Digital Markets, Competition and Consumers Act 2024, and they put a Theme Store listing at risk. Inventory status is the honest version: it reads your actual stock and says what it finds. A variant you don't track shows "In stock" rather than a made-up figure, and one you allow overselling on says "Available on backorder".
Pickup availability reads your real locations
If a location in your Shopify admin offers local pickup, the block shows whether the variant the shopper has selected is ready to collect — "Pickup available at Shop Name" with the ready time you set there, or "Pickup currently unavailable" when that location is out of stock of it. A button opens a pop-up listing every pickup location with its availability, address and phone number.
To switch pickup on:
- Go to Settings → Shipping and delivery and scroll to Local pickup, under the delivery methods.
- Each of your locations is listed — click Set up (or Manage) on the one shoppers can collect from.
- Tick This location offers local pickup.
- Set the expected pickup time — "Usually ready in 24 hours", say. This exact phrase is what the block shows shoppers, so pick one you can honour.
- Optionally add pickup instructions for the order-ready email, then Save. The block starts rendering for stocked variants from the next page load.
Pickup availability is per variant per location, from your real inventory — a size that location doesn't stock shows as unavailable to collect even while delivery remains fine. Shopify's own reference: Local pickup.
Because the product information re-renders from Shopify on every variant change, the pickup line always describes the variant actually in the buy box — pick a size only your warehouse stocks and the message updates with it. On a store with no pickup locations the block renders nothing at all: no empty box, no setting to remember to turn off. The wording, like every built-in label, is editable per language in Edit default theme content, and the ready time ("Usually ready in 24 hours") is the one from your pickup settings, never invented.
Size guide: one page, every product
The Size guide block opens one of your pages in a pop-up, so the shopper never leaves the product. If you don't have a size page yet:
- In admin, go to Content → Pages (older admins: Online Store → Pages) and click Add page.
- Title it Size guide and build the content — the editor takes tables, which is usually what a size chart is. Paste from a spreadsheet and tidy, or use the table button.
- Set visibility to Visible and Save.
- In the theme editor, select the Size guide block in the product section and pick the page in its Page setting.
One page serves every product carrying the block. Ranges with different charts (footwear vs clothing) can use different pages on different product templates.
Rating: connecting your review app
Shopify gives themes no access to review data, so the Rating block reads the metafield your review app writes — Judge.me, Loox, Okendo and others all write one. Set which metafield under Reviews in the section settings; the defaults match Shopify's standard review definitions, so most stores need no change at all.
That single setting also feeds your Google structured data, so the stars shoppers see and the rating Google reads can never disagree. Until a rating exists the block shows nothing — no empty stars, no "no reviews yet" — and the structured data omits it entirely, which is what Google requires.
Share without the tracking
The usual share row is a set of network buttons, and each one is a request to Facebook or X that lets them set a cookie on a page your shopper never asked them to visit. This block uses the browser's own share sheet instead — which offers every app the shopper actually has — and falls back to copying the link on desktop. No third-party scripts, no pixels, nothing loaded from anyone else.
The collapsible group is a real accordion
Add a Collapsible group to the column, then add rows inside it — the same add-and-drag nesting as the trust icons, so there's one pattern to learn, not two. The group owns what every row should share: heading size, icon position (left or right of the heading), row spacing and rule colour. Set them once on the group instead of on every row.
The group can also keep one row open at a time: open Returns and Delivery folds away by itself, so a long accordion never sprawls down the column. Even this is done without JavaScript — the rows are built on the browser's native disclosure element, and the exclusivity is the browser's own. That means everything works with the keyboard, is announced properly by screen readers, and — the part scripted accordions always get wrong — the browser's in-page search can find text inside a closed row and open it. If several rows are set "open by default" while one-at-a-time is on, the browser keeps the first; that's the native behaviour, not a fault.
A row's content can come from four places, in any combination: rich text typed into the row, one of your pages, one of your store's own policies (so a Delivery row shows the real shipping policy with nothing copied and nothing to keep in step; leave the heading blank and the policy's name is used), and the product's own description. A row with no content yet shows a note in the editor and nothing at all to shoppers.
A policy row needs the policy to exist first. In admin: Settings → Policies, pick the policy (Refund, Shipping, Privacy, Terms), write it — or start from Shopify's template with Insert template and edit it to match what you actually do — and Save. Until a policy has content, a row pointed at it shows nothing to shoppers. Policies you save here also appear automatically in your checkout footer, so they're worth doing once regardless of the theme.
The same rows aren't limited to the product column: the Collapsible content section hosts them directly — full-width lower on the product page, as an FAQ on the homepage, or on any other page. Its preset arrives with Delivery, Returns and Product care rows ready to edit, and a "narrow content width" switch keeps long accordions at a comfortable reading width.
"Goes well with" — cross-sells you choose, as a bundle
A block for the product column showing the products you decided go with this one — the lead that matches the harness, the wax that suits the board. Shoppers can tick several and add them all, with the product they're looking at, in one go.
Where the list comes from
From Search & Discovery, Shopify's own free app. Install it, open Product recommendations, choose a product and add the complementary items — up to ten, in whatever order you arrange them. That's the entire setup. There's no metafield to create, no tags to type and no product handles to get right; the theme reads exactly what you picked.
It also means the list can be edited in bulk. Because Shopify stores these choices in a standard product field, a spreadsheet import can populate hundreds of products at once rather than you clicking through them one at a time — and the app and the import stay in step with each other either way.
Add as many or as few as suit each product: three on one and five on the next is fine, and that's exactly what shows. Products you haven't chosen any for simply don't display the block — no empty heading, no placeholder.
What the shopper sees
- A tile per product — image, name and price, each linking to the full product page.
- A variant dropdown where the product has options, so a lead can be chosen in the right colour without leaving the page. If the variant has its own photo, the tile's image changes to match as soon as it's picked. Sold-out options are shown as unavailable before they can be chosen, not after.
- A tick box per product, and a bar that appears once something is ticked showing the running total and a single button that adds everything at once.
Products with a lot of variants link through to their own page instead of squeezing a long dropdown into a small tile. You control where that limit sits under Advanced; the default is twelve.
Settings
| Setting | What it does |
|---|---|
| Heading | "Goes well with" by default. Anything you like — "Complete the set", "Don't forget". |
| Layout | Rows, two up, or a scrolling row — set separately for desktop and mobile. A scrolling row suits five or more tiles in a narrow column. |
| Let shoppers add several at once | On by default. Turn it off to show the products as a plain list with no tick boxes and no bar. |
| Include this product in the total | The bar adds the product being viewed alongside the ticked ones, using the quantity chosen above it. |
| Note under the bar | Your own line of text — see below. |
| Show the saving after adding | Displays the discount Shopify actually applied once the items are in the cart. |
Offering a discount on the bundle
You can, and it takes no app and no code — but the discount is created in Shopify, not in the theme. Under Discounts, create an automatic discount in either of these shapes:
- Buy X get Y — buy the harness, take 15% off the lead.
- Amount off products with a minimum quantity — any two items from a collection, 10% off.
Automatic discounts need no code from the shopper and apply themselves at the cart, which is exactly where a multi-item add lands. Nothing needs connecting between the two.
How it differs from the recommendation sections below
"Goes well with" is chosen by you, product by product, and sits beside the buy button because that's where a companion product belongs. The two sections further down the page are broader: one is Shopify's own algorithm suggesting similar products, the other pulls from the product's collection. Similar isn't complementary — a shopper looking at a harness gets more harnesses from an algorithm, and the matching lead only from you. Most stores will want both.
Without JavaScript
The tiles remain a working list of linked products with their prices. The tick boxes and the bundle bar aren't shown, because they'd have nothing to do — rather than being displayed and then failing silently.
Recommendations — "You may also like" and "More from this collection"
One section, two sources, and both can sit on the page at once — your product template ships with the pair stacked below the product:
- Shopify recommendations — related products from Shopify's own engine, which learns from the store's orders. The section loads lazily as the shopper approaches it and requests nothing until then. If Shopify has nothing to suggest, the section takes up no space at all — no stray heading, no empty band.
- The product's collection — other items from the smallest collection the product belongs to, rendered into the page with no JavaScript at all. The product being viewed is never shown.
Why the smallest collection? Because it's the most specific. Create a collection for a style or range — say "Wanna Bee" with its six matching pieces — and that collection automatically beats the bigger "Harnesses" category and the whole-catalogue collection. That's the intended workflow: group a style into its own collection and every product in it cross-sells the rest, with nothing to configure per product. (The homepage collection and collections where the product is the only member are ignored.)
Each instance has its own heading, columns, card styling, alignment and quick-add settings, shared with the rest of the theme's product grids — and because both sources render the standard product card, badges, prices and hover images all behave exactly as they do everywhere else.
Sticky add-to-cart
A slim bar slides in at the bottom of the screen when the main add-to-cart button scrolls out of view — image, title, live price and an add button that submits the real product form. Fully removable if you don't want it.
The bar carries a full variant picker. The same controls as the product page — swatches, buttons or a dropdown, resolved per option — so a shopper who has scrolled deep into the description can change size or colour without scrolling back. Turn it off and the bar shows the selected variant as a link back to the picker instead.
It has its own settings, in the sticky add-to-cart section rather than in the product picker: control type per option, the buttons-to-dropdown threshold, unavailable styling, button widths and alignment. They're deliberately separate, because a bar one row tall wants different choices from a full-width column — Auto suits it best, reaching for a dropdown much sooner than the info column does. Option names are hidden by default so the bar stays a single line (they're still announced to screen readers); switch them on if you'd rather see them.
Layout. On desktop everything sits on one line at the height of the product thumbnail — image, title, picker, add to cart — and nothing wraps, so the bar never grows. On mobile there isn't room for four things across, so the title sits above the picker and button. The thumbnail always shows the whole image rather than cropping it to fill.
Changing a variant in the bar drives the real picker, so the server resolves it exactly as it would on the page itself, and the bar follows any change made elsewhere — including the browser's back button.
Structured data
Every product page renders Product JSON-LD with per-variant offers server-side — pricing, availability and identifiers for rich results in Google. Nothing to configure. Barcode numbers you've entered on variants are included as proper GTINs where they're genuine 8, 12, 13 or 14-digit codes (an internal SKU typed into the barcode field is simply left out rather than sent wrong) — see SEO Features for where every value comes from.