Documentation menu
Documentation / Blocks & themes / theme-shelf
Theme Shelf
npm install @wabbit/tome-theme-shelfFirst install from our registry?
Get a registry token from your credentials page. You need a purchase that includes this package, or a Craft Library membership.
Add the registry and your token to the
.npmrcat the root of your project, with your token in place ofYOUR_TOKEN:@wabbit:registry=https://npm.wabbit.com/ //npm.wabbit.com/:_authToken=YOUR_TOKEN
Shelf, a Tome theme for small-batch shops: a printed catalogue that happens to take your order. Anybody for the shop (expanded display, tabular figures) with Spectral for the maker, the shelf line and the chamfered edition tag, colour by commerce role. Four palettes (Bisque, Stone, Pantry, Smoke) plus derived opposite modes, self-hosted OFL fonts, activated by data-tome-theme="shelf".
Overview
Shelf is a Tome theme for small-batch shops: makers, studios and independent sellers whose product and whose story are one page. Its stance is "small batch, big care". The site reads like a well-made printed catalogue that happens to take your order: the maker's voice sits beside the price and the add button, every row of products stands on a 2px shelf line, and nothing is louder than the thing you came to buy. It ships four palettes (Bisque, Stone, Pantry, Smoke), each in light and a derived opposite mode.
This package is a theme (a ThemeManifest of kind: 'theme'). It owns no blocks. It declares the whole token surface blocks read, self-hosts its two OFL font families, and restyles blocks and chrome through their public hooks. It is the first of the commerce themes: the storefront blocks it styles ship in their own packs.
Layer: ui · Family / tier: themes / addon · Versioning: independent.
Peer dependencies
| Peer | Range | Required |
|---|---|---|
@wabbit/tome-ui | >=0.20.0 <1.0.0 | yes |
@wabbit/tome-core | >=1.23.0 <2.0.0 | no (optional) |
@wabbit/tome-ui is a CSS-contract peer. Its tokens.css re-declares the var()-aliased Layer 2 under [data-tome-theme] and defines the fixed-band ink family this theme sets, and it ships the platform scroll reveal Shelf turns on (0.20.0 adds the font-waiting arm, --tome-reveal-wait: fonts). @wabbit/tome-core is needed only for the ./register subpath and the ThemeManifest type, and 1.23.0 is where a palette gains defaultMode, which Smoke sets. The package imports no React and no Payload.
Usage
Import the stylesheet once at the app root, after tome-ui's tokens, and activate the theme on <html>:
// app/layout.tsx
import '@wabbit/tome-ui/tokens.css'
import '@wabbit/tome-theme-shelf/styles.css'
import '@wabbit/tome-theme-shelf/fonts.css'<html data-tome-theme="shelf"> <!-- Bisque, light -->
<html data-tome-theme="shelf" data-tome-palette="stone"> <!-- Stone -->
<html data-tome-theme="shelf" data-tome-palette="pantry" data-theme="dark">
<html data-tome-theme="shelf" data-tome-palette="smoke" data-theme="dark">With no data-tome-palette the theme renders Bisque. data-theme="dark" gives each its derived dark mode. Smoke is the exception: it is a designed dark page, in its [data-theme="dark"] slot, with a derived light page in the plain palette slot. It is meant to open dark, so the manifest sets defaultMode: 'dark' on it, which createThemeConfig(), the admin switcher and the storefront read to set data-theme="dark" when it is picked. A site with its own switcher can read SHELF_PALETTE_DEFAULT_MODES, the same fact as a map.
To have the admin ThemeSwitcher, createThemeConfig() and other registry readers list it, register it on the server before building the Payload config:
import '@wabbit/tome-theme-shelf/register'Header: Navbar 5, links centred
Shelf recommends the tome-chrome Header global on Navbar 5 (design version '5'): the wordmark left, the shelf links centred, the account link and the bag on the right, in a full-width square bar on the page ground. Set the Header global's `navAlignment` to `"center"`; Navbar 5 honours it from chrome 0.23.0 and writes data-nav-align="center" on its root.
- The bar earns its chrome on scroll. At the top of the page it is bare (the page ground, a transparent hairline). Once the page has scrolled past 20px, Navbar 5 reports the state (
useReportNavScrolled) and the frame carriesdata-nav-scrolled="true"; the theme then gives the bar the surface tone and a hairline. The frame keeps chrome's own sticky and hide-on-scroll behaviour; the theme only paints it. A transparent overlay header over a hero (data-overlay="true") stays the platform's. - The name is your
LogoComponent. Mark it up with the lockup hook:<span data-shelf-wordmark>Your Shop</span>(Anybody at wdth 140, 600, uppercase, tracked 0.04em). - The call to action is the buy pill: ink, 44px, bone on Smoke.
Footer 7 (its own tonal zone, no footer call-to-action band, the credits line in the small print wherever plates show) is recommended and is styled in the next release, with the cart slot, the numbered shelf prefix and the checkout mode.
Scroll reveal
Shelf turns on tome-ui's opt-in platform scroll reveal (--tome-reveal: on) and maps its timing to a short glide: --tome-reveal-rise 14px, --tome-reveal-duration 640ms, --tome-reveal-stagger 70ms, with --tome-reveal-wait: fonts. It runs only where the site mounts the controller once in its root layout:
import { TomeReveal } from '@wabbit/tome-ui/reveal'
// inside <body>, once
<TomeReveal />Without the controller nothing moves and nothing is hidden. Blocks that animate themselves (data-tome-motion="self") are skipped by the controller. data-tome-reveal="off" on <html>, a region or a block root turns it off there. Under reduced motion there is no reveal and no smooth scroll.
Motion
User-triggered motion springs; scroll motion glides. Three private tokens carry it, each a transition fragment (duration and easing) so a treatment writes transition: transform var(--shelf-spring):
| Token | For | Value |
|---|---|---|
--shelf-spring | the bag drawer and the filter sheet spring in | 420ms, a slight overshoot |
--shelf-glide | reveals, the occasion row's rise | 640ms ease-out |
--shelf-roll | the bag count and the price in the add button roll like an odometer | 360ms ease-out |
Under prefers-reduced-motion all three become 0.01ms linear and the reveal rise is 0, so steppers swap instantly, counts change without rolling, and the drawer and sheet appear without sliding. treatments/base.css settles every other transition and animation once for the whole themed document.
Fonts
Anybody (the shop: display, UI, names, prices, the meta voice) and Spectral (the maker) ship inside the package as latin-subset woff2, with their SIL OFL 1.1 licences (fonts/OFL-Anybody.md, fonts/OFL-Spectral.md). There is no third-party font request. Pick one delivery path:
import '@wabbit/tome-theme-shelf/fonts.css': the bundler resolves the files from this package.- Your own host: copy the files from
@wabbit/tome-theme-shelf/fonts/*and injectgetShelfFontStyles({ src }). next/font/local, using the file list fromgetShelfFontFaces().
import { getShelfFontPreloads } from '@wabbit/tome-theme-shelf/fonts'
// in <head>: preload Anybody and the maker's roman
{getShelfFontPreloads({ src: '/fonts/shelf/' }).map((l) => <link key={l.href} {...l} />)}Both are subset from the repo TTFs by scripts/subset-fonts.py (SHA-pinned sources, byte-identical output, and a fail-loud tnum check), not taken from the served builds, because the served builds strip features Shelf depends on: the served Spectral has no onum and the served Anybody drops case. Anybody is one variable face, wdth 100 to 150 and wght 300 to 700 (39,264 bytes); Spectral is 400 (29,484), 400 italic (30,600) and 600 (31,192). Neither family has a Reserved Font Name.
Two voices. The shop speaks in Anybody and the maker speaks in Spectral. A sentence in the serif always reads as a person; a line in the grotesk always reads as a fact you can act on.
- Display: Anybody at an expanded width (titles at
wdth125, the hero at 118), weight 500, never heavier than 600,text-wrap: balance. - UI, names, prices: Anybody at
wdth100, weights 400, 500 and 600. - Meta voice: Anybody at
wdth140, 500, 12px, uppercase, 0.1em, tabular, withcaseon. Always above a heading, never alone. - Maker's voice: Spectral 400 at 19 to 21px on the 600px measure, with old-style figures. Italic only inside a quoted person; 600 is the bold.
- Every Anybody rule sets its weight. The default instance of the subset is
wght300, the axis floor, so a rule that sets the family and no weight draws light text. - Every number in a row is tabular, in Anybody (prices, quantities, counts, edition and order numbers, dates), set explicitly on the price and count hooks. A price never appears in Spectral. Anybody has no U+2197, so the new-tab arrow is set in Spectral.
Sites can mark their own markup with data-shelf-meta (the meta voice), data-shelf-maker (the maker's voice), data-shelf-quote (a quoted person, italic) and data-shelf-figures (tabular figures).
Effects
Two opt-in effects. Both are off unless a site asks, on <html> or on one section:
<html data-tome-theme="shelf" data-shelf-effect="keyed-plates">
<html data-tome-theme="shelf" data-tome-palette="pantry" data-shelf-effect="grain keyed-plates">
<section data-shelf-effect="grain">…</section>- `keyed-plates`. Product plates are exported keyed to the well (the white sweep normalised to white, the shadows kept), so a plate laid on the well with
mix-blend-mode: multiplymakes the well its ground and the piece stands on the shelf line instead of in a white box. The blend is the per-mode private token--shelf-plate-blend:multiplyin light modes,normalin dark modes, where plates show as lit prints in their 4px frame (no cut-outs in v1). Only the primary tile image and the drawer's thumbnails of it blend; close-ups and in-use frames are real photographs and never do. Without the effect, plates show as lit prints in every palette, which is safe for any merchant's photos. - `grain`. A faint fractal-noise grain (alpha .05) on the ground, the earned header and the hero: Pantry in its designed light mode only. Every other palette and mode declares
--shelf-grainasnone, so the effect is inert there. It never touches the dark band or the footer.
Public API
| Export | Subpath | Purpose |
|---|---|---|
shelfThemeManifest (ThemeManifest) | ., ./manifest | The theme's manifest: name shelf, kind: 'theme', tier addon, four palettes, fonts and one pairing, recommended blocks and chrome (Navbar 5, footer 7), styledBlocks, requires.tomeUi. |
SHELF_PALETTE_IDS, ShelfPaletteId | ., ./manifest | ['bisque', 'stone', 'pantry', 'smoke'], the data-tome-palette values. |
SHELF_PALETTE_DEFAULT_MODES | ., ./manifest | The mode each palette opens in: light for three, dark for Smoke. The manifest carries the same fact as each palette's defaultMode. |
SHELF_STYLED_BLOCKS, SHELF_RECOMMENDED_BLOCKS | ., ./manifest | The block slugs the theme restyles, and the slugs its demo is composed from (catalog slugs only; the storefront pack's blocks join when they publish). |
ThemeManifest (type) | . | Re-exported from @wabbit/tome-core/registry/theme. |
getShelfFontFaces(), getShelfFontStyles({ src }), getShelfFontPreloads({ src }) | ., ./fonts | Font descriptors, @font-face CSS for a custom host, and preload links. |
SHELF_FONT_STACKS, SHELF_FONT_LICENSES, SHELF_LATIN_UNICODE_RANGE, SHELF_SHOP_WEIGHTS, SHELF_MAKER_WEIGHTS | ., ./fonts | The CSS stacks the tokens use, the licence file per family, the one subset range (it includes U+2197), and the weights used. |
ShelfFontFace, ShelfFontPreloadDescriptor, ShelfFontStylesOptions (types) | ., ./fonts | The descriptor shapes. |
| side effect | ./register | Registers shelfThemeManifest in tome-core's theme registry. |
| stylesheet | ./styles.css | Tokens, treatments and the effects. The file that does everything visual. |
| stylesheet | ./fonts.css | The four @font-face rules. Optional. |
| font files | ./fonts/* | The woff2 files and their OFL licences, for self-hosting. |
import { shelfThemeManifest } from '@wabbit/tome-theme-shelf/manifest'
shelfThemeManifest.palettes.map((p) => p.label)
// ['Bisque', 'Stone', 'Pantry', 'Smoke']Tokens
The theme sets Layer 1 (the names tome-ui's tokens.css reads: --background, --foreground, --card, --primary, --accent, --muted, --border, --input, --ring, the feedback, chart and muted2 names) and lets tome-ui's generated re-resolution derive the aliased Layer 2. It sets these Layer-2 literals directly, because no Layer-1 input sits behind them:
--tome-color-accent-textand the feedback*-textweights, and--tome-color-surface-tint- the fixed-band inks:
--tome-color-surface-solid-dark,--tome-color-on-solid-dark,--tome-color-on-solid-dark-muted,--tome-color-accent-on-solid-dark,--tome-color-primary-on-solid-dark,--tome-color-border-inverse, and the light fixed surface--tome-color-surface-inversewith--tome-color-on-inverse,--tome-color-on-inverse-muted,--tome-color-accent-on-inverse,--tome-color-primary-on-inverse. The page's one dark band is the closing conversion. - the four
--tome-type-*families, each paired with its--font-*slot
Selector ladder (specificity, never load order):
| Selector | Specificity | Holds |
|---|---|---|
[data-tome-theme="shelf"] | 0,1,0 | Bisque light, type, radius, rhythm, lanes, motion |
[data-tome-theme="shelf"][data-theme="dark"] | 0,2,0 | Bisque dark |
[data-tome-theme="shelf"][data-tome-palette="p"] | 0,2,0 | palette light (for Smoke, its derived light page) |
[data-tome-theme="shelf"][data-tome-palette="p"][data-theme="dark"] | 0,3,0 | palette dark (for Smoke, the designed page) |
The palette rules are generated from shelf/scripts/palettes.mjs --fix (zero failures), never retyped. Every palette rule, light and dark, declares the full set: Layer 1, the literals, the band inks and the private colour tokens (--shelf-mark, --shelf-well, the role colours and their 12% tints, --shelf-ink-hover, the badge and chip fills, --shelf-placeholder, --shelf-shadow, --shelf-grain-ink, --shelf-grain, --shelf-plate-blend).
Colour by commerce role. Exactly four role colours per palette, and nothing else may wear them:
| Role | Token | Used for |
|---|---|---|
| Buy | --primary (the ink; bone on Smoke) | every add, checkout and pay pill, in every palette |
| Notice | --accent / --tome-color-accent-text / --shelf-notice | the sale price, low stock, the filter count |
| Confirm | --success | in stock, added, paid, delivered |
| Error | --error | validation and payment failures |
--warning maps to the notice colour and --tome-color-info-text to ink, so a foreign block cannot add a fifth role colour. The mark (--shelf-mark) draws bars, rules and dots (the free-shipping bar, the link underline, the focus ring where it clears 3:1); where the accent carries text, the text-safe shade is used. Pantry's bright marmalade (`#C9640E`) is a mark only and never sits behind text; anything that carries a number fills with the deep marmalade #9A470C, which is also Pantry light's platform --accent.
Shape and rhythm. --radius 8px; plates 4px, cards, fields and drawer lines 8px, the buy object 22px (the roundest thing on the page), pills (999px) for things you press only; bands and the drawer's viewport edge are square. The shelf line is a 2px ink rule every row stands on (--shelf-line). One section rhythm (56 to 104px), a 600px prose measure that is not negotiable, and tome-ui's grid with Shelf's lanes: three columns of left and right marginalia and one prose-pad column, four and four from 1760px. tome-ui sets the root at 18px, so the approved design's 16px-root sizes are converted by 16/18.
The field-on-well rule. No field sits on the well (the well holds chips, swatches, plates and tracks). As a safety net, --input clears 3:1 on the well in every mode, so a foreign field placed on a well still passes.
Palettes
| Palette | Page / surface / well | Ink / muted | Mark | Notice text | Opens |
|---|---|---|---|---|---|
| Bisque (default) | #F1E7E2 / #FBF7F3 / #E6DAD3 | #1E1918 / #5C514B | cobalt #2C46A8 | #2C46A8 | light |
| Stone | #ECEEEB / #F8F9F7 / #DEE2DD | #191E1D / #4E5754 | petrol #1E5B70 | #1E5B70 | light |
| Pantry | #F6EFD8 / #FCF9EE / #ECE3C8 | #2A1C15 / #5F4D40 | marmalade #C9640E (mark only) | deep marmalade #9A470C | light |
| Smoke | #1B1613 / #26201C / #312A25 | #F2EADF / #BCAF9F | glaze blue #A3B5F5 | #A3B5F5 | dark |
Contrast
All ratios are WCAG 2.x. Every text pair clears 4.5:1 and every mark 3:1 in all eight modes, including on the well. The test suite re-measures every pair from the shipped values. The tightest passes are the field outline on the well (3.06 to 3.78) and the role text on the well (4.73 to 6.14).
| Pair | Bisque | Stone | Pantry | Smoke (dark) |
|---|---|---|---|---|
| tightest Shelf-painted text | 4.73 | 4.94 | 5.00 | 6.14 |
| tightest mark or field outline | 3.12 | 3.06 | 3.06 | 3.72 |
What it styles
| Hook | Treatment |
|---|---|
Section openers ([data-opener], -meta, -title, -link, -count) | The masthead (move 5): the meta label in the left marginalia lane, the title from the prose line, the link in the right lane, a hairline under all three; stacked below 1024px. One hook set, emitted by every block that opens a band. |
high-impact-hero split | Move 1, photo and promise. From 1024px on the page's 16 columns: the copy on columns 1 to 8 and one photo from column 9 to the edge, with a SOLID caption chip ([data-hero-caption-chip]) over the photo that names the piece and its price in tabular figures and carries the add ([data-hero-caption-action]); below 1024 it stacks. The meta line (the eyebrow), the promise in Anybody at wdth 118 and weight 500, balanced, the maker line in Spectral, the buy pill and the outline pill, then the proof line ([data-hero-proof-segment], one per fact, divided by middots). |
storefront-store-facts strip ([data-store-facts], [data-fact], [data-fact-label]) | Three plain facts between two hairlines: a row of label and text on phones, three equal columns with the label above its text from 768px. May also be authored as plain text on the same hooks. |
The shelf line ([data-shelf-line]) | A 2px ink rule, on a row or on an empty element. |
The edition tag ([data-edition-tag]) | A chamfered label (6px corners, a clip-path) in the meta voice on the well; on a plate ([data-tile-plate]) it takes the surface tone. Never carries a price. |
Price and count hooks ([data-price], -now, -was, [data-add-price], [data-add-count], [data-opener-count]) | Tabular figures. |
Chrome Navbar 5 ([data-visible][data-nav-theme][data-overlay], [data-nav-scrolled], [data-nav-align="center"]) | The bar that earns its chrome on scroll, the shelf links, the lockup hook [data-shelf-wordmark]. |
[data-appearance] links and buttons (in main, nav and the footer) | The buy pill (ink) and the outline pill; 44px; the price inside is tabular. |
[data-new-tab] | The north-east arrow after the link text, in Spectral. |
[data-compliance-label] (every block) | The plate tag ("Example", credits): a small label on the surface colour; inside a photo, the overlay at its bottom left. |
Treatments target only public hooks: data-block-type on the block wrapper, data-block-variant on the root, the blocks' documented data-* part attributes, and published component tokens. Element names are used only where a block publishes no part hook (a hero's headline and photo, the header's link list). They never target class names. data-block-type is matched in its catalog form (high-impact-hero, not the stored highImpactHero), and a test fails any other form.
Not in this release (the home bands): the product tile and its flags, the shelf rows and the next-edition tile, the contents rows, the closing band, the testimonial, FAQ, term-ledger, split-view and journal bands, and Footer 7. The restock alert's form (S7) is styled on the contract's hooks and is checked when that block publishes.
Commerce treatments
The shop, the product page, the bag and the single-task pages, built to the approved mockups (a-r2-catalog.html, a-r2-product.html, a-r2-lookup.html). Every rule is scoped to the theme, reads colour from tokens only, and reaches blocks through their public hooks.
| File | Styles |
|---|---|
filters.css | The shop's rail (numbered shelves with counts, the current one on the 2px rule; checkbox facets with counts, an empty option dimmed in the muted ink), the toolbar (live count, Filter button, applied chips on the well, Clear all, Sort), "Show the next 6", the no-results state, the phone shelf tabs and the bottom sheet (spring in, grab handle, "Show N pieces" as the ink pill) |
gallery.css | product-gallery: frames on the well at the plate radius, 4rem thumbnails with an ink border on the current one, the swipe dots |
buy-box.css | storefront-buy-box: the 22px buy object, kicker, name, maker line, price with the edition tag, swatches and segments (aria-checked), the quantity stepper, the ink add button with the price inside, the availability line, the folds, the phone buy bar, sold out as a neutral chip |
drawer.css | storefront-cart-drawer: square, 440px, spring in; the free-shipping bar (mark fill, confirm once reached), lines, the line stepper, "Goes with", the empty bag's three shelves, the foot with the checkout pill and the total inside it |
cart.css | storefront-cart-trigger and chrome's cartSlot: the ink outline bag with its count dot; the AddToBag pill and the stepper it becomes in place; maps the pack's --tome-roll to --shelf-roll |
facts.css | storefront-store-facts, all five layouts: strip, assure, proof, steps (with the step icons and the marked current step), line |
single-task.css | storefront-order-lookup (ask and sent), storefront-checkout-step, storefront-order-confirmation (pending, paid, closed) |
checkout-chrome.css | Chrome's chromeMode="checkout": the reduced header (wordmark, "Back to the shop") and footer (the credits line, copyright and legal links, on the well) |
The rules the treatments hold to: buy is ink on every pill (bone on Smoke); the notice colour appears only on a sale, on low stock and on the filter count; prices and counts are tabular and right-aligned in the drawer and on the order pages; controls are 44px (the 36px chips and small add pills carry a 44px hit area); the bright marmalade is never behind text and the focus ring is the palette's ring token (the darkened marmalade on Pantry light). The stepper swap, the count and price rolls and the free-shipping growth are the packs' own and run only under prefers-reduced-motion: no-preference; the drawer and the sheet spring in on --shelf-spring under the same condition.
Stripe hosted-checkout branding
Stripe's hosted checkout is branded in the Stripe Dashboard (Settings, Branding, Checkout), once per merchant account. It takes a background colour, a button colour, a font from Stripe's list, a border style, a logo and an icon; Stripe picks the button text colour itself. Values below are each palette in its designed mode, from the generated palette tables.
| Palette | Background | Button (ink) | Button text (Stripe picks) | Notice, for reference | Button on background |
|---|---|---|---|---|---|
| Bisque (light) | #F1E7E2 | #1E1918 | #FBF7F3 | cobalt #2C46A8 | 14.3:1 |
| Stone (light) | #ECEEEB | #191E1D | #F8F9F7 | petrol #1E5B70 | 14.5:1 |
| Pantry (light) | #F6EFD8 | #2A1C15 | #FCF9EE | deep marmalade #9A470C | 14.3:1 |
| Smoke (dark) | #1B1613 | #F2EADF (bone) | #1B1613 | glaze blue #A3B5F5 | 15.0:1 |
For every palette:
- Font:
be_vietnam_pro, the nearest Stripe font to Anybody atwdth100. The alternativemontserratis closer to the expanded display but too wide at form sizes. Spectral has no near match, and the checkout carries no maker prose. - Border style:
rounded. Shelf's fields and lines are radius 8;pillwould round Stripe's inputs too, and Shelf keeps pills for things you press. - Logo: the store's wordmark on the palette's background colour. Icon: the store's mark. Display name: the store's name.
- Branding is dashboard-only in v1: per-session
branding_settingswould need a change to the economy Stripe adapter. - Merchant of record: the Stripe account is the merchant's own, so the business name and logo must match the store, as card-network rules require.
Server / client posture
The package is pure CSS plus constants. It has no React components and no 'use client' files, so it is inert under React Server Components. It adds nothing to a client bundle beyond the stylesheets the consumer imports. ./register is a server-side import.
Tests
pnpm --filter @wabbit/tome-theme-shelf test runs the suites: theme conformance (both modes of assert-theme-conformance), palettes (the full required token set in all eight modes, the commerce-role invariants, the grain and the plate blend), contrast (every text pair at 4.5:1 and every mark at 3:1 in all eight modes, the role tints re-computed), no colour literals outside tokens/, fonts (A4.1 pairing, explicit Anybody weights, tabular figures, and the shipped woff2 files read for tnum, case, onum and the variation axes), the manifest, the hook contract and hook coverage.
License
Wabbit Tome Commercial License (see LICENSE.md). The bundled fonts keep their SIL OFL 1.1 licences.
Exports
| Import path |
|---|
@wabbit/tome-theme-shelf |
@wabbit/tome-theme-shelf/manifest |
@wabbit/tome-theme-shelf/register |
@wabbit/tome-theme-shelf/fonts |
@wabbit/tome-theme-shelf/styles.css |
@wabbit/tome-theme-shelf/fonts.css |
@wabbit/tome-theme-shelf/fonts/* |
Changelog
0.1.xMinor
New package:
@wabbit/tome-theme-shelf, a Tome theme for small-batch shops: makers, studios and independent sellers whose product and whose story are one page.It reads like a well-made printed catalogue that happens to take your order: the shop speaks in Anybody, an expanded grotesk with tabular figures, and the maker speaks in Spectral. A 2px shelf line under every row, a chamfered edition tag, numbered section openers, and colour by commerce role: buy is ink, and the one accent is only ever a notice.
- Four palettes, each in light and a derived opposite mode. Bisque (default), Stone, Pantry and Smoke, activated by
data-tome-theme="shelf"anddata-tome-palette. Smoke is a designed dark page with a derived light page, and opens dark through the manifest'sdefaultMode(theSHELF_PALETTE_DEFAULT_MODESmap says the same for a site's own switcher). Pantry's bright marmalade is a mark only and never sits behind text. Every text pair clears 4.5:1 and every mark, field outline and focus ring 3:1 in all eight modes, including on the well, and the tests re-measure them. - Self-hosted OFL fonts. Anybody (one variable face,
wdth100 to 150 andwght300 to 700, with workingtnumandcase) and Spectral 400, 400 italic and 600 (withonum), subset from the upstream TTFs, through./fonts.css,getShelfFontStyles()ornext/font/local. The tests read the shipped woff2 files for their features and axes. - Navbar 5 that earns its chrome on scroll. The theme styles the header on chrome's opt-in
data-nav-align="center"anddata-nav-scrolledhooks: bare at the top, a surface tone and a hairline once scrolled. - The hero and the opener. The
splithero is the copy on the first eight columns and one photo from column nine, a solid caption chip with the price in tabular figures and the add, and a proof line of plain facts; the promise is Anybody atwdth118. The section opener is the masthead: label in the left lane, title from the prose line, link in the right lane, a hairline under all three. - The store-facts strip, the shelf line and the edition tag, as CSS on the
data-store-facts,data-shelf-lineanddata-edition-taghooks. - Motion tokens.
--shelf-spring,--shelf-glideand--shelf-roll, each collapsing to an instant swap underprefers-reduced-motion, with the platform scroll reveal turned on as a short glide. - Two opt-in effects,
data-shelf-effect="keyed-plates"(keyed product plates multiply into the well in light modes; lit prints in dark and by default) anddata-shelf-effect="grain"(Pantry light only).
Not in this release: the product tile, shelf rows, contents rows, filters, buy box, bag drawer, checkout surfaces, the home bands below the hero, and Footer 7.
SHELF_STYLED_BLOCKSlists only the blocks that are styled.Checkpoint B round 2: the shop's results and toolbar sit on the main column from reading-start at 1024px and up; the product page's gallery (8 columns) and buy box (7) fill their columns; the making pairs win over the pack's 7fr 5fr split; the compact restock is a chip on the occasion row's plate; a count-only opener is a caption; the lookup facts panel starts level with the task; "What we don't do" loses the ledger's second 2px rule; Footer 7 is tight.
Parity pass (2026-10-19): ruled-split opener parts take the content lane below 1024 (the home no longer overflows at 320); the size segment group wraps and fits its lane; label and low-stock chips are fit-content; journal row titles are the 16px UI step; the footer shelf links, breadcrumb and signup band link carry a 44px hit area; a
[data-guide-tile]treatment stands the guide tile on the shelf line at plate height on the surface.- Four palettes, each in light and a derived opposite mode. Bisque (default), Stone, Pantry and Smoke, activated by
1 fix (0.1.1)
- 0.1.1
On a phone the Shelf footer now stacks into one left-aligned column, with each store fact on its own line under a hairline instead of squeezed into a narrow third.