@letterstory/design
v0.1.16
Published
Letterstory design tokens, the phantom theme registry, and the design principles — one source for every Letterstory repo.
Readme
@letterstory/design
The cross-repo layer of Letterstory's design system: the tokens (colour, radius, font, type scale), the phantom theme registry Lettersprite renders, and the design principles as a shipped file. Framework-neutral data with one thin adapter per framework, so every Letterstory repo reads the same values and none of them copies a palette.
Zero runtime dependencies, no build step to install. Published from this directory by
.github/workflows/publish-design.yml (npm Trusted Publishing, on merge to main);
design-version-guard.yml requires a version bump whenever a shipped file changes.
What ships
| File | What it is |
| --------------------- | -------------------------------------------------------------------------------------------------- |
| tokens.json | Source. The token values as one grouped object: color.{light,dark}, radius, font, size |
| tokens.css | The tokens as CSS custom properties on :root, with the dark overrides under .dark |
| tailwind-v4.css | A @theme inline block mapping the custom properties to Tailwind v4 theme keys |
| tailwind-v3.cjs | A Tailwind v3 preset whose theme.extend references the custom properties |
| styled.js + .d.ts | A plain object of resolved values (no stylesheet needed) for styled-components and similar |
| themes.json | Source. The phantom theme registry: groups and themes (value, label, hint, group, tags) |
| themes.d.ts | Literal-union types for the registry (PhantomBlogTheme, PhantomBlogThemeGroup, …) |
| PRINCIPLES.md | The design principles, copied from the root AGENTS.md of the letterstory repo |
tokens.json and themes.json are the sources. Everything else is generated by
scripts/build.mjs and committed; a test regenerates and diffs, so editing a generated file
by hand fails CI. To change a value: edit the JSON, run node design/scripts/build.mjs,
commit both.
In tokens.json, color.dark holds only the values that differ from light (exactly what the
.dark block sets); styled.js resolves that into a full dark palette. Derived tokens keep
their CSS expressions (brand-light is oklch(from var(--brand) …), radius.sm is
calc(var(--radius) - 4px)), so rebranding is one value: color.light.brand.
Consuming it
Tailwind v4 (letterstory, lettersprite): import the tokens, then the mapping, after Tailwind itself.
@import "tailwindcss";
@import "@letterstory/design/tokens.css";
@import "@letterstory/design/tailwind-v4.css";bg-brand, text-accent-peach, text-rising, rounded-lg, text-2xs now resolve to the
tokens. --font-sans / --font-mono are set under Tailwind's own names, so font-sans and
preflight's default pick up DM Sans with no mapping. Dark mode is the .dark class
(@custom-variant dark (&:is(.dark *)) in your stylesheet).
Tailwind v3 (lettertrace, kernels): load tokens.css on the page and add the preset.
// tailwind.config.js
module.exports = {
darkMode: "class",
presets: [require("@letterstory/design/tailwind-v3")],
};styled-components (the marketing repo): a resolved object, no stylesheet.
import { tokens } from "@letterstory/design";
// tokens.color.light["accent-peach"] === "#ee724a"; tokens.color.dark.background; tokens.font.sansPlain CSS (admin, the company site): import tokens.css and use var(--brand),
var(--accent-peach), var(--radius), var(--font-size-2xs) …
Theme registry (lettersprite, letterstory):
import registry from "@letterstory/design/themes"; // themes.json, typed by themes.d.ts
import type { PhantomBlogTheme } from "@letterstory/design/themes";The registry says which looks a customer may choose; src/themes/ in lettersprite says
what each one is. Neither half is complete alone, and an unregistered theme is unpickable
while an unimplemented one silently renders the default. src/themes/registry.test.ts there
asserts both directions against this package, so a theme added at either end cannot land alone.
Where the values come from
The letterstory app is a consumer, not a source: src/app/globals.css imports
design/tokens.css and design/tailwind-v4.css by relative path and defines no token of its
own, and src/types/deployments.ts re-exports themes.json as PHANTOM_BLOG_THEMES. Edit
design/tokens.json or design/themes.json, never globals.css. The principles in
PRINCIPLES.md are generated from the root AGENTS.md; edit that.
Versioning
Bump version in package.json whenever a shipped file changes (the guard enforces it);
the merge to main publishes. Adding a token or a theme is a minor bump; changing a value is
a patch; renaming or removing a token is a major.
