# Theme kit

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

Source: https://storeware.ai/docs/themes/theme-kit

`@storeware/theme-kit` is the other half of [the theme schema](https://storeware.ai/docs/themes/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](https://github.com/storeware-ai/theme-kit). It depends on nothing, not even the schema, though a renderer will use both.

## The stylesheet

```ts
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.

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

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

The [token reference](https://storeware.ai/docs/themes/reference/tokens) lists the property each token becomes.

## Load order

```ts
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.

```ts
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` |

```ts
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.
