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-sectionis the wrapper one band gets..sw-bandis 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 };| Field | What it holds |
|---|---|
products | The products the plain grid draws. |
collections | Every collection the shop publishes. |
media | Pictures, by the id a media setting holds. |
featured | Single products, by handle. |
fromCollection | Products, by the handle of the collection they came from. |
articles | Posts, by the handle of their blog. |
reviews | Reviews of the product the page is about, as a list and a summary, or null. |
related | What 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:
| Type | Asks for |
|---|---|
KitProductLike | id, handle, title |
KitCollectionLike | id, handle, title |
KitArticleLike | path, title |
KitReviewLike | id, rating |
KitImageLike | url |
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.