Storeware Docs

Theme kit

The stylesheet a theme's bands are drawn with, and the data a renderer fills before it draws them.

@storeware/theme-kit is the other half of the theme schema. The schema says what a theme may be. The kit is what draws one: the section stylesheet, and the shape of the data a renderer fills for a page. Use it to build a preview, a screenshot service or a renderer of your own.

It is Apache-2.0 licensed, and the source is at github.com/storeware-ai/theme-kit. It depends on nothing, not even the schema, though a renderer will use both.

The stylesheet

import '@storeware/theme-kit/sections.css';

The sheet holds the shape every shop has in common: grids, containers, the rhythm between bands, aspect ratios, and the rules behind every preset a section offers. What makes one shop look unlike another is read from custom properties, so a theme restyles a shop without touching a rule in the sheet.

Two classes are the structure:

  • .sw-section is the wrapper one band gets.
  • .sw-band is the container inside it.

Some settings become attributes on the wrapper. presetAttributes in the schema turns "preset": "image_scrim" into data-sw-layout and data-sw-emphasis, and the rules for those live in the sheet. shapeAttributes does the same for the shape of a band's pictures, as data-sw-shape. A setting left at its default adds no attribute, so the band's markup does not change.

.sw-section:empty hides the wrapper of a band that drew nothing, so an unfinished template reads as a shorter page.

The sheet draws no header, footer, navigation, basket or checkout. Those belong to one shop's application, not to a theme.

Custom properties

Most properties the sheet reads carry a fallback, so a page that sets none of them still renders. The token layer has none, and the host declares it before the sheet: the colours and radius themeCss writes from a theme's tokens (--sw-bg, --sw-surface, --sw-fg, --sw-muted, --sw-line, --sw-accent, --sw-accent-fg and --sw-radius), and the host's own scale, such as --sw-line-strong, --sw-radius-full, --sw-font-display, the spacing steps --sw-space-* and the type sizes --sw-text-*. Without them, the bands render without those values.

import { themeCss } from '@storeware/theme-schema';

const style = `<style>${themeCss(theme.tokens)}</style>`;

The token reference lists the property each token becomes.

Load order

import './tokens.css';
import './app.css';
import '@storeware/theme-kit/sections.css';

Load the sheet after the tokens, because it reads them, and after your own stylesheet. Your sheet may style the same classes, .sw-section and .sw-card among them, but every rule of yours that touches one must be more specific than the kit's bare class, for example by qualifying it with an attribute on :root. Then loading the kit last cannot take a decision away from you.

The stylesheet is a subpath rather than something the entry point imports, so code that reads only the types pays nothing for it, and load order stays your decision.

The data a page needs

Each section in the schema declares what it needs fetched: a product grid needs products, a featured product needs one by handle, a blog band needs recent posts from a named blog. dataPlanFor() in the schema gathers those needs for a whole page into one plan. KitSectionData is the shape that plan is filled into.

import { NO_KIT_DATA, type KitSectionData } from '@storeware/theme-kit';

const data: KitSectionData = { ...NO_KIT_DATA, products: myProducts };
FieldWhat it holds
productsThe products the plain grid draws.
collectionsEvery collection the shop publishes.
mediaPictures, by the id a media setting holds.
featuredSingle products, by handle.
fromCollectionProducts, by the handle of the collection they came from.
articlesPosts, by the handle of their blog.
reviewsReviews of the product the page is about, as a list and a summary, or null.
relatedWhat else a shopper looking at this product might want.

Wherever a band looks something up by name, the field is a map. Two bands that feature the same collection are one read, not two, so adding a band adds no query unless it asks for something no other band wanted.

Your types, not ours

KitSectionData is generic over your own types. Each parameter asks only for what a band reads, and defaults to that minimum:

TypeAsks for
KitProductLikeid, handle, title
KitCollectionLikeid, handle, title
KitArticleLikepath, title
KitReviewLikeid, rating
KitImageLikeurl
type ShopData = KitSectionData<MyProduct, MyCollection, MyArticle>;

A host with rich types passes its own objects with no adapter. A host with nothing but a JSON file passes three-field objects.

Rendering nothing is normal

NO_KIT_DATA is a complete empty fill, so a page can render before anything has loaded. A band whose data is missing draws nothing rather than throwing: a renamed product, a gallery with no pictures yet and a shop with no reviews are all ordinary.

Two smaller types complete the contract. KitPageSubject says what the page is about, for bands that read that rather than a setting: a reviews band names no product and means this one. KitTranslator is a plain (key, vars) => string, and returning the key itself is a valid implementation.

Compatibility

Class names are added and never renamed. A custom property is read with a fallback unless the token layer is required to declare it. As with the schema, the kit may grow and never narrows.

On this page