Reference
Sections
What ships, and how each behaves.
| Section | React | What it does |
|---|---|---|
header | ✅ | Nav, mega menus, cart + menu triggers, localization |
announcement-bar | ✅ | Swiper slider with optional countdown |
slideshow | ✅ | Full-bleed Swiper hero |
featured-collection | ✅ | Product carousel with editable cards |
collection | ✅ | Collection grid — filtering, sorting, pagination |
main-product | ✅ | Variants, subscriptions, add to cart |
cart | ✅ | Main cart page |
cart-drawer | ✅ | Slide-out cart |
mobile-menu | ✅ | Slide-out nav |
footer | ❌ | Menu / text / newsletter blocks |
404, article, blog, collections, main-login, page, password, search | ❌ | Plain Liquid |
Section groups: header-group (header, announcement bar), footer-group
(footer), overlay-group (cart drawer, mobile menu).
Every React section receives the same payload shape, whichever one it is:
1export default function AnySection({ id, settings, blocks, ...resources }) {2 // `id` the section id — needed by useSectionState and useCarousel3 // `settings` keyed by their schema ids, so snake_case4 // `blocks` [{ id, type, settings, shopify_attributes }]5 // resources whatever the .liquid opted into with `with_product` etc.6}See Adding a section for the Liquid side.
Header
Navigation comes from the Shopify menu (Navigation in the admin), not blocks — merchants expect to manage it there, and the footer shares it. Blocks only add mega menu content on top. With no blocks you still get working dropdowns.
Each Mega menu block names the top-level item it opens under, matched on title, case-insensitively. Its own menu supplies the panel: top-level links become column headings, children become the links under them, plus an optional promo image.
Name an item that doesn't exist and nothing renders — which looks broken rather than mistyped, so in the editor it logs the titles that are available.
Announcement bar
Swiper-backed, one per header group. Each message block can carry a countdown with its own deadline. Selecting a block in the editor pins that slide.
Featured collection
Pick a collection; the section shows its products as a carousel. One source, deliberately — with no collection there is nothing to show, and in the theme editor the section says so rather than rendering an empty space.
The card is built from blocks: Product Card: Image / Title / Vendor / Price / Options / Button. Reorder them and the card reorders. The preset ships Image + Title + Price.
Product Card: Options puts variant pickers on the card. Each option — Size, Colour — gets its own control, so a product with several reads as several choices rather than one list of combinations.
| Setting | |
|---|---|
| Style | Buttons or dropdowns |
| Show option names | The Size / Colour labels above each control |
| Hide sold-out values | Off by default — see below |
Sold-out values stay visible and struck through by default, because a value that
silently vanishes looks like a bug. A combination that cannot exist is a
different thing and is always disabled: with Small selected, a Green that
only comes in Large is not a choice.
Two things follow from adding this block:
- The card image tracks the selection. Picking a variant that has its own photo swaps the card image to it. Variants often share the product image, so nothing changing is the normal case rather than a failure.
- Quick add starts working for multi-variant products, because there is now an unambiguous variant to add.
It costs payload. Variant pickers need the variant list, and variants are the expensive part of a product — roughly 1.5 KB each, inline in the HTML, multiplied by every card in the carousel. The section includes them only when a Product Card: Options block is present, so a carousel without one is exactly as light as it was.
Product Card: Button does quick add through the shared cart store — opens the drawer, updates the header count. Without an Options block it only quick-adds products that have nothing to choose; anything with options links to the product page instead, because silently adding an arbitrary variant is worse than one more click.
Why not native theme blocks (
{% content_for 'blocks' %})? They render Liquid server-side, and React'screateRootreplaces everything inside its mount element. The two can't both own the card. Native blocks are still the right call for a Liquid-rendered section.
Collection grid
React grid with storefront filtering, sorting and three paging styles. Lives on the collection template.
Filtering never rebuilds a query. Every control navigates to a URL Shopify
generated (url_to_add / url_to_remove), and the section re-fetches itself
from that URL via ?section_id= — the Section Rendering API. Liquid stays the
only thing that knows how to query a collection, and the URL stays real:
shareable, bookmarkable, correct on refresh. If a fetch fails it falls back to a
normal navigation, so the shopper still gets there.
| Setting | |
|---|---|
| Columns | Separate desktop and mobile counts, plus grid gap |
| Filters | Sidebar or drawer — sidebar becomes a drawer below md either way |
| Sorting | Shopify's own sort list, so labels stay translated |
| Pagination | Numbered pages, Load more, or Infinite scroll |
The card is built from blocks, the same set Featured collection uses: Product Card: Image / Title / Vendor / Price / Options / Button. Reorder them and every card in the grid reorders. Add none and the card falls back to image, title and price.
Adding Product Card: Options includes each product's variants in the page. That costs more here than in a carousel — a grid is a whole page of products rather than a handful — so it is worth being deliberate about. See Featured collection for what the block does.
Promotion blocks drop a tile into the grid at a 1-based position, with column and row span. A position past the last product appends rather than disappears — otherwise a filter that shrinks the results would silently swallow it.
Filters come from Shopify's Search & Discovery app. No filters configured there means an empty array, which the UI treats as normal rather than broken.
Values Shopify reports with a count of 0 render disabled rather than hidden,
so the list doesn't jump around and the shopper can see why a combination is
unavailable. Infinite scroll still renders a Load more button underneath — it's
the fallback when IntersectionObserver never fires, and some people would
rather ask.
Main product
Product-template only, and not addable — it has no preset, because it's the
template's main section. Its default blocks live in templates/product.json.
The right column is built from blocks; the gallery isn't one, since it's the other half of the layout rather than a row in a stack.
| Block | |
|---|---|
| Title | Optional override, defaults to the product title |
| Vendor | |
| Price | Compare-at, subscription price, per-delivery for prepaid plans |
| Product options | Buttons or dropdown; sold-out values struck through |
| Store availability | Per-location stock — needs the API |
| Stock level | Low-stock threshold, exact count or just "Low stock" |
| Quantity | Respects the variant's min / max / increment |
| Buy button | Custom label, goes through the shared cart store |
| Description | The product description |
| Text | Free rich text (repeatable) |
| Accordion | Heading + rich text, collapsible (repeatable) |
Variant, quantity and plan state lives in the section, not the blocks — the price block and the buy button have to agree on which variant is selected, and they're siblings.
Two judgement calls in the stock block: untracked inventory never reports
"0 left" (inventory_quantity is meaningless when the shop isn't tracking, so
it says "In stock"), and available-but-zero reads as backorder rather than
sold out, because that's a shop allowing oversell.
Slideshow
Swiper-backed. Per-slide text position (9-way grid), overlay colour and opacity. Section-level height, transition, speed, loop, autoplay, arrows, pagination, keyboard.
Three things are enforced in code rather than trusted to the setting, because each would otherwise be a control that silently does nothing:
- Loop turns itself off when there aren't more slides than fit the view.
- Fade forces one slide per view — Swiper requires it.
- Autoplay is skipped under
prefers-reduced-motion, and pauses while a slide is selected in the editor.
Cart
Both the main cart page and the drawer read the same store, so a change in one is reflected in the other. Both can show a free-shipping progress bar.
Footer
Menu, Text and Newsletter blocks in a responsive grid. Allowed in the footer group only, once.
This one is deliberately Liquid. The newsletter needs {% form 'customer' %},
which emits the hidden fields Shopify's contact endpoint expects and exposes
form.posted_successfully? afterwards. React could post the same fields but
couldn't read the result — the form object only exists inside the tag. Bonus: the
footer works without JavaScript, and footer links stay in server-rendered HTML.