The basics
This page covers building a template set's markup from scratch, for when duplicating and restyling a built-in one (see Theming & CSS vars) isn't enough.
Files in a template set
| File | Role |
|---|---|
layout.html |
The outer shell — the results container, and optionally a search input, a close button, filters panel, and other chrome around them. Rendered once when the widget initialises. |
card.html |
Rendered once per product. Its output is repeated for every hit in the results grid. |
filter_term.html |
Rendered once per facet with discrete values (e.g. Brand, Category) — see Filters. |
filter_range.html |
Rendered once per numeric range facet (e.g. Price). |
styles.css |
The CSS for all of the above. Write selectors against whatever class names your own markup uses. |
A minimal layout.html
<div class="my-widget" data-ss-root data-ss-anchor>
<div data-ss-grid></div>
</div>
data-ss-root— required on every template set. Marks the widget's root element; the panel's colour settings are injected as CSS custom properties scoped to it.data-ss-anchor— optional. Present renders the widget anchored, inline below the search input; absent renders it as an overlay. See Basic Concepts for what that means. This is the only thing that controls it — there's no separate layout setting anywhere else.data-ss-grid— required. This is where each product'scard.htmloutput gets injected, one per hit.
That's the entire minimum needed for a working, if bare-bones, widget. Everything else — a rendered search input, a close button, filters, voice search, recent or popular searches — is optional markup you add on top, using the data-ss-* hooks the widget looks for. See Supporting the Design Switches for the ones tied to a toggle in the panel.
A minimal card.html
<a href="{{link}}" class="my-card">
<img src="{{image}}" alt="{{title}}">
<div class="my-card-title">{{title}}</div>
{{#price_regular}}<span>{{price_regular}}</span>{{/price_regular}}
</a>
card.html is rendered once per product, with the product's fields available directly as {{field}} — see Available fields below for the full list. For the conditional syntax ({{#field}}, {{^field}}...) see Advanced conditionals.
Available fields
Any field below can be printed as a value with {{field}} inside card.html (and filter_term.html/filter_range.html, where relevant) — this isn't just about conditionals, plain interpolation works the same way.
From the feed
Every field of the connected feed is available by its internal name — the structural fields every feed has (id, title, link, image, price, sku, sale_price, brand, description, categories, disable_add_to_cart...) plus any custom field you added yourself. See SoloSearch format for the full structural list, and Adding custom fields for your own.
This also includes effective_price — sale_price when the product is on offer, price otherwise. It's computed once when the feed is parsed, not by the widget, which is why it's also the field used elsewhere in the panel for price sorting, range filters, and value-based boosts. See Calculated fields.
Computed by the widget
A handful of fields don't come from the feed at all — the widget computes them at render time, purely for display:
| Field | Value |
|---|---|
price_regular |
price, formatted with the product's currency — only set when the product is not on offer. |
price_sale |
sale_price, formatted — only set when the product is on offer. |
price_original |
price, formatted — the "was" price meant to be shown struck through next to price_sale. Only set when the product is on offer. |
price_raw |
price as a plain number, with no currency formatting — for templates that want to format it themselves. |
sale_price_raw |
sale_price as a plain number, with no currency formatting. |
discount_percent |
The discount as a rounded string like -20% — empty when there's no offer. |
price_regular and price_sale/price_original are mutually exclusive by design — a product is either on offer or it isn't — which is why the built-in card templates pair them with conditionals instead of printing both:
{{#price_sale}}<span class="ss-price-original">{{price_original}}</span><span class="ss-price-sale">{{price_sale}}</span>{{/price_sale}}
{{#price_regular}}<span class="ss-price">{{price_regular}}</span>{{/price_regular}}
Boolean display toggles like show_price or show_brand are a separate thing — those come from the panel's Display options, not from product data. See Supporting the Design Switches.
A minimal styles.css
.my-widget { position: fixed; background: #fff; border: 1px solid #ddd; }
.my-card { display: flex; gap: 8px; padding: 8px; text-decoration: none; color: inherit; }
Nothing special here — regular CSS against the class names you chose above. See Theming & CSS vars for the --ss-* custom properties the panel's colour pickers write to, and how to hook your own stylesheet into them.
Next steps
- Supporting the Design Switches — react to the panel's Display options toggles
- Advanced conditionals — the full conditional syntax, including how to condition on your own custom feed fields