Storefront widgets

Widgets are the parts of Cascade your shoppers see on your store. This page helps you choose them, install them and make them match your theme.

What each widget shows

  • Star rating: a product's average and review count, for product cards and the top of a product page.
  • Product reviews: the full list for a product, with the rating breakdown, a "Write a review" form, your replies, the "What reviewers say" summary and your review questions. Shoppers can sort by newest, highest, lowest or most helpful, click a bar in the breakdown to filter, and vote reviews helpful.
  • Reviews carousel: a sliding row of your best reviews, for a home page or landing page.
  • Review photos and videos: a wall of the photos and videos shoppers attached to reviews. Clicking one opens it full size with the review behind it.
  • Wishlist button and wishlists: a save button for a product page, and the customer's lists on their account page. See Wishlists.
  • Wishlist banner: a small card at the top of a product page that only appears when somebody arrives from a shared wishlist link, showing the shopper's own photo and note for that product. See Sharing and gift purchases.
  • Loyalty: the program panel, either on a rewards page or behind a floating button in the corner of every page, plus a "Earn 250 points with this purchase" badge for product pages. See Loyalty.

A product page with the star rating, the wishlist button, the points badge and the reviews widget

The reviews carousel on a home page

The loyalty panel on a rewards page, showing a member's balance, rewards and tier

Reviews written on your storefront wait in Reviews unless auto-publishing is on and the rating clears your threshold. They are never marked as verified buyers.

Install the widgets

  • On Shopify, open the Cascade app in your Shopify admin and turn on the Cascade blocks you want. The card that appears once an area is set up points you there, and each block has its own settings, such as "Only this product". See Widgets in your theme.
  • Anywhere else, your developer adds one script tag and a line per widget. Settings → Widget installation has the snippets ready with your key filled in, as HTML or React, one copy button per piece, and a Copy a prompt for an AI agent button that hands the whole job to a coding assistant. The same card appears at the top of an area's screens right after its setup. Or send them the For developers section below.

Appearance

Out of the box the widgets take their font and text color from your page and have a transparent background, so on most stores there is nothing to do. When there is:

  1. Open Settings → Widget appearance.

  2. Change the colors, font, corner radius and star color in the form. The live preview beside it shows the real widgets with sample data.

    The Widget appearance page with the color form and the live preview

  3. Save. Your storefront picks the change up within five minutes.

Check the accent color against the text on it: a light accent with light text is unreadable.

Per site

Appearance is set per store, so a second storefront can look like itself. Pick the store at the top of the page. Copy from another site copies the colors and custom CSS across in one go; it is a copy, not a link, so the two drift apart the moment you edit either again. See Running more than one store.

Custom CSS

Anything the form cannot express goes in the custom CSS box. If you would rather not write it, describe what you want in the box above the editor and choose Write it for me: you get the CSS, a sentence on what it changes and the preview showing it, and nothing is saved until you choose Accept. Ask for a look (colors, spacing, type, borders, corners), not a layout: it cannot reorder sections, hide the rating breakdown or remove the verified-buyer badge. There is a limit of 20 requests, refilling at one a minute.

Your CSS can only restyle the widgets, never the rest of your page. Anything Cascade has to remove is reported back to you when you save. The details are under Custom CSS rules below.

The Custom CSS card with a description, the editor and the Save CSS button

Putting an earlier version back

Every save of the custom CSS is kept. The last 50 are listed under Earlier versions with who saved them and what they asked for, and Restore puts one back. Colors are not versioned: use Reset beside a color to go back to the default.

Reply to a review

Open a review in Reviews and use the reply composer. A published reply appears under the review on your storefront, headed "Response from" the name you replied as. A draft reply is never shown to shoppers.

If something isn't working

  • A widget shows the default colors for a few minutes after a change: the stylesheet is cached for five minutes. Wait, then reload.
  • A rule in your custom CSS did nothing: check the message shown when you saved. Rules for the page outside the widgets, imports, fonts and outside URLs are removed.
  • Wishlists or the loyalty balance show the signed-out view on a custom store: the storefront isn't identifying the customer. Your developer needs Identifying the customer.
  • Stars are not showing in Google: see Structured data below, and give Google time to recrawl.

Next steps

For developers

The widgets in @cascade-commerce/widgets render ratings, reviews, wishlists and the loyalty program on any storefront. If your storefront is a React app, the same widgets are React components you can import and compose: see React widgets. Every endpoint they call is in the Widget API reference.

The script tag

Drop one script tag on the page and mark up the elements you want filled in:

<script
  src="https://cdn.example.com/cascade/embed.js"
  data-cascade-base-url="https://api.cascade.example.com"
  data-cascade-publishable-key="pk_live_..."
></script>

<div data-cascade-rating="classic-tee"></div>
<div data-cascade-reviews="classic-tee"></div>
<div data-cascade-reviews-carousel></div>
<div data-cascade-media-grid></div>
<div data-cascade-add-to-wishlist="classic-tee"></div>
<div data-cascade-wishlists></div>
<div data-cascade-wishlists-modal></div>
<div data-cascade-wishlist-banner="classic-tee"></div>
<div data-cascade-loyalty></div>
<div data-cascade-loyalty-launcher></div>
<div data-cascade-loyalty-points data-cascade-price="49.00"></div>

The value of each product attribute is the product slug. On Shopify that is the product handle: data-cascade-reviews="{{ product.handle }}".

Optional script tag attributes:

AttributeWhat it does
data-cascade-siteWhich of your sites this storefront is. Without it, your oldest site's appearance is used.
data-cascade-currencyISO currency code for money in the loyalty panel. Defaults to USD.
data-cascade-theme-versionPins the theme stylesheet to a version so it can be cached indefinitely.
data-cascade-loyalty-visitsSet to false to stop the loyalty widget reporting visits.
data-cascade-customer-email, data-cascade-timestamp, data-cascade-signatureIdentify the signed-in shopper. See Identifying the customer.

The review and rating widgets read public data, so the publishable key is all they need. The wishlist widgets need a signed-in shopper for their real work and keep a guest's saves in the browser until then. The loyalty widget renders for everybody and gains the member's own state once signed in.

The properties you can set

Everything the widgets draw resolves through these CSS custom properties. The appearance editor sets them, and you can set any of them from your own stylesheet.

PropertyWhat it colorsDefault
--cascade-fontThe font stack.inherit
--cascade-textBody text.inherit
--cascade-muted-textDates, counts, help lines.65% of the text color
--cascade-surfaceThe ground behind cards and panels.transparent
--cascade-surface-mutedRating bar tracks, thumbnails, the summary panel.A faint gray
--cascade-borderEvery hairline between rows, cards and controls.A light gray
--cascade-accentFilled buttons, the selected tab, progress fills.CanvasText
--cascade-accent-textText and glyphs on top of the accent.Canvas
--cascade-starA filled star, and the rating breakdown bars.#facc15
--cascade-dangerError messages, and a negative review theme.#b42318
--cascade-radiusCorner radius on cards, panels and controls.8px

Two more are derived rather than chosen, and are set from custom CSS only: --cascade-control-radius rounds buttons and inputs on their own (follows --cascade-radius), and --cascade-floating-surface is the opaque ground under the loyalty panel and the wishlist dialog.

How the theme reaches your storefront

The embed script requests the stylesheet before it renders anything, so the page never flashes default colors:

GET /widgets/theme.css?apiPublishableKey=pk_live_...&site=main-store

The URL is cached for five minutes. The appearance page also shows a versioned URL, which is immutable until you next change something: put the version on the script tag as data-cascade-theme-version, or link the versioned URL from your own <head> if you load the embed script late.

Custom CSS rules

The class names below are a supported API and will not be renamed without notice. Anything else in the markup is internal. A modifier is always a -- suffix on its block (.cascade-button--primary, .cascade-loyalty-tier--current).

WidgetClasses
Shared.cascade (on every widget's outermost element), .cascade-button, .cascade-button--primary, .cascade-button--link, .cascade-input, .cascade-muted, .cascade-heading, .cascade-notice
Rating.cascade-rating, .cascade-rating-stars, .cascade-rating-count
Reviews.cascade-reviews, .cascade-reviews-summary, .cascade-reviews-overall, .cascade-reviews-average, .cascade-reviews-breakdown, .cascade-reviews-breakdown-row, .cascade-reviews-breakdown-bar, .cascade-reviews-breakdown-fill, .cascade-reviews-toolbar, .cascade-reviews-list, .cascade-reviews-item, .cascade-reviews-item-header, .cascade-reviews-item-name, .cascade-reviews-item-title, .cascade-reviews-item-body, .cascade-reviews-item-media, .cascade-reviews-item-response, .cascade-reviews-empty, .cascade-reviews-ai-summary, .cascade-reviews-write, .cascade-reviews-write-toggle, .cascade-reviews-form, .cascade-reviews-form-field, .cascade-reviews-form-label, .cascade-reviews-form-stars, .cascade-reviews-form-star, .cascade-reviews-form-media, .cascade-reviews-form-thumb, .cascade-reviews-form-actions, .cascade-reviews-form-status, .cascade-reviews-question-summary, .cascade-reviews-question-summary-item, .cascade-reviews-question-summary-header, .cascade-reviews-question-summary-value, .cascade-reviews-question-track, .cascade-reviews-question-marker, .cascade-reviews-question-ends, .cascade-reviews-question-options, .cascade-reviews-question-option, .cascade-reviews-question-option-bar, .cascade-reviews-question-option-fill, .cascade-reviews-item-answers, .cascade-reviews-item-answer, .cascade-reviews-item-answer-label, .cascade-reviews-item-answer-value, .cascade-reviews-question, .cascade-reviews-question-scale, .cascade-reviews-question-step, .cascade-reviews-question-choices, .cascade-reviews-question-choice
Reviews carousel.cascade-reviews-carousel, .cascade-reviews-carousel-header, .cascade-reviews-carousel-nav, .cascade-reviews-carousel-arrow, .cascade-reviews-carousel-track, .cascade-reviews-carousel-card, .cascade-reviews-carousel-card-title, .cascade-reviews-carousel-card-body, .cascade-reviews-carousel-card-footer, .cascade-reviews-carousel-name, .cascade-reviews-carousel-verified, .cascade-reviews-carousel-product, .cascade-reviews-carousel-thumb, .cascade-reviews-carousel-empty
Media grid.cascade-media-grid, .cascade-media-grid-tiles, .cascade-media-grid-tile, .cascade-media-grid-tile-button, .cascade-media-grid-image, .cascade-media-grid-play, .cascade-media-grid-load-more, .cascade-media-grid-empty, .cascade-media-grid-caption, .cascade-media-grid-caption-title, .cascade-media-grid-caption-meta, .cascade-media-grid-verified, .cascade-media-grid-product-link
Loyalty.cascade-loyalty, .cascade-loyalty-header, .cascade-loyalty-balance, .cascade-loyalty-section, .cascade-loyalty-list, .cascade-loyalty-row, .cascade-loyalty-row-name, .cascade-loyalty-tiers, .cascade-loyalty-tier, .cascade-loyalty-progress, .cascade-loyalty-stamps, .cascade-loyalty-code, .cascade-loyalty-launcher, .cascade-loyalty-launcher-button, .cascade-loyalty-launcher-panel, .cascade-loyalty-points
Wishlists.cascade-wishlists, .cascade-wishlists-header, .cascade-wishlists-row, .cascade-wishlists-items, .cascade-wishlists-item, .cascade-wishlists-modal, .cascade-add-to-wishlist, .cascade-add-to-wishlist-popover, .cascade-wishlist-banner, .cascade-wishlist-banner-image, .cascade-wishlist-banner-attribution, .cascade-wishlist-banner-note, .cascade-wishlist-banner-link

Custom CSS is sanitized on save, and the sanitized version is what is stored:

  • Every rule is scoped under .cascade. A rule for body or :root is kept but can never match.
  • Only @media, @supports and @container survive. @import, @font-face and the rest are removed. Load a webfont from your own theme and point --cascade-font at it.
  • url() may only carry a data: URI. A declaration with any other url() is dropped.
  • Nested rules are dropped. Write each selector out in full.
  • 20,000 characters is the limit. Past that the save is refused.

CSS written by Write it for me goes through the same sanitizer and replaces the custom CSS in full rather than appending to it.

Reviews widget options

The write form asks for a rating, title, review, name and email, up to five photos or videos, and any review questions. A signed-in shopper's email is filled in and locked. Turn the form off to collect reviews only from the requests you send:

<div data-cascade-reviews="classic-tee" data-cascade-review-form="false"></div>

Helpful votes need no sign-in: the widget keeps a random token in local storage so a visitor can change their vote rather than vote twice. Votes are anonymous and only published reviews accept them.

Review questions

Questions set up on Settings → Reviews appear in three places: in the write form (scales as a row of steps, multiple choice as radios or checkboxes, required ones block sending until answered), above the list as a summary of published answers (a scale as a track with a marker at the average, a multiple choice as one bar per option; shares can exceed 100% where several options are allowed), and on each review card as that reviewer's answers. A question nobody has answered is left out. Archiving keeps existing answers; hiding a question or option removes it from the storefront while the answers stay in reports.

To keep the answers on the cards but drop the summary above the list:

<div data-cascade-reviews="classic-tee" data-cascade-question-summary="false"></div>

Review summaries

Once a product has at least five published reviews, the reviews widget shows a generated "What reviewers say" summary with up to five themes, refreshed at most once a day per product. It is kept out of the structured data. Regenerate, hide or revert it from the product page in the dashboard, or turn the feature off with "Summarize what reviewers say" on Settings → Reviews. See Review summaries.

<div data-cascade-reviews-carousel></div>
<div data-cascade-reviews-carousel="classic-tee"></div>

Store-wide is the default; each card then names and links to its product when Cascade knows the URL. A product slug narrows it to one product.

AttributeWhat it does
data-cascade-min-ratingLowest rating to show. Defaults to 4 store-wide, and to no floor when the carousel is one product's.
data-cascade-max-reviewsHow many reviews to load. Defaults to 10, and the maximum is 50.
data-cascade-headingThe heading above the cards. Defaults to "What shoppers say".

Cards are ordered by rating, highest first, and slide with the arrow buttons, by dragging, or with the arrow keys once focused. Nothing advances on its own. The carousel emits no structured data. On Shopify it is the Reviews carousel block, with the same settings plus an "Only this product" checkbox.

Media grid

<div data-cascade-media-grid></div>
<div data-cascade-media-grid="classic-tee"></div>
AttributeWhat it does
data-cascade-min-ratingLowest rating to show. There is no floor by default, so every published review counts.
data-cascade-max-itemsHow many tiles a page holds. Defaults to 12, and the maximum is 48.
data-cascade-headingThe heading above the grid. Defaults to "From our customers".

Tiles are ordered newest review first and a review's own photos stay together, so a page can come back a little over the number asked for. "Load more photos" appends the next page. Store-wide, the lightbox carries a "View product" link. Arrow keys walk the grid and Escape closes it. The grid emits no structured data. On Shopify it is the Review photos and videos block.

Structured data

Both review widgets can emit schema.org JSON-LD, which produces the star snippets in Google results: a Product node with an AggregateRating, and from the reviews widget the individual Review records too.

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Classic T-Shirt",
  "url": "https://shop.example.com/products/classic-tee",
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": 4.67,
    "reviewCount": 3,
    "bestRating": 5,
    "worstRating": 1
  },
  "review": [
    {
      "@type": "Review",
      "name": "Great shirt",
      "reviewBody": "Fits perfectly and the material is soft.",
      "datePublished": "2026-01-02",
      "author": { "@type": "Person", "name": "Alice J." },
      "reviewRating": { "@type": "Rating", "ratingValue": 5, "bestRating": 5, "worstRating": 1 }
    }
  ]
}

When the product has published questions and answers, the reviews widget also emits a QAPage node: one Question per published question, your answer as acceptedAnswer and shopper answers as suggestedAnswer. Questions with no published answer are left out. The generated instant answer is absent from both nodes.

WidgetEmits markup by defaultWhy
data-cascade-reviewsYesIt belongs on a product page, once per page.
data-cascade-ratingNoIt usually repeats across a collection or grid.

Override either with data-cascade-structured-data (or ="false"). In React the same controls are the structuredData and productUrl props on <ProductRatingWidget> and <ProductReviewsWidget>.

Placement rules:

  • Put the marked-up widget on the product's own page. Markup on a home page or blog post is ignored or flagged in Search Console.
  • One marked-up widget per page. With both widgets on a product page, let the reviews widget emit it.
  • Leave it off in collection and search grids.
  • If your theme already publishes an aggregateRating from another review app, turn Cascade's off rather than shipping two contradictory ratings.

The product URL comes from <link rel="canonical">, falling back to the current path without query or fragment; set data-cascade-product-url when neither is right (a quick-view drawer, for example). Nothing is emitted until the product has one published review. Only published reviews are included, reviewers appear as first name and last initial ("Anonymous" if unnamed), and reviews loaded through "Load more" are added as they appear.

Verify with Google's Rich Results Test using the URL tab: the markup only exists after the page's JavaScript runs.

Identifying the customer

Anything that belongs to one shopper (wishlists, loyalty balance) needs the storefront to prove who is signed in with a signature made on your server. Add three attributes to the script tag:

<script
  src="https://cdn.example.com/cascade/embed.js"
  data-cascade-base-url="https://api.cascade.example.com"
  data-cascade-publishable-key="pk_live_..."
  data-cascade-customer-email="ada@example.com"
  data-cascade-timestamp="1735689600"
  data-cascade-signature="48eb876e883be4490d05d06ea8b9fe870b481c1098cc7ef88553998690a1dd31"
></script>
AttributeValue
data-cascade-customer-emailThe signed-in customer's email address.
data-cascade-timestampUnix time in seconds when you made the signature.
data-cascade-signatureHex HMAC-SHA256 of the email and timestamp, keyed with your secret.

All three go together. For a signed-out shopper leave all three off: the wishlist widgets keep guest lists in the browser that are claimed on sign-in (see Before signing in), and the loyalty widget shows the signed-out catalog. A page with some but not all of them is refused by the wishlist widgets and treated as signed out by the loyalty widget.

The signing recipe

  1. Take the email exactly as sent in data-cascade-customer-email, including its case.
  2. Take the current Unix time in seconds, as a string.
  3. Concatenate the two, in that order, with nothing between them.
  4. HMAC-SHA256 the result, keyed with your widget signing secret, as lowercase hex.

A signature is good for 24 hours; sign on every page render anyway. The secret is under Settings → API keys in the widget signing secret card. There is one per organization, shared by every site. Keep it on your server and out of theme files, JavaScript bundles and version control: anyone holding it can sign as any of your customers.

Rotate secret on the same card takes effect immediately with no grace period: every storefront still signing with the old value fails until the new one is deployed. Rotations are recorded in the audit trail.

Node
import { createHmac } from "node:crypto"

const signingSecret = process.env.CASCADE_SIGNING_SECRET

export function cascadeCustomerAuth(email) {
  const timestamp = String(Math.floor(Date.now() / 1000))
  const signature = createHmac("sha256", signingSecret).update(`${email}${timestamp}`).digest("hex")

  return { email, timestamp, signature }
}

Test vector: secret wsk_demo_signing_secret, email test@example.com, timestamp 1735689600 gives 48eb876e883be4490d05d06ea8b9fe870b481c1098cc7ef88553998690a1dd31.

On Shopify there is nothing to do. The Cascade embed asks Cascade for the signature of whoever is signed in, on your store's own domain, and the secret never leaves Cascade. Do not sign in Liquid: the secret would have to live in a theme setting or metafield, both readable by anyone who can edit your theme. See Widgets in your theme.

Every widget call carrying a customer answers 401 with one of:

MessageWhat happened
Unknown publishable keyThe publishable key is wrong, or belongs to another organization.
Invalid timestampThe timestamp isn't a number. It has to be Unix seconds, not an ISO date.
Signature expiredThe timestamp is more than 24 hours from now. Check your server clock.
Invalid signatureThe digest doesn't match. Usually the email and timestamp were joined with a separator, the digest is base64 rather than hex, or the secret is stale.
Missing emailThe email was blank.
Missing signatureThe signature was blank.

Wishlists

<div data-cascade-add-to-wishlist="classic-tee"></div>
<div data-cascade-wishlists></div>
<div data-cascade-wishlists-modal></div>
<div data-cascade-wishlist-banner="classic-tee"></div>

A save button for a product page, the list manager for an account page, and the same manager behind a button. All three need the customer attributes above. See Wishlists for sharing, purchase tracking and the Shopify markup.

The last one is the banner a product page opens with when somebody arrives from a shared wishlist link (?cascadeWishlist=...&cascadeItem=...). It needs no customer attributes, reads the link's parameters itself, and renders nothing at all on an ordinary visit, so it is safe to place on every product page. In React it is <WishlistBannerWidget productSlug="..." />.

Loyalty

<div data-cascade-loyalty></div>
<div data-cascade-loyalty-launcher="Rewards" data-cascade-loyalty-position="bottom-left"></div>
AttributeWhat it renders
data-cascade-loyaltyThe panel, inline, for a rewards or account page. Takes no value.
data-cascade-loyalty-launcherA floating pill (bottom right by default) that opens the panel. Its value is the label.
data-cascade-loyalty-positionbottom-left moves the launcher.

Without a customer the panel shows the catalog: tiers, ways to earn, offers, stamp cards and what points buy. With the customer attributes it adds the member's balance, tier progress, stamp cards and held rewards with their codes. Joining, claiming and canceling a reward need a signed-in shopper.

Which sections appear is decided in the dashboard, always in the order tiers, ways to earn, offers, stamp cards, rewards. Ways to earn, offers and stamp cards follow the Sections switches on Loyalty programs → Settings; tiers appears whenever the program has any; rewards appears once the program's points are named. A switched-off section's rules are never sent to the browser.

Visits. The widget reports a visit when it loads for a signed-in shopper, which is what the "Visits the store" trigger uses. Visits are deduplicated per browser session and the rule's allowance is enforced by Cascade in your organization's time zone. Set data-cascade-loyalty-visits="false" on the script tag to report them yourself or not at all.

Points on the product page.

<div data-cascade-loyalty-points data-cascade-price="49.00"></div>
AttributeWhat it does
data-cascade-loyalty-pointsThe badge. Takes no value.
data-cascade-priceWhat the shopper would spend, in whole currency units rather than cents. Required.
data-cascade-currencyThe currency the price is in. Falls back to the script tag's currency.

The price is read once when the badge mounts, so re-render it on a variant change (or use the React component's price prop). A member reads "Earn 250 points with this purchase." with their tier multiplier applied; everyone else reads "Join to earn 250 points with this purchase." The badge renders nothing when there is no program, no live purchase rule, a paused program, no named points, or a price too small to earn; product-restricted rules are left out. On a page with the inline embed, the badge links to it. On Shopify it is the Points on product page block.

Referral links. A member's share link is your site's base URL with ?ref= and their code. The widget reads ref from the landing URL, remembers it, and sends it the first time it loads for a signed-in shopper, which attributes the referral and enrolls the friend with the "referred" join source. The code is forgotten once Cascade has answered. Nothing to wire up beyond having the widget on the landing page.

Links from loyalty emails go to your storefront: the Sites card on Settings → Loyalty takes a loyalty page URL per site, defaulting to the site's base URL. Every link carries #cascade-rewards; the launcher opens its panel on arrival and the inline embed scrolls into view.

Every endpoint behind these embeds is in the Widget API reference.