@bannerboy/playground
v1.0.1
Published
Playground Design System — Precis Digital
Maintainers
Keywords
Readme
Playground Design System
Precis Digital’s light, playful pastel foundation — tokens, typography, components, and icons in one package.
npm: @bannerboy/playground · Figma: Playground Design System · Preview: run npm run preview
Contents
- For the team
- Install
- Quick start
- Package structure
- Design principles
- Tokens
- Typography
- Components
- Icons
- Dark mode
- Frameworks
- Preview & development
- Publishing
For the team
| Role | Start here |
| ---- | ---------- |
| Designer | Figma file (link above) → match tokens & text styles → check live preview |
| Frontend | Install package → import @bannerboy/playground/css → use .pg-* classes |
| New joiner | Run npm run preview locally → browse foundations + components side by side |
Naming conventions
| Kind | Pattern | Example |
| ---- | ------- | ------- |
| CSS classes | pg- prefix | .pg-btn, .pg-heading-medium |
| CSS variables | -- prefix | --color-accent-green, --sp-4 |
| npm imports | @bannerboy/playground/… | @bannerboy/playground/css |
Three layers — use the right one
| Layer | When | Example |
| ----- | ---- | ------- |
| Tokens | Custom CSS, JS/TS, Tailwind preset | var(--font-text-medium-size) |
| Typography utilities | All visible copy in HTML/JSX | .pg-text-medium, .pg-heading-small |
| Component classes | Layout & chrome only | .pg-modal, .pg-card-slide |
Stack typography on components:
<p class="pg-modal__title pg-heading-medium">Invite people</p>
<p class="pg-modal__desc pg-text-medium pg-muted">Need a colleague to help you out?</p>Install
npm install @bannerboy/playgroundRequires access to the @bannerboy scope on npm. See PUBLISHING.md for setup.
Alternative (no npm registry):
npm install github:Precis-Digital/playground-design-systemQuick start
HTML
<link rel="stylesheet" href="node_modules/@bannerboy/playground/fonts.css" />
<link rel="stylesheet" href="node_modules/@bannerboy/playground/playground.css" />React / Vite
// main.tsx
import "@bannerboy/playground/fonts";
import "@bannerboy/playground/css";import { color, spacing } from "@bannerboy/playground";
export function Welcome() {
return (
<div className="pg-card">
<h1 className="pg-heading-medium">Hello</h1>
<p className="pg-text-medium">Built with Playground.</p>
<button type="button" className="pg-btn pg-btn--primary">
Get started
</button>
</div>
);
}TypeScript tokens
import { color, spacing, radius, typography, alert, gray } from "@bannerboy/playground";Package structure
@bannerboy/playground
├── playground.css ← import this (tokens + base + components)
├── fonts.css ← optional Google Fonts loader
├── tokens.css ← CSS custom properties only
├── base.css ← reset + typography utilities
├── components.css ← all component styles (imports below)
├── components/
│ ├── core.css ← buttons, badges, inputs, cards shell
│ ├── cards.css
│ ├── modals.css
│ ├── loaders.css
│ ├── tooltips.css
│ └── headers.css
├── dist/ ← compiled tokens.ts + theme helpers
├── assets/
│ ├── icons/ ← 790 SVG icons (outlined + filled)
│ └── *.svg ← logos
└── preview/ ← local docs site (not published to npm)Exports map
| Import path | What you get |
| ----------- | ------------ |
| @bannerboy/playground | TypeScript tokens + theme API |
| @bannerboy/playground/css | Full stylesheet |
| @bannerboy/playground/fonts | Inter + Space Grotesk loader |
| @bannerboy/playground/tokens.css | Tokens only |
| @bannerboy/playground/base.css | Typography utilities only |
| @bannerboy/playground/components.css | Components only |
| @bannerboy/playground/tailwind | Tailwind preset |
| @bannerboy/playground/assets/* | Icons, logos |
Design principles
- Light-mode first — dark mode is supported via
data-theme="dark". - One accent per surface — green, purple, and yellow are equals; pick one per view.
- Body text uses
--ink(#1F242B), not--color-core-black(#000). - Brand colours never flip in dark mode — pastels,
--ink, and core black stay fixed. - Sentence case on buttons — no uppercase CTAs.
- Figma is source of truth — tokens marked
[Figma]in code match variables exactly.
Tokens
Primary & accent
| Token | Value | Use |
| ----- | ----- | --- |
| --color-core-black | #000000 | Brand swatch, strong borders |
| --color-core-neutral-grey | #F6F5F1 | Paper / recessed surfaces |
| --color-accent-green | #CDFAA5 | Accent surface |
| --color-accent-purple | #D2ABFF | Accent surface |
| --color-accent-yellow | #FFF07B | Accent surface |
| --ink | #1F242B | Text on light surfaces |
Surfaces & text (theme-aware)
| Token | Light | Dark |
| ----- | ----- | ---- |
| --bg-base | #FFFFFF | #141414 |
| --bg-paper | #F6F5F1 | #0A0A0A |
| --text-primary | --ink | white |
| --text-secondary | 60% ink | 64% white |
| --text-muted | 40% ink | 40% white |
Alert colours
Pair background + text for alert banners and inline messages.
| Alert | Background | Text |
| ----- | ---------- | ---- |
| Success | --alert-success-bg · hsl(120, 60%, 96%) | --alert-success-text · hsl(120, 45%, 30%) |
| Warning | --alert-warning-bg · hsl(45, 90%, 95%) | --alert-warning-text · hsl(40, 70%, 45%) |
| Error | --alert-error-bg · hsl(0, 80%, 96%) | --alert-error-text · hsl(0, 65%, 45%) |
Spacing (4px base)
| Token | Size |
| ----- | ---- |
| --sp-1 | 4px |
| --sp-2 | 8px |
| --sp-3 | 12px |
| --sp-4 | 16px |
| --sp-5 | 20px |
| --sp-6 | 24px |
| --sp-8 | 32px |
| --sp-10 | 40px |
| --sp-12 | 48px |
| --sp-16 | 64px |
Corner radius
| Token | Size | Typical use |
| ----- | ---- | ----------- |
| --radius-xs | 4px | Chips |
| --radius-sm | 6px | Buttons, inputs |
| --radius-md | 8px | Cards, modals |
| --radius-lg | 12px | Large panels |
| --radius-xl | 16px | Marketing surfaces |
| --radius-pill | 999px | Tags, pill CTAs |
Grey ramp
--gray-50 … --gray-900 — warm neutral scale for borders, disabled states, and UI chrome.
Typography
Display & Heading — Space Grotesk Bold
Text — Inter Regular (default), Medium (.pg-medium), SemiBold (.pg-semibold)
Display
| Class | Size | Line-height | Tracking |
| ----- | ---- | ----------- | -------- |
| .pg-display-large | 96px | 95% | −4% |
| .pg-display-medium | 76px | 100% | −3% |
| .pg-display-small | 64px | 100% | −2% |
Heading
| Class | Size | Line-height | Tracking |
| ----- | ---- | ----------- | -------- |
| .pg-heading-x-large | 48px | 115% | −1.5% |
| .pg-heading-large | 38px | 120% | −1.5% |
| .pg-heading-medium | 28px | 130% | −1.5% |
| .pg-heading-small | 22px | 140% | −1.5% |
| .pg-heading-x-small | 16px | 140% | 0% |
Text
| Class | Size |
| ----- | ---- |
| .pg-text-x-large | 24px |
| .pg-text-large | 20px |
| .pg-text-medium | 16px |
| .pg-text-small | 14px |
| .pg-text-x-small | 12px |
All text sizes use 150% line-height and 0% tracking.
Utilities
| Class | Purpose |
| ----- | ------- |
| .pg-medium | Inter Medium (500) |
| .pg-semibold | Inter SemiBold (600) |
| .pg-muted | Secondary copy colour |
| .pg-eyebrow | 12px uppercase section label |
Components
Core
| Element | Classes |
| ------- | ------- |
| Card shell | .pg-card · .pg-card--paper · .pg-card--dark |
| Buttons | .pg-btn--primary · .pg-btn--outline · .pg-btn--accent-{green\|purple\|yellow} |
| Pill CTAs | .pg-btn--action · .pg-btn--action-outline · .pg-btn--sm |
| Badges | .pg-badge · .pg-badge--{green\|purple\|yellow} |
| Inputs | .pg-input · .pg-input--underline · .pg-input--embed |
<button type="button" class="pg-btn pg-btn--primary">Save</button>
<input type="text" class="pg-input" placeholder="Email" />
<span class="pg-badge pg-badge--purple">Draft</span>Cards
From Figma Cards (node 136:3766).
| Pattern | Classes |
| ------- | ------- |
| Slide card | .pg-card-slide (+ --selected, --simple) |
| Template card | .pg-card-template |
| Image split | .pg-card-split (image column first in DOM) |
| CTA split | .pg-cta-card (+ .pg-cta-card__panel--invert) |
Modals
From Figma Modals (node 136:3751).
| Piece | Classes |
| ----- | ------- |
| Shell | .pg-modal · .pg-modal__close · .pg-modal__title · .pg-modal__desc |
| Fields | .pg-modal-field · .pg-input |
| Dropdown | .pg-modal-dropdown__trigger · .pg-modal-dropdown__list · .pg-modal-dropdown__option |
| Wizard | .pg-modal-progress · [data-wizard-step] |
| Success | .pg-modal-success |
| Tags | .pg-modal-tags · .pg-modal-tag |
Modal copy always uses typography utilities:
<div class="pg-modal">
<p class="pg-modal__title pg-heading-medium">Request template</p>
<p class="pg-modal__desc pg-text-medium pg-muted">Missing a template?</p>
<!-- fields… -->
</div>Loaders
From Figma Loaders (node 136:3773).
| Pattern | Class |
| ------- | ----- |
| Dot ring | .pg-loader · .pg-loader--sm |
| Text + dots | .pg-loader-text |
Both respect prefers-reduced-motion.
Tooltips
From Figma Tooltips (node 136:3775).
<div class="pg-tooltip">
<button type="button" class="pg-tooltip__trigger" aria-label="Help">…</button>
<div class="pg-tooltip__bubble">
<p class="pg-tooltip__title pg-text-small pg-medium">Title</p>
<p class="pg-tooltip__desc pg-text-x-small pg-muted">Description</p>
</div>
</div>Add .pg-tooltip--open to force visible (docs/screenshots).
Headers
From Figma Headers (node 140:2469).
| Pattern | Classes |
| ------- | ------- |
| Page header | .pg-header · .pg-header--compact |
| Crumbs / meta | .pg-header__crumbs · .pg-header__meta |
| Card header | .pg-card-header · .pg-card-header__kebab |
Icons
790 SVG icons in assets/icons/ — outlined/ and filled/, grouped by category.
import icon from "@bannerboy/playground/assets/icons/outlined/arrows/chevron-down.svg";Icons use currentColor — they inherit text colour. In dark mode, inline SVGs flip to white automatically (accent buttons keep --ink).
Browse the full set: npm run preview → Icons section.
Dark mode
Semantic surfaces, text, and borders flip when data-theme="dark" is set on <html>. Brand pastels and --ink stay fixed.
import {
setPlaygroundTheme,
persistPlaygroundTheme,
initPlaygroundTheme,
} from "@bannerboy/playground";
setPlaygroundTheme("dark"); // one-shot
persistPlaygroundTheme("dark"); // + localStorage
initPlaygroundTheme(); // restore on loadReact toggle example:
import { useEffect, useState } from "react";
import {
getPlaygroundTheme,
persistPlaygroundTheme,
type PlaygroundTheme,
} from "@bannerboy/playground";
export function ThemeToggle() {
const [theme, setTheme] = useState<PlaygroundTheme>("light");
useEffect(() => setTheme(getPlaygroundTheme()), []);
function toggle() {
const next = theme === "dark" ? "light" : "dark";
persistPlaygroundTheme(next);
setTheme(next);
}
return (
<button type="button" className="pg-btn pg-btn--outline" onClick={toggle}>
{theme === "dark" ? "Light" : "Dark"}
</button>
);
}Frameworks
Next.js (App Router)
// app/layout.tsx
import "@bannerboy/playground/css";
import { Inter, Space_Grotesk } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
weight: ["400", "500", "600"],
variable: "--font-inter",
});
const spaceGrotesk = Space_Grotesk({
subsets: ["latin"],
weight: ["700"],
variable: "--font-space-grotesk",
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${inter.variable} ${spaceGrotesk.variable}`}>
<body>{children}</body>
</html>
);
}Map font variables in global CSS (skip @bannerboy/playground/fonts when using next/font):
:root {
--font-primary: var(--font-space-grotesk), system-ui, sans-serif;
--font-secondary: var(--font-inter), system-ui, sans-serif;
}Tailwind (optional)
// tailwind.config.js
import playground from "@bannerboy/playground/tailwind";
export default {
presets: [playground],
content: ["./src/**/*.{js,ts,jsx,tsx}"],
};Import @bannerboy/playground/css alongside Tailwind for component classes, or use the preset for token-aligned utilities only.
Preview & development
Local reference site with foundations, live component demos, and icon browser.
npm run preview
# → http://localhost:4173After editing demos or icons:
npm run preview:buildBuild TypeScript output:
npm run buildContributing checklist
- Match Figma tokens — don’t invent one-off colours or radii.
- Component CSS = layout/chrome only; typography = utility classes in markup.
- One accent colour per surface.
- Run preview and check light + dark before opening a PR.
- Bump
versioninpackage.jsonbefore npm publish.
Publishing
Package publishes to npm as @bannerboy/playground. Source stays on GitHub.
See PUBLISHING.md for npm login, 2FA, and install instructions for consuming repos.
