Skip to content

Subscriptions - selling plans

Two selectors. The purchase mode (one-time vs subscribe) and the subscription frequency are separate controls: change-purchase-option is the toggle (author two purchase-option children); change-selling-plan is the frequency picker shown only while subscribing (it clones one selling-plan-option per real plan). Both emit sf:selling-plan-change, which storesynk-product catches to set selectedSellingPlanId (null = one-time) - the value add-to-cart / buy-now send to checkout. Both hide via [ss-empty] when the variant has no sellingPlanAllocations.

  • Purpose: one-time-vs-subscribe toggle. Wires its purchase-option children: clicking the value="single" option clears the plan (one-time → sellingPlanId: null); the value="recurring" option applies the current, else last-chosen, else first plan. Reflects [ss-active] on the current mode’s option. It is NOT a template-clone container - it toggles state on author-provided children and captures no template (so no SSK-107).
  • Attributes:
    • default-option : "single" | "recurring" = "recurring" - the widget’s initial mode. Subscribe-first is the default: when a product subtree authors this widget, the initial state preselects recurring with the variant’s first selling plan, so the widget paints “Subscribe” active and change-selling-plan open/filled on first load (both client and SSR). Set default-option="single" to opt back to one-time-first.
  • Widget-scoped subscribe-first (important): the recurring default applies only to a storesynk-product subtree that contains a change-purchase-option. Products without the widget - collection/list cards, search results, bundle members - always start one-time (selectedSellingPlanId: null), so a card’s add-to-cart can never silently add a subscription.
  • Required ancestor / context: storesynk-product (consumes productContext).
  • Children: exactly two purchase-option elements - one value="single" and one value="recurring".
  • Events: emits sf:selling-plan-change (bubbles, composed; detail.sellingPlanId - a plan id for recurring, or null for one-time).
  • State it sets: [ss-active] on the current-mode purchase-option (the recurring option by default; the single option under default-option="single"), paired with aria-pressed on that option’s resolved control; [ss-empty] on itself when the selected variant has no selling plans (hide with change-purchase-option[ss-empty] { display:none }).
  • Accessibility (built in): each purchase-option is wired through the Clickable primitive - a nested native <button> is used as the control if you provide one, otherwise the bare pill is upgraded to role="button" + tabindex="0" + Enter/Space - with aria-pressed mirroring [ss-active] on that control, written deterministically every update. Identical to the in-cart change-line-purchase-option. The SSR fill stamps the same attributes in the same order, so the toggle is operable before hydration.
  • Purpose: one selectable purchase mode inside a change-purchase-option (or, in the cart, a change-line-purchase-option). A logic-free styleable element - no logic of its own; the enclosing toggle makes it a control.
  • Attributes:
    • value : "single" | "recurring" - which mode this option selects. Author this.
    • (Engine-set: [ss-active] on the current mode, plus role/tabindex/aria-pressed on the resolved control - the pill itself unless you nest a <button>.)*
  • Required ancestor / context: change-purchase-option or change-line-purchase-option (SSK-108).
  • Children: your label markup (e.g. “One-time purchase” / “Subscribe & save”) - give it visible text, which becomes the control’s accessible name. Style the active one via [ss-active].
  • Purpose: subscription frequency selector, shown only while subscribing. Captures its first selling-plan-option child as the template, removes it, and clones it once per real selling plan of the selected variant. Selecting one dispatches sf:selling-plan-change. Under the widget’s subscribe-first default (a change-purchase-option in the same product without default-option="single"), it renders open and filled on first paint - the variant’s first plan is preselected - rather than starting hidden.
  • Attributes:
    • labels : string = "" - optional comma-separated custom plan names, in plan order, overriding the Shopify plan names (e.g. labels="Every week, Every 2 weeks, Every month"). (The old one-time-label attribute no longer exists - the one-time choice moved to change-purchase-option; authoring one-time-label now trips SSK-201.)
  • Required ancestor / context: storesynk-product (consumes productContext; also storeContext to money-format each plan’s price).
  • Children: exactly one selling-plan-option template (rule SSK-107 if missing).
  • Events: emits sf:selling-plan-change (bubbles, composed; detail.sellingPlanId - the selected plan id).
  • State it sets: value (a real plan id) + [ss-active] on each clone, paired with aria-pressed on that clone’s resolved control; [ss-empty] on itself when the variant has no plans or while in one-time mode (selectedSellingPlanId == null) - hide with change-selling-plan[ss-empty] { display:none }.
  • Accessibility (built in): every clone is wired through the Clickable primitive - a nested native <button> is used as the control if the template provides one, otherwise the selling-plan-option itself is upgraded to role="button" + tabindex="0" + Enter/Space - with aria-pressed mirroring [ss-active]. Identical to the in-cart change-line-selling-plan. The SSR fill stamps the same attributes in the same order.
  • Purpose: one selectable subscription plan. The first one authored is the clone template. A logic-free styleable element - no logic of its own; the enclosing selector sets its value (a real selling-plan id - there is no synthetic one-time row here anymore), toggles [ss-active], and makes it a keyboard-operable control.
  • Attributes: none authored. Engine-set: value, [ss-active], plus role/tabindex/aria-pressed on the resolved control (the option itself unless the template nests a <button>).
  • Required ancestor / context: change-selling-plan or change-line-selling-plan (SSK-108).
  • Children: optionally show-selling-plan-name and/or show-selling-plan-price, plus any markup (a radio <input>, price text, etc.) - their text becomes the control’s accessible name. Style the selected one via [ss-active].
  • Purpose: invisible (display:contents) text holder inside a selling-plan-option; the engine writes the plan’s name (or the matching labels override) into its textContent.
  • Attributes: none. Required ancestor / context: selling-plan-option within change-selling-plan (SSK-108).
  • Events / state: none.
  • Purpose: invisible text holder inside a selling-plan-option; the engine writes the money-formatted price for that plan into its textContent.
  • Attributes: none. Required ancestor / context: selling-plan-option within change-selling-plan (SSK-108).
  • Events / state: none.

Imported from the Storesynk skill v0.1.0 (references/component-reference.md). To change this page, change it there.