Static IDs in Shopify Sections Break When a Section Is Used Twice — Here's Why
A section that works perfectly when added once to a page breaks in subtle ways when a merchant adds it twice via the Theme Editor — modal dialogs target the wrong element, JavaScript finds only the first instance, anchor links scroll to the wrong section. The root cause: hardcoded HTML IDs. When the same section is rendered twice, you get two elements with identical IDs. Shopify provides the fix built-in: section.id, a guaranteed-unique identifier for every section instance.
Why duplicate IDs cause invisible bugs
HTML requires that every ID on a page is unique. When two sections with the same hardcoded ID render on the same page, document.getElementById() returns only the first match, querySelector() returns only the first match, anchor links scroll to the first occurrence only, and aria-controls and aria-labelledby point to the wrong element. These bugs are intermittent — they only appear when merchants duplicate a section — making them hard to reproduce during development but common in production.
What section.id provides
Every Shopify section instance has a unique section.id assigned by Shopify — a short alphanumeric string like 's1a2b3c4' that is guaranteed unique across all section instances on the page. Use it to construct unique IDs: id="section-{{ section.id }}", id="modal-{{ section.id }}", id="carousel-{{ section.id }}". These IDs are stable — the same section instance always gets the same section.id — so JavaScript, CSS, and aria attributes targeting them work reliably.
Scoping JavaScript to the section container
When you switch to section.id-based IDs, scope JavaScript queries to the section container rather than using document-level selectors. Shopify wraps every section in a div with id="shopify-section-{{ section.id }}". Use this as your root: const section = document.querySelector('#shopify-section-{{ section.id }}'); const modal = section.querySelector('[role="dialog"]');. This guarantees JavaScript operates on the correct section instance even when the same section type appears multiple times.
Using section.id in blocks and passing to JavaScript
For block-level elements, combine both IDs: id="block-{{ section.id }}-{{ block.id }}". Pass IDs to JavaScript via data attributes rather than inline scripts: data-section-id="{{ section.id }}". JavaScript can read: const sectionId = element.dataset.sectionId; const container = document.querySelector('#shopify-section-' + sectionId);. This keeps JavaScript clean and decoupled from Liquid template syntax.
The fix: before and after
// CODE_COMPARISON
Frequently asked questions
- When exactly does the duplicate ID bug appear?
The bug appears whenever a merchant adds the same section type more than once to a page in the Theme Editor. This is common with sections like featured products, testimonials, image banners, and any section marked as repeatable. If your section is structurally unique per page (like a header or footer), duplicate IDs are technically not a problem — but using section.id is still best practice.
- Does Shopify Theme Check warn about hardcoded IDs?
Theme Check does not have a rule that detects static IDs inside section files. Syphio flags this as an architecture issue when it detects literal id= attributes in section context. Manual code review and a team convention to always use section.id are the most reliable prevention methods.
- What is the difference between section.id and block.id?
section.id is unique per section instance on the page. block.id is unique per block within a given section. For element IDs inside blocks, use both: id="item-{{ section.id }}-{{ block.id }}". This guarantees uniqueness across all section instances and all block positions simultaneously.
// SCAN_YOUR_CODE
Does your theme have this bug?
Paste your code. Syphio automatically detects and fixes this error and hundreds of others — in seconds.
Validate my Liquid →