Documentation menu
Documentation / Blocks & themes / theme-studio
Theme Studio
npm install @wabbit/tome-theme-studioFirst 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
Studio, a Tome theme for studios whose portfolio is the sales argument: architects, design-build firms, interior designers, landscape architects, photographers and small creative agencies. The work speaks: a photograph is the largest thing on every page, type behaves like a caption and never sits on it, and a numbered project archive with a facts block carries the rest. Archivo at three widths, a mark-only accent, one dark band for the one ask. Four palettes (Gallery, Concrete, Black Box, Blueprint) plus derived opposite modes, a self-hosted OFL font, activated by data-tome-theme="studio".
Overview
Studio is a Tome theme for studios whose portfolio is the sales argument: architects, design-build firms, interior designers, landscape architects, photographers and small creative agencies. Its stance is "the work speaks". The photograph is the largest thing on every page, and type behaves like a caption, set beside or below the work and never on top of it. The site reads like a well-kept project archive, with numbered jobs, credits and a facts block on every project, so a visitor can browse twenty projects as easily as admire one. It asks for nothing until the visitor has seen enough work to want to talk, and then it asks once, in the one dark band at the end.
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 one OFL font family, and restyles blocks and chrome through their public hooks. The blocks it is composed from are the portfolio blocks (work-index, project-hero, title-block, plate-sequence, service-rows), the dossier pack's phase-ledger and record-roster, team-roster and contact from agency-essentials, and the marketing-starter testimonial.
Layer: ui · Family / tier: themes / addon · Versioning: independent.
Peer dependencies
| Peer | Range | Required |
|---|---|---|
@wabbit/tome-ui | >=0.21.0 <1.0.0 | yes |
@wabbit/tome-core | >=1.25.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 Studio turns on. @wabbit/tome-core is needed only for the ./register subpath and the ThemeManifest type.
What is in the first release
The first release ships the whole home page and the case study:
- Foundation: four palettes (Gallery, Concrete, Black Box and Blueprint) with their derived opposite modes, the type and rhythm tokens, the self-hosted font, the base treatments, the section opener with its counter, the Title Block cells, the motion tokens with their reduced-motion paths, and 44px hit areas on every control.
- Chrome: Navbar 1 (the 125% name, centred nav, the current-page underline, the word "Menu" trigger, the numbered type-only phone menu) and Footer 7 on its tint zone.
- Home: the hero (
work-indexfeature); the ledger (work-indexindex: chips, the count, the Plates and Ledger toggle, the phone filter sheet, and the Plates view with one frame per palette); selected work with its straddle; the practice statement (title-blockpractice); the service rows; the pinned process (phase-ledgerpinned); people (team-roster); clients (record-roster); and the close as the one dark band (contactestimatewith the inquiry form). - Case study:
project-hero;plate-sequencein all three modes with margin captions pinned under the header, and the full-screen drawing viewer; the credits, brief and facts bands; thenextteaser; the client voice (testimonial); and the morph CSS for the cross-document view transition (off under reduced motion).
STUDIO_STYLED_BLOCKS lists every block with a treatment.
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-studio/styles.css'
import '@wabbit/tome-theme-studio/fonts.css'<html data-tome-theme="studio"> <!-- Gallery, light -->
<html data-tome-theme="studio" data-tome-palette="concrete"> <!-- Concrete, light -->
<html data-tome-theme="studio" data-tome-palette="blackbox" data-theme="dark"> <!-- Black Box, dark by design -->
<html data-tome-theme="studio" data-tome-palette="blueprint" data-theme="dark"> <!-- Blueprint, dark by design -->With no data-tome-palette the theme renders Gallery. Gallery and Concrete are light designs and data-theme="dark" gives each its derived dark mode. Black Box and Blueprint are designed dark: their manifest entries carry defaultMode: 'dark', a site that opens them sets data-theme="dark", and their derived light page sits in the plain palette slot.
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-studio/register'Type: one family, three widths
Archivo is a variable face with a width axis, and the width is the hierarchy. Headings and job numerals set at 112%, running copy at 100%, the meta voice at 80% and the studio's name at 125%, with small size steps because the photographs supply the scale contrast. The largest words on any page are at the h2 size; the page title is an h1 set at that size. Display is 400 and never bold; job numerals are 300; the meta voice is 500, sentence case, with tabular figures, in ruled cells, never a tracked uppercase eyebrow and never monospace. Archivo's default instance is not the voice (the subset's default weight is 600), so every Studio rule that sets the family also sets its weight and its width.
Running text is capped at a 600px measure (--studio-measure), at every width, and the tests fail a rule that widens it.
The accent is a mark, never text
The accent colour (--studio-mark) appears in a handful of places only: the filled dot on a new project, the inset 2px rule on a pressed chip or view toggle, the filled square on the current plate and on "Featured above", the active nav underline and hover underlines, and the focus ring. It never colours text, and every accented state also changes shape or weight, so Black Box (a white mark, 1.27:1 against its ink) loses no meaning. The platform's accent-text therefore resolves to the ink, and the one action fill is the ink: a square 44px rectangle with one hover idiom everywhere, a plain swap to the ground with a 1px ink edge.
Type never sits on the work
No text node sits inside an image wrapper, no scrim or gradient covers a photograph, and a stock-photo or sample label (data-compliance-label) is a caption on the ground, not an overlay. Plates are full colour, square-cut and always credited.
Header: Navbar 1
Studio recommends the tome-chrome Header global on Navbar 1 (design version '1'): the studio's name in plain type at the left, the links, and the call to action always at the right. The header sits on the ground and earns its surface and a hairline once the page scrolls ([data-nav-scrolled="true"], which Navbar 1 reports when the Header global turns on reportScrolled); the platform's visibility frame hides it on scroll down and returns it on scroll up. The active nav item is ink 600 with a 2px underline in the mark. Below 900px the bar is the name and a word "Menu" button (button[data-mobile-menu-trigger="word"]). The name is plain set type: mark it up with the hook [data-header-wordmark] (header.wordmarkText) and it sets at 125%.
The hero
The home hero is one flagged project: a 16:10 plate that bleeds off the right edge from grid line 8 to full-end at 1024px and up, settling once from scale 1.045 over 1400ms, with the copy beside it in content-start to line 7, bottom-aligned to the plate. The copy is the meta line, the job number at h2 size in weight 300, the title, two sentences, the plate count with the square mark, and the button. The nine-cell Title Block runs under both as one facts line. The settle plays without script and does not play under reduced motion.
The ledger
The Work Index as real data: permanent job numbers in the left lane (a gap stays a gap), the title with its place, the kind cell (type, status, year, the new mark) and a static 4:3 thumbnail in the last three content columns, last in the DOM. Rows are full-width rules on the list's subgrid, never card borders, and nothing depends on hover. The featured project keeps its row, quieted: muted number and title, the thumbnail dimmed to 55%, and "Featured above" with the square mark.
Scroll reveal and the morph
Studio turns on tome-ui's opt-in platform scroll reveal (--tome-reveal: on) with a small rise and fade: --tome-reveal-rise (--studio-rise), --tome-reveal-duration 800ms (--studio-reveal), --tome-reveal-stagger 80ms. It also sets --tome-reveal-wait: fonts. The reveal runs only where the site mounts the controller:
import { TomeReveal } from '@wabbit/tome-ui/reveal'
// inside <body>, once
<TomeReveal />Without the controller nothing moves and nothing is hidden. --studio-morph (600ms) is the duration of the cross-document view transition that grows a listing image into the project page's lead image; a site opts in with tome-motion's route transition, and reduced motion resolves the settle, the reveal and the morph to their end state (--studio-settle, --studio-reveal and --studio-morph become 0s).
Fonts
Archivo (one variable face carrying wdth 80 to 125 and wght 300 to 600) ships inside the package as a latin-subset woff2 with its SIL OFL 1.1 licence (fonts/OFL-Archivo.md). There is no third-party font request by default. Three ways to deliver it:
import '@wabbit/tome-theme-studio/fonts.css': the bundler resolves the file from this package.- Your own host: copy the file from
@wabbit/tome-theme-studio/fonts/*and injectgetStudioFontStyles({ src }). next/font/local, using the file fromgetStudioFontFaces().
import { getStudioFontPreloads } from '@wabbit/tome-theme-studio/fonts'
// in <head>: preload the one face
{getStudioFontPreloads({ src: '/fonts/studio/' }).map((l) => <link key={l.href} {...l} />)}The face is cut from the full TTF in the google/fonts repository at a pinned commit (scripts/subset-fonts.py, SHA-256 checked), not from the Google Fonts CSS API. The subset keeps tnum across the kept axes: the ten tabular figures share one advance at every tested width and weight (the recipe checks its own output and the test suite re-checks the shipped file), which is what lets job numbers, years and counts stack on one column. The face has no U+2197, so the new-tab mark is a CSS mask on [data-new-tab]::after, never a glyph. No italic is shipped: captions and quotes are upright. The fallback face in the stack is Instrument Sans (width 75 to 100), which keeps the condensed meta voice and the body but not the expanded display widths.
Public API
| Export | Subpath | Purpose |
|---|---|---|
studioThemeManifest (ThemeManifest) | ., ./manifest | The theme's manifest: name studio, kind: 'theme', tier addon, four palettes, fonts and one pairing, recommended blocks and chrome (Navbar 1, footer 7), styledBlocks, requires.tomeUi. |
STUDIO_PALETTE_IDS, StudioPaletteId | ., ./manifest | ['gallery', 'concrete', 'blackbox', 'blueprint'], the data-tome-palette values. |
STUDIO_PALETTE_DEFAULT_MODES | ., ./manifest | The mode each palette opens in: light for Gallery and Concrete, dark for Black Box and Blueprint. |
STUDIO_STYLED_BLOCKS, STUDIO_RECOMMENDED_BLOCKS, STUDIO_PENDING_CATALOG_BLOCKS | ., ./manifest | The block slugs the theme restyles, the slugs its demo is composed from, and the ones the portfolio pack has not yet registered in the block catalog. |
ThemeManifest (type) | . | Re-exported from @wabbit/tome-core/registry/theme. |
getStudioFontFaces(), getStudioFontStyles({ src }), getStudioFontPreloads({ src }) | ., ./fonts | Font descriptors, @font-face CSS for a custom host, and preload links. |
STUDIO_FONT_STACKS, STUDIO_FONT_LICENSES, STUDIO_LATIN_UNICODE_RANGE, STUDIO_DISPLAY_WEIGHTS, STUDIO_UI_WEIGHTS | ., ./fonts | The CSS stacks the tokens use, the licence file for the family, the subset range, and the weights used. |
StudioFontFace, StudioFontPreloadDescriptor, StudioFontStylesOptions (types) | ., ./fonts | The descriptor shapes. |
| side effect | ./register | Registers studioThemeManifest in tome-core's theme registry. |
| stylesheet | ./styles.css | Tokens and treatments. The file that does everything visual. |
| stylesheet | ./fonts.css | The @font-face rule. Optional. |
| font files | ./fonts/* | The woff2 file and its OFL licence, for self-hosting. |
import { studioThemeManifest } from '@wabbit/tome-theme-studio/manifest'
studioThemeManifest.palettes.map((p) => p.label)
// ['Gallery', 'Concrete', 'Black Box', 'Blueprint']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 the Layer-2 names that have no Layer-1 input behind them:
--tome-color-accent-text, the feedback*-textweights, and--tome-color-surface-tint- the fixed-band inks (
--tome-color-surface-solid-darkand its family,--tome-color-surface-inverseand its family) - the four
--tome-type-*families, each paired with its--font-*slot
The colour roles Studio adds are private: --studio-mark (the accent as a mark), the inquiry band (--studio-band, -on-band, -band-muted, -accent-on-band, -band-input, -ring-on-band and the hairline -band-rule), the chips (--studio-chip-fill, -chip-edge), the plate frame (--studio-mat for Gallery, --studio-keyline for Blueprint; Concrete butts its plates edge to edge and Black Box shows them on black) and the accent, confirm and error tints.
Selector ladder (specificity, never load order):
| Selector | Specificity | Holds |
|---|---|---|
[data-tome-theme="studio"] | 0,1,0 | Gallery light, type, radius, rhythm, lanes, motion |
[data-tome-theme="studio"][data-theme="dark"] | 0,2,0 | Gallery dark |
[data-tome-theme="studio"][data-tome-palette="p"] | 0,2,0 | palette light (derived, for Black Box and Blueprint) |
[data-tome-theme="studio"][data-tome-palette="p"][data-theme="dark"] | 0,3,0 | palette dark (designed, for Black Box and Blueprint) |
The palette files are generated by scripts/generate-tokens.mjs from scripts/palette-roles.json, the output of the palette script (scripts/palettes.mjs --fix), and copied, never retyped.
Shape and rhythm. Radius is zero everywhere (--radius: 0, every --tome-radius-* step but full), except the filter chips, which are full pills. Rules are 1px: ink for a section's or a list's top, the border colour between rows and cells. The prose measure is 600px. The page lanes are three marginalia columns each side and one prose-pad column, four each side from 1760px.
Type scale. The approved mockups were authored on a 16px root and tome-ui sets html { font-size: 18px }, so every rem value is converted (x 8/9) and the vw terms are kept.
Palettes
| Palette | Ground / surface | Ink / muted | Mark | Opens |
|---|---|---|---|---|
| Gallery (default) | #F6F6F4 / #FFFFFF | #1E2023 / #595D62 | sticker red #F0124B | light |
| Concrete | #E8E5DF / #F4F2EE | #24211D / #57524B | chalk-line blue #0868CF | light |
| Black Box | #121212 / #1C1C1C | #E4E4E0 / #A0A09B | white #FFFFFF | dark |
| Blueprint | #14304F / #1B3B5F | #EAF0F6 / #AFC1D4 | markup yellow #FFEB70 | dark |
Contrast
All ratios are WCAG 2.x. Text must reach at least 4.5. Marks, field outlines, chip edges, the focus ring and the inquiry band's field outline and ring must reach at least 3. The band's hairline has no floor. The test suite re-measures every pair in all eight palette modes from the shipped values, the band pairs included, and pins the tightest ones: Studio-painted text bottoms out at 4.61 (Blueprint dark, error text on its tint) and marks and outlines at 3.06 (Gallery light, the chip edge on the tint).
What it styles
| Hook | Treatment |
|---|---|
Section openers ([data-work-opener], [data-work-kicker], [data-work-heading], [data-work-intro]) | A ruled cell holding the counter-numbered label in the narrow left lane, the heading and one sentence from the reading line. The number is a CSS counter with empty alternative text. |
Title Block cells ([data-title-block], [data-tb-cell], [data-tb-label], [data-tb-value], [data-tb-link]) | Ruled cells in the meta voice: nine across from 1024px, three from 640px, two on phones. |
Marks ([data-mark="dot"], [data-mark="square"]) | The accent as a filled shape, always beside text that says the same thing. |
Chrome header ([data-visible][data-nav-theme][data-overlay], [data-header-wordmark], button[data-mobile-menu-trigger="word"]) | The ground, the surface and hairline once scrolled, the ink-fill call to action, the 125% name, the word menu button. |
work-index hero ([data-work-feature-media], [data-work-feature-copy], [data-work-feature-num], [data-work-feature-title], [data-work-plate-count], [data-work-feature-cta]) | The bleeding 16:10 plate with the settle, the copy beside it, the facts line under both. |
work-index ledger ([data-work-list], [data-work-row], [data-work-num], [data-work-title], [data-work-kind], [data-work-thumb], [data-work-new], [data-work-featured-note], [data-work-thumb-credits], [data-work-more]) | Ruled rows on a subgrid, the work column, the quieted featured row, the credits line. |
[data-appearance] links and buttons (in main, nav and the footer) | The ink-fill square rectangle, 44px; the outline in ink; hover swaps to the ground with a 1px ink edge. |
[data-compliance-label] (every block) | A caption on the ground in the meta voice, never an overlay on a photograph. |
[data-new-tab] | The north-east arrow after the link text, a CSS mask. |
Focus (:focus-visible) | A 2px ring in the ring colour, 3px clear of the element. |
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. The one positional read is the Title Block cell's row start.
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-studio testThe vitest suites cover:
- the theme conformance check in theme mode (Layer-1 surface,
*-textliterals, radius and font slots, the full dark redeclaration, palette coverage for all four palettes in both modes, scoping, no module class names, font pairing) and in platform mode - manifest validation, its agreement with
package.json, registration, and the pending-catalog tripwire for blocks the portfolio pack has not yet registered - every palette defining the full required set in both modes, every value agreeing with the palette script's output, the band rule present in all eight modes
- a contrast test that re-measures every text pair at 4.5 and every mark or boundary at 3 in all eight modes, the inquiry band's pairs included, and pins the tightest pairs
- the A4.1 font pairing, the width tokens, every Archivo rule setting both its weight and its width, the self-hosted face with its licence,
tnumand the axes read out of the woff2 file,tnumacross the width range, and the subset range - hook coverage: every hook this release styles has a selector, the hooks of unreleased treatments are not styled early, and the few that carry behaviour only are listed with the reason
- the measure: every running-text hook resolves to 600px and nothing widens it
- no colour literal outside
src/tokens/, no comment that swallows a rule, no structural or class selector, no hand-coloured shadow, no accent bar, radius zero except the chip, no scrim or filter over a photograph, and every treatment rule scoped to the theme - the reveal switch and timing, the page lanes, the motion tokens, the hero's and the ledger's geometry, and every reduced-motion path
License
Commercial. See LICENSE.md.
Exports
| Import path |
|---|
@wabbit/tome-theme-studio |
@wabbit/tome-theme-studio/manifest |
@wabbit/tome-theme-studio/register |
@wabbit/tome-theme-studio/fonts |
@wabbit/tome-theme-studio/styles.css |
@wabbit/tome-theme-studio/fonts.css |
@wabbit/tome-theme-studio/fonts/* |
Changelog
0.1.xMinor
New theme package
@wabbit/tome-theme-studiofor studios whose portfolio is the sales argument.It is a new package, so the minor bump is purely additive. The first release ships four palettes (Gallery, Concrete, Black Box and Blueprint) with their derived opposite modes, and Archivo at three widths as a self-hosted OFL subset that keeps
tnum.The first treatments cover the base, the section opener with its counter, the Title Block cells, the Navbar 1 header, the home hero (
work-indexlayoutfeature) and the ledger (work-indexlayoutindex), plus the motion tokens with their reduced-motion paths. The accent is a mark and never text, and type never sits on the work.The home-page treatments follow, all through public hooks:
- The Work Index tools: the filter chips with their ink label and muted count, the live count line, the Plates and Ledger toggle with its 44px hit areas, the phone Filter button and the full-height filter sheet.
- The Plates view with one frame per palette: Gallery's wall-coloured mat, Black Box's bare images, Concrete's edge-to-edge tiles and Blueprint's 1px keyline.
- Selected work, with its three-cell Title Block, its credits and the straddle (the last plate overhangs into the next band, which takes the overhang at its top).
- The practice statement, the service rows, the pinned process (the dossier seams mapped to the theme's pin offset, gap and mark), people, and the clients' names with their quote.
- The close as the one dark band, re-pointing both the Layer-1 and the Layer-2 colour names to the palette's band family, and Footer 7 on its own tint zone.
- The numbered, type-only phone menu, the centred header layout and the current-page hook.
The case study is styled too:
project-herois the opener and the lead plate (breadcrumb, the 21:9 lead with its 4:5 phone crop and one settle, the plate line, the h1 at the h2 size, the facts line and "About the project"), with nothing laid over the photograph.plate-sequencestyles all three modes with margin captions pinned at--studio-pin-top(mapped to the pack's--tome-plate-pin-top), the numbered count line with its square mark, credits as the renderer prints them, and the drawings on the surface colour in a 1px frame that takes the palette's ink.- The drawing viewer is a full-screen native dialog with 44px open and close controls, a bar in the meta voice, and a scrolling region that draws its focus ring inside itself.
- The
title-blockcredits, brief and facts bands, thework-indexnextteaser (image first, ink and outline buttons), and thetestimonialclient voice (upright, at display width, on the tint) join the treatments. - The morph CSS classes every project image with
view-transition-classand times thetome-project-*groups and the root cross-fade from--studio-morph. Reduced motion switches every view-transition animation off. The theme declares noview-transition-nameand leaves@view-transitionto tome-motion'sRouteTransition.
Fixes after the first browser pass against the approved home and case study:
- The pinned process lays its plates one per row from 1024 (the pack's phone grid and plate 1's two-track span are reset), in the approved columns (list 1 to 8, plates 10 to 16), and the active phase steps 1 to 5.
- Clients uses the pack's
headingfield; the names align left in their cells, the quote's marks are drawn, and a ledger row used as the heading stays on one line. - The footer reads on the tint in all four palettes (and their dark modes), keeps a newline in the subline, styles the new brand rows, and puts the copyright before the credits line.
- The straddle's statement takes the overhang plus its own rhythm, so the plate's credit sits clear of its heading; the statement styles the new sample note.
- A new
treatments/targets.cssmakes the header links, the name, ledger and service titles, a service's representative project, Plates tile titles, a facts cell's link, a footer line's link and narrow crumbs 44px targets without moving the layout. - The work heading, the ledger and service titles, the chips and the count line share one text edge; the facts cell is 66.5px; passage and About paragraphs have a gap; the crumb slashes centre on their links; the close band's heading clears its paragraph by 24px and a one-step form's legend is visually hidden.
- The pinned offset follows the header: while the header is hidden the process list and plate captions pin at one step from the top, and ride back down when it returns.