The engine sets and removes these attributes at runtime to reflect state. All state
The engine sets and removes these attributes at runtime to reflect state. All state
attributes are ss--prefixed - the prefix
guarantees no collision with native HTML or other tools’ attributes. There are no state
classes anymore: everything is an ss- attribute, so target them in CSS as attribute
selectors (e.g. add-to-cart[ss-out-of-stock] { … }, .thumb[ss-active] { … }). Authoring
them yourself is at best ignored and at worst breaks cloning - treat them as read-only hooks.
ss--prefixed state attributes set on the component itself:
ss-loading- onadd-to-cart,buy-now,add-bundle-to-cart,add-volume-to-cart(during the action) and onstoresynk-list,search-result-list,predictive-search,storesynk-bundle,storesynk-volume-discount,storesynk-mix-match,storesynk-addons(during fetch); onadd-mix-match-to-cartduring its multi-line add; onchange-country/change-languagewhile a locale switch is in flight (paired witharia-busy="true"and a disabled child<select>); and on acart-linewhile one of its own line mutations (increase-line-quantity/decrease-line-quantity/remove-cart-line, and the in-cart subscription controlschange-line-purchase-option/change-line-selling-plan) is in flight - the initiating action stampsss-loadingon the enclosingcart-line, setsaria-disabled="true"on that line’s controls, and ignores further clicks until the transport settles (both cleared on completion). Stylecart-line[ss-loading](e.g. dim it) for visible feedback.ss-out-of-stock,ss-unavailable- onadd-to-cart,buy-now,add-bundle-to-cart, andadd-volume-to-cart(mutually exclusive; on the bundle buttonss-unavailable= some member’s selection resolved to no variant,ss-out-of-stock= every member has a variant but at least one is unpurchasable.add-volume-to-carttracks the page’s selected variant, exactly likeadd-to-cart).ss-empty- on any product displayer with an empty/null value (paired withhidden); onstoresynk-list/search-result-listwhen there are no results; onshow-metafield/metafield-wrapperwhen the metafield is absent; onchange-purchase-optionwhen the variant has no selling plans; onchange-selling-planwhen there are no plans or while in one-time mode; on their cart counterpartschange-line-purchase-option(the line’s merchandise offers no selling plans) andchange-line-selling-plan(no plans or the line is one-time); and onchange-country/change-language(paired withhidden) on a single-market shop where there is ≤1 option to pick. Also onstoresynk-bundlewhen the product has no live bundles (or no namespace is configured); onshow-bundle-original-total/show-bundle-savings/show-bundle-percentageat zero savings; and onshow-member-quantity/show-member-subtotal(paired withhidden) when the member’s quantity is 1 / the subtotal is not computable. Also onstoresynk-volume-discountwhen the product has no live volume discount (or no namespace is configured); and (paired withhidden) onshow-tier-label/show-tier-badgewhen the tier has no label/badge, and onshow-tier-original-priceon rows with no savings (nothing to strike through). Also onstoresynk-mix-matchwhen the product has no live mix-match bundles (or no namespace is configured); on the mix-match money/hint displayers with nothing to show (empty selection, no savings, no next tier); onmix-match-selection-listwhile nothing is picked; and (paired withhidden) onshow-item-quantity/show-pick-quantity/show-selection-overflow/show-section-countat their zero states. Also onstoresynk-addonswhen no live offer applies to the product (or no namespace is configured); and (paired withhidden) onshow-addons-subtitlewhen the merchant left it blank, onshow-addon-pricewhile the row card resolves no variant, and onshow-addon-original-pricewhen the offer takes nothing off the price (none / free-shipping / 0%).ss-on-sale- onshow-sale-badgewhen the selected variant is discounted (no class).ss-open- onstoresynk-cart(drawer open) andpredictive-search(dropdown open).ss-active- on the currently-selectedoption-value, the activeshow-thumbnail, the activeselling-plan-optionclone and the current-modepurchase-option- in the PDP selectors and their in-cart counterparts alike, each paired witharia-pressed="true|false"on that option’s resolved control (client and SSR) - the selectedlocale-valuepill (template mode; paired witharia-checked="true"), and the selectedvolume-tierrow (also paired witharia-checked="true"- the row is an ARIA radio).ss-unavailable="nonexistent"/ss-unavailable="out-of-stock"- on eachoption-valueto mark combinations that don’t exist or are sold out.ss-adopt- on a host whose children the server already rendered: a list host (e.g.storesynk-list, via a server renderer / the<StoresynkList>wrapper), the bundle widget’sstoresynk-bundle/bundle-member-list, the volume-discount widget’sstoresynk-volume-discount/volume-tier-list, and the mix-match widget’sstoresynk-mix-match/mix-match-section-list/mix-match-item-list/mix-match-selection-list, and the product-addons widget’sstoresynk-addons/addon-list. The client keeps the SSR-rendered children and hydrates them in place instead of clearing them. SSR-emitted marker, never hand-authored (see Server-side rendering & adoption).ss-variants-truncated- on a bundle member-card (or mix-match pool-card, or add-ons row-card)storesynk-productwhose variant list hit the query cap (100 variants): the card degrades to default-variant mode (itschange-optionselectors render nothing). CSS hook for a “choose options on the product page” affordance (e.g.storesynk-product[ss-variants-truncated] .member-truncated { display: block }).ss-bundle-key- SERVER-set on each SSR-renderedbundle-offerclone (the bundle’s$app:bundlemetaobject gid) so the client can detect that the live bundle set drifted since the HTML was built (date windows open/close). Never authored.ss-volume-key- SERVER-set on the SSR-renderedstoresynk-volume-discountclone (the winning$app:volume_discountmetaobject gid) - the volume analog ofss-bundle-key, so the client can detect that the live discount drifted since the HTML was built (date windows open/close). Never authored.ss-mix-match-key: SERVER-set on each SSR-renderedmix-match-offerclone (the bundle’s$app:mix_matchmetaobject gid), the mix-match analog ofss-bundle-key, so the client can detect that the live set drifted since the HTML was built. Never authored.ss-addons-key: SERVER-set on the SSR-renderedstoresynk-addonsoffer clone (the winning$app:product_addonsmetaobject gid) - the product-addons analog ofss-mix-match-key, so the client can detect that the winning offer drifted since the HTML was built (a date window opened/closed, the overlap rule re-decided). Never authored.ss-selected: engine-set on a mix-match pool card (the item-list clone) while the card’s currently selected variant is in the customer’s selection - the CSS hook that swaps the card’s Add row for its quantity stepper (see the mix-match recipes below) - and on an add-ons row card (the addon-list clone) while its row is ticked. Never authored.ss-open: also on amix-match-sectionwhose accordion panel is open (alongside the drawer and predictive-search uses listed above).ss-met: engine-set on amix-match-sectionwhoseminrequirement is met (the checkmark / complete-state CSS hook). Never authored.ss-incomplete: engine-set onadd-mix-match-to-cartuntil every section’s count is inside its [min, max] (paired witharia-disabled="true"on the focusable control). Never authored.ss-max: engine-set onadd-mix-match-item/increase-mix-match-itemwhile the item’s section is at itsmaxcapacity (adding is a no-op; the control shows why). Never authored.ss-logged-in- set on<storesynk-store>(client and SSR) while a customer session is logged in - the CSS hook for auth-gated UI (e.g.storesynk-store[ss-logged-in] log-in { display: none }/storesynk-store:not([ss-logged-in]) log-out { display: none }). Driven by the customer-accounts module; never authored.ss-member- set on<storesynk-customer-pricing>while it renders the member preview (a logged-in customer whose tags match the rule). CSS hook for member-vs-prompt styling. Never authored.ss-customer-pricing-key- SERVER-set on the SSR-renderedstoresynk-customer-pricingclone (the winning$app:customer_pricingmetaobject gid) - the customer-pricing analog ofss-volume-key, so the client can detect that the live rule drifted since the HTML was built. Never authored.
Non-ss- engine-set attributes:
hidden- set (native) on product displayers when their value is empty (alongsidess-empty).aria-disabled="true"- set on the focusable control ofadd-to-cart/buy-now(the nested<button>/<a>, or the element itself if none) when there is no purchasable variant; also on unavailableoption-valueradios.aria-currentmarks the activeshow-thumbnail.value- on eachoption-value(the option value name), on eachselling-plan-optionclone (a real selling-plan id - there is no synthetic one-time value anymore), and on eachlocale-valuepill (the country/language ISO code the picker dispatches on select).index- onshow-thumbnailclones (and assigned to the template instance during expansion). Authoringindexon ashow-thumbnailtemplate is rule SSK-303.
Always-allowed global/standard attributes (never flagged): class, id, style,
slot, title, role, tabindex, hidden, data-*, aria-*.
SSR adoption payload markers (server-embedded, not authored by hand - see Server-side
rendering & adoption below): the data-storesynk-product / data-storesynk-shop attributes
mark an inline <script type="application/json"> payload placed as a direct child of
storesynk-product / storesynk-store. They are data-* markers on a native <script>, so
they’re always allowed. When a server renderer fills the markup, it writes the same state
attributes into the built HTML (e.g. ss-empty on an empty displayer, ss-on-sale on a
discounted show-sale-badge) - again read-only hooks, not something you author.
Centralized config block (data-storesynk-config) - the one author-facing inline JSON,
unlike the two payload markers above. A <script type="application/json" data-storesynk-config>
as a direct child of <storesynk-store> carries the shared StoresynkConfig (domain,
token, country, language, persistLocale, the customer* fields, and the nested pixels
{ enabled, client[], server[], relayEndpoint, requireConsent } / cart
{ openOnAdd, closeOnOutside, noteDebounce } groups - no pixels.exclude; the app namespace is
not here). The store reads it (_readConfigPayload → _applyConfigDefaults) and reflects each
key onto its own attributes only where the attribute is absent, so a tag attribute always wins
(precedence: tag attribute > config block > default; a boolean presence attribute can only turn
a config default on). It is an inert data block - there is no window.storesynkConfig /
configureStoresynk global in @storesynk/elements (removed); the no-build surface hand-writes the
block and the Astro/Next SSR wrappers emit its pixels/cart groups. Also a data-* marker on a
native <script>, so it’s always allowed. Integrators typing the block import the
StoresynkConfig / StoresynkPixelsConfig / StoresynkCartConfig types from
@storesynk/core - @storesynk/elements’ published type surface is deliberately narrow
(the tag union + HTMLElementTagNameMap; see Typing the tags below) and exports no values.
Imported from the Storesynk skill v0.1.0 (references/component-reference.md). To change this page, change it there.