Skip to content

Converting existing HTML

Turn a storefront design you already have into a live Storesynk storefront.

Keep your elements and CSS classes; only change tag names and add wrappers. The engine targets tag names and ss-* attributes, never class names. This guide uses the plain HTML path; in Astro, Next.js, or React SSR the tags are identical.

<body>
<storesynk-store domain="your-store.myshopify.com" token="your-public-storefront-token">
<!-- your existing storefront markup, converted below -->
</storesynk-store>
<script type="module" src="https://cdn.storesynk.io/elements/0.1/storesynk.js"></script>
</body>

Exactly one store per page. A component outside its provider ancestor renders nothing, silently. See the context model.

For a single product page, wrap the markup in <storesynk-product handle="…"> (or product-id="…", not both). For a repeating grid, the card template carries no handle; see step 5.

Before:

<div class="product">
<img class="product__img" src="/img/snowboard.jpg" alt="The Classic Snowboard" />
<h1 class="product__title">The Classic Snowboard</h1>
<p class="product__price">$699.95</p>
<button class="btn btn--primary">Add to cart</button>
</div>

After:

<storesynk-product handle="classic-snowboard" class="product">
<show-image main class="product__img"><img alt="" /></show-image>
<h1 class="product__title"><show-title></show-title></h1>
<p class="product__price"><show-price></show-price></p>
<add-to-cart><button class="btn btn--primary" type="button">Add to cart</button></add-to-cart>
</storesynk-product>

The hardcoded $699.95 is deleted, not kept as a fallback. Money always renders through a displayer so it re-formats on market and currency changes.

Your static nodeStoresynk tag
Product title text<show-title>
Price text<show-price>; strikethrough price → <show-compare-price> (hides itself when not discounted)
Vendor / brand line<show-vendor>
Description block<show-description> (renders the product’s HTML description)
Main image <img>keep the <img>, wrap it: <show-image main class="THEIRS"><img alt="" /></show-image>, or <show-media main> for responsive srcset, video, and 3D
“Sale” badge<show-sale-badge format="percentage" class="THEIRS"> (shows only when on sale)
A custom field (care instructions, specs)<show-metafield key="care"> (namespace defaults to custom); wrap label + value in <metafield-wrapper> to hide the block when absent
Add-to-cart <button>wrap it: <add-to-cart><button class="THEIRS">Add to cart</button></add-to-cart>; text-out-of-stock / text-unavailable customize the labels
“Buy now” buttonsame pattern with <buy-now>
Link to the product page<product-link> around your <a> (stamps href="/products/{handle}")

4. Swap option pickers and the quantity stepper

Section titled “4. Swap option pickers and the quantity stepper”

Reuse your pill, radio, or select markup as the per-value template inside <change-option>. The first <option-value> (or first <option> in a <select>) is cloned once per value:

<change-option name="Size" class="sizes">
<show-option-label class="sizes__label"></show-option-label>
<div class="sizes__values">
<option-value class="sizes__pill"><show-option-title></show-option-title></option-value>
</div>
</change-option>

Add <show-option-swatch type="color"> inside the option-value for colour swatches; <show-active-option-title> prints the selected value. name="…" is for a single known product only. On any multi-product route use group="1"/"2"/"3"; a name that matches nothing self-hides and the shopper buys the default variant.

For quantity, wrap your stepper in <change-quantity min="1"> and rename the parts to <decrease-quantity> / <input-quantity> / <increase-quantity>.

5. Repeating markup: keep exactly one template

Section titled “5. Repeating markup: keep exactly one template”

Every repeating container keeps its first child as a template, removes it, and clones it per item. Delete your N hard-coded cards down to one:

<storesynk-list collection="best-sellers" limit="8" class="grid">
<!-- ONE card template - cloned per product; note: no handle on the product -->
<storesynk-product class="card">
<product-link>
<a>
<show-image main class="card__img"><img alt="" /></show-image>
<span class="card__title"><show-title></show-title></span>
</a>
</product-link>
<show-price class="card__price"></show-price>
<add-to-cart><button type="button">Add to cart</button></add-to-cart>
</storesynk-product>
</storesynk-list>

The same rule applies to every clone container: search-result-list, predictive-search (an <input> and one item template), cart-line-list, product-list inside <storesynk-collection>, filter-list, change-filter, and active-filter-list. A faceted collection page maps onto <storesynk-collection> the same way.

  • Cart icon count → <show-cart-count>; the open button → <open-cart class="THEIRS">.
  • The drawer → <storesynk-cart> with a <cart-line-list> whose first child is one <cart-line>.
  • Total → <show-cart-total>; the checkout button → <checkout-link><a class="THEIRS">Checkout</a></checkout-link>.
<storesynk-cart class="drawer">
<div class="drawer__head">
<h2>Your cart</h2>
<close-cart aria-label="Close cart">×</close-cart>
</div>
<cart-line-list class="drawer__lines">
<cart-line class="line">
<show-line-image class="line__img"><img alt="" /></show-line-image>
<div class="line__info">
<show-line-title class="line__title"></show-line-title>
<show-line-variant class="line__variant"></show-line-variant>
<show-line-price class="line__price"></show-line-price>
</div>
<div class="line__qty">
<decrease-line-quantity>−</decrease-line-quantity>
<show-line-quantity></show-line-quantity>
<increase-line-quantity>+</increase-line-quantity>
</div>
<remove-cart-line class="line__remove">Remove</remove-cart-line>
</cart-line>
</cart-line-list>
<div class="drawer__foot">
<p class="drawer__total">Total: <show-cart-total></show-cart-total></p>
<checkout-link><a class="btn btn--primary">Checkout</a></checkout-link>
</div>
</storesynk-cart>

With a <storesynk-cart> on the page, <open-cart> opens the drawer; without one it navigates to /cart (see cart page). For an empty message, style a plain element off storesynk-cart[ss-empty]. Never repurpose <clear-cart>, which wipes the cart on click.

Ancestor rules judge the rendered tree, not source files. A fragment validated alone reports missing-ancestor errors even though the provider lives in the layout. Don’t add a second <storesynk-store> to fix that; validate the composed result.

Validate each converted page through your connected AI tool before testing in a browser. Fix Errors and Warnings. See validator rules.

  • Leaving N hard-coded cards in a grid. Only the first child is used; the rest is dead markup.
  • A displayer outside its provider renders nothing, silently. See nothing renders.
  • A second <storesynk-store> or nested <storesynk-product>. Bindings double up or point at the wrong product.
  • Hardcoded option name= on a shared template ships the “wrong item” bug. Use group="1"/"2"/"3".
  • Old tag names. value-optionoption-value, show-labelshow-option-title, show-swatchshow-option-swatch, empty-cartclear-cart. Old names stay inert.
  • Self-closing custom elements. <show-title/> is invalid HTML.
  • Raw money strings. Every price goes through a displayer.
  • Credentials injected after the bundle loads. The credential script must run before the bundle <script>.
  • Unlabeled wrapped inputs. apply-discount, cart-note, search-input, and change-price wrap your fields; give each an aria-label or <label>.