Mental model

Dashbook is the spec, not a runtime for non-Svelte consumers. Three artefacts port across stacks; component shapes ride on top.

  • Tokens — colors, type, spacing, radius, motion. Authored as CSS variables; trivially mirrored as a JS module for React Native. Documented at /foundations/color, /foundations/typography, /foundations/spacing, /foundations/radius, /foundations/motion.
  • Fonts — PP Supply Mono (commercial · Pangram Pangram) and Bai Jamjuree (SIL OFL · free). Self-host the PP file; serve Bai Jamjuree from any CDN or bundle.
  • Assets — the dash.fi wordmark + app icon as SVG. Download from /brand/logo.
  • Components — each /components/<name> page has an anatomy section with exact dimensions, tokens-per-part, composition rules, and explicit non-features. Reimplement directly from that contract.
The token sheet

One CSS file. Drop into any project. Works in vanilla HTML, any framework, any build tool.

css
/* dashfi-tokens.css  ·  drop into any project, link from the page head.
   Sourced from /foundations/color, /foundations/typography, /foundations/radius. */

:root {
	/* Product palette (light) */
	--bg: #faf9f5;          --fg: #0f1412;
	--bg-muted: #f1f0ea;    --fg-muted: #6b6b66;
	--border: #ebeae5;      --input-border: #d3d5da;
	--primary: #0f1412;     --primary-fg: #faf9f5;
	--brand: #267357;       --brand-fg: #ffffff;
	--accent: #EBFF00;      --destructive: #b52217;
	--card: #fefefb;        --popover: #ffffff;
	--ring: #267357;

	/* Type families (see /foundations/typography) */
	--font-sans: 'Bai Jamjuree', ui-sans-serif, system-ui, sans-serif;
	--font-mono: 'PP Supply Mono', ui-monospace, SFMono-Regular, monospace;
	--font-display: 'PP Supply Mono', ui-monospace, monospace;

	/* Radius scale (see /foundations/radius) */
	--radius-none: 0;       --radius-sm: 8px;
	--radius-md: 10px;      --radius-lg: 12px;
	--radius-xl: 16px;      --radius-full: 9999px;

	/* Motion (see /foundations/motion) */
	--dur-fast: 150ms;      --dur-normal: 300ms;
	--easing-out: cubic-bezier(0.16, 1, 0.3, 1);
}

[data-theme='dark'], html.dark {
	--bg: #0f1412;          --fg: #ffffff;
	--bg-muted: #191f1d;    --fg-muted: #819896;
	--border: #1e2928;      --input-border: #1e2928;
	--primary: #ffffff;     --primary-fg: #0f1412;
	--brand: #4db38d;       --brand-fg: #000000;
	--card: #141a18;        --popover: #161d1a;
	--ring: #4db38d;
}
Fonts

@font-face works in every browser. PP Supply must be self-hosted (commercial license); Bai Jamjuree can come from a CDN.

css
/* PP Supply Mono — commercial license (Pangram Pangram).
   Host on a first-party domain; do not redistribute via CDN.
   Bai Jamjuree — SIL Open Font License; safe to bundle anywhere. */

@font-face {
	font-family: 'PP Supply Mono';
	src: url('/fonts/PPSupplyMono-Regular.woff2') format('woff2');
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'PP Supply Mono';
	src: url('/fonts/PPSupplyMono-Ultralight.woff2') format('woff2');
	font-weight: 200;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'Bai Jamjuree';
	src: url('https://cdn.jsdelivr.net/npm/@fontsource/[email protected]/files/bai-jamjuree-latin-400-normal.woff2') format('woff2');
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}
Vanilla HTML + CSS + JS

No framework. Three sheets (tokens, fonts, page styles) + plain markup. Buttons and inputs match the canonical anatomies — 40px button, underline-only input, mono uppercase label.

dashfi-page.css
css
/* dashfi-page.css  ·  page-level resets + the canonical primitive shapes.
   Pairs with dashfi-tokens.css + dashfi-fonts.css. */

body {
	margin: 0;
	background: var(--bg);
	color: var(--fg);
	font-family: var(--font-sans);
	padding: 32px;
}

/* Button — matches /components/button anatomy (default size, 40px) */
.btn {
	height: 40px;
	padding: 0 20px;
	border: none;
	border-radius: var(--radius-md);
	background: var(--primary);
	color: var(--primary-fg);
	font-family: var(--font-sans);
	font-size: 14px;
	font-weight: 500;
	cursor: pointer;
	transition: opacity var(--dur-fast) var(--easing-out);
}
.btn:hover { opacity: 0.8; }

/* Input — matches /components/input anatomy (underline-only) */
.input {
	height: 40px;
	padding: 8px 0;
	width: 100%;
	background: transparent;
	border: none;
	border-bottom: 1px solid var(--input-border);
	font-family: var(--font-sans);
	font-size: 14px;
	color: var(--fg);
	outline: none;
}
.input:focus { border-bottom-color: var(--fg); }

/* Label — matches /components/label anatomy (mono uppercase tracked muted) */
.label {
	font-family: var(--font-mono);
	font-size: 11px;
	color: var(--fg-muted);
	letter-spacing: 0.15em;
	text-transform: uppercase;
}
Markup
html
<!-- Link the three sheets in your page head, then drop the markup. -->
<!-- Stack-agnostic: works in a static .html file, a Rails view, a Django -->
<!-- template, an Express response — anything that renders HTML. -->

<label class="label" for="email">Work email</label>
<input id="email" class="input" type="email" placeholder="[email protected]" />
<button class="btn">Continue</button>
React (web)

Two patterns: (1) Tailwind with the dashfi-tokens.css imported and Tailwind's color/font scales mapped to the CSS vars; (2) plain inline styles using var(--…). The Tailwind path is the smoother developer experience.

Tailwind config
typescript
// tailwind.config.ts (or @theme in app.css for Tailwind v4)
import type { Config } from 'tailwindcss';

export default {
	content: ['./src/**/*.{ts,tsx}'],
	theme: {
		extend: {
			colors: {
				background: 'var(--bg)',
				foreground: 'var(--fg)',
				muted: { DEFAULT: 'var(--bg-muted)', foreground: 'var(--fg-muted)' },
				primary: { DEFAULT: 'var(--primary)', foreground: 'var(--primary-fg)' },
				brand: { DEFAULT: 'var(--brand)', foreground: 'var(--brand-fg)' },
				border: 'var(--border)',
				input: 'var(--input-border)',
				ring: 'var(--ring)',
				popover: 'var(--popover)'
			},
			fontFamily: {
				sans: ['Bai Jamjuree', 'ui-sans-serif', 'system-ui', 'sans-serif'],
				mono: ['PP Supply Mono', 'ui-monospace', 'monospace']
			},
			borderRadius: { md: '10px', lg: '12px', xl: '16px' }
		}
	}
} satisfies Config;
Button component (matches /components/button anatomy)
typescript
// Button.tsx — match /components/button anatomy
import { forwardRef, type ButtonHTMLAttributes } from 'react';

type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
	variant?: 'brand' | 'default' | 'secondary' | 'outline' | 'ghost';
	size?: 'sm' | 'default' | 'lg';
};

const variantClasses: Record<NonNullable<Props['variant']>, string> = {
	default:    'bg-primary text-primary-foreground hover:opacity-80',
	brand:      'bg-brand text-brand-foreground hover:opacity-80',
	secondary:  'bg-[#354cef] text-white hover:opacity-80',     // cobalt — vnext
	outline:    'border border-input bg-transparent hover:bg-muted',
	ghost:      'bg-transparent hover:bg-muted'
};
const sizeClasses: Record<NonNullable<Props['size']>, string> = {
	sm:      'h-9 px-4 text-sm',         // 36
	default: 'h-10 px-5 text-sm',        // 40
	lg:      'h-11 px-8 text-sm'         // 44
};

export const Button = forwardRef<HTMLButtonElement, Props>(
	({ variant = 'default', size = 'default', className = '', ...rest }, ref) => (
		<button
			ref={ref}
			className={`inline-flex items-center justify-center gap-2 font-medium rounded-md transition-opacity active:scale-[0.97] ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}
			{...rest}
		/>
	)
);
React Native

No CSS variables; mirror the token sheet as a JS module and read it from StyleSheet. The resolved px values + hex colors are the contract; layout primitives swap to RN's flex API.

tokens.ts
typescript
// tokens.ts — JS module mirror of dashfi-tokens.css
export const colors = {
	bg:         { light: '#faf9f5', dark: '#0f1412' },
	fg:         { light: '#0f1412', dark: '#ffffff' },
	bgMuted:    { light: '#f1f0ea', dark: '#191f1d' },
	fgMuted:    { light: '#6b6b66', dark: '#819896' },
	border:     { light: '#ebeae5', dark: '#1e2928' },
	inputBorder:{ light: '#d3d5da', dark: '#1e2928' },
	primary:    { light: '#0f1412', dark: '#ffffff' },
	primaryFg:  { light: '#faf9f5', dark: '#0f1412' },
	brand:      { light: '#267357', dark: '#4db38d' },
	brandFg:    { light: '#ffffff', dark: '#000000' },
	ring:       { light: '#267357', dark: '#4db38d' }
};

export const radius = { none: 0, sm: 8, md: 10, lg: 12, xl: 16, full: 9999 };
export const motion = { fast: 150, normal: 300, slow: 500 };
export const fonts = {
	sans: 'BaiJamjuree-Regular',
	mono: 'PPSupplyMono-Regular',
	display: 'PPSupplyMono-Ultralight'
};
Button (React Native)
typescript
// Button.tsx (React Native) — match /components/button anatomy
import { Pressable, Text, StyleSheet, useColorScheme } from 'react-native';
import { colors, radius, fonts } from './tokens';

type Variant = 'brand' | 'default';
type Size = 'sm' | 'default' | 'lg';

export function Button({
	label,
	variant = 'default',
	size = 'default',
	onPress
}: { label: string; variant?: Variant; size?: Size; onPress?: () => void }) {
	const mode = useColorScheme() ?? 'light';
	const bg = variant === 'brand' ? colors.brand[mode] : colors.primary[mode];
	const fg = variant === 'brand' ? colors.brandFg[mode] : colors.primaryFg[mode];
	const height = size === 'sm' ? 36 : size === 'lg' ? 44 : 40;

	return (
		<Pressable
			onPress={onPress}
			style={({ pressed }) => [
				styles.btn,
				{ backgroundColor: bg, height, opacity: pressed ? 0.8 : 1 }
			]}
		>
			<Text style={[styles.label, { color: fg }]}>{label}</Text>
		</Pressable>
	);
}

const styles = StyleSheet.create({
	btn: {
		alignItems: 'center',
		justifyContent: 'center',
		paddingHorizontal: 20,
		borderRadius: radius.md
	},
	label: { fontFamily: fonts.sans, fontSize: 14, fontWeight: '500' }
});
Server-rendered backends (Rails / Django / Express / etc.)

Same token sheet works as a static asset in any backend. Email templates need inlined styles because most clients strip style tags and drop @font-face — fall back to system fonts and inline the brand-critical hex values.

html
# Rails / Django / Express — serve the token sheet as a static asset.
# Same dashfi-tokens.css works in any server-rendered template.

# Rails:    app/assets/stylesheets/dashfi-tokens.css   →  stylesheet_link_tag
# Django:   static/css/dashfi-tokens.css               →  static template tag
# Express:  app.use(express.static('public'))          →  link rel=stylesheet

# For email templates (Gmail/Outlook strip style tags + drop @font-face),
# inline the brand-critical values directly:

<table style="background:#faf9f5;color:#0f1412;font-family:Georgia,serif;">
	<tr><td style="padding:32px;">
		<a href="#" style="background:#0f1412;color:#faf9f5;padding:12px 20px;
			border-radius:10px;text-decoration:none;font-weight:500;">
			Continue
		</a>
	</td></tr>
</table>
Currency + numeric formatting

Intl.NumberFormat is universal — works the same in browser, Node, Deno, Bun. Pair monetary values with --font-mono for tabular alignment (see /foundations/typography → tabular numbers).

javascript
// Currency + numeric formatting — same logic backend or frontend.
// Pair with --font-mono for tabular display.

const fmt = new Intl.NumberFormat('en-US', {
	style: 'currency',
	currency: 'USD',
	minimumFractionDigits: 2
});
fmt.format(1240.5);   // "$1,240.50"

const compact = new Intl.NumberFormat('en-US', {
	notation: 'compact',
	style: 'currency',
	currency: 'USD'
});
compact.format(1240500);   // "$1.24M"
Build tools

The token sheet is plain CSS. Every modern bundler imports CSS files; no plugin or loader is required.

Vite / Webpack / esbuild / Parcel / Rollup / Bun
typescript
// vite.config.ts — no special handling needed.
// Just import the CSS once:

// src/main.ts
import './dashfi-tokens.css';
import './dashfi-fonts.css';
import './app.css';

// Vite, Webpack, esbuild, Parcel, Rollup, Bun — all bundle CSS @imports the same way.
Tailwind v4 (Tailwind reads CSS vars natively via @theme)
css
/* Tailwind v4 — single app.css, no config file needed. */
@import 'tailwindcss';
@import './dashfi-tokens.css';
@import './dashfi-fonts.css';

@theme {
	--color-background: var(--bg);
	--color-foreground: var(--fg);
	--color-primary: var(--primary);
	--color-primary-foreground: var(--primary-fg);
	--color-brand: var(--brand);
	--color-brand-foreground: var(--brand-fg);
	--color-border: var(--border);
	--color-input: var(--input-border);
	--color-ring: var(--ring);
	--font-sans: 'Bai Jamjuree', ui-sans-serif, system-ui, sans-serif;
	--font-mono: 'PP Supply Mono', ui-monospace, monospace;
	--radius-md: 10px;
}
PostCSS
css
/* PostCSS — drop dashfi-tokens.css before your app stylesheet.
   No plugin required — the file is plain CSS. */

/* postcss.config.cjs */
module.exports = {
	plugins: { autoprefixer: {}, cssnano: {} }
};

/* src/styles.css */
@import './dashfi-tokens.css';
@import './dashfi-fonts.css';
@import './app.css';
License + asset notes

One commercial dependency. Treat carefully.

  • PP Supply Mono — commercial license from Pangram Pangram. Each project must hold its own seat. Self-host the woff2 files on a first-party domain; do not redistribute via public CDN. Dash.fi's license covers internal product + marketing surfaces.
  • Bai Jamjuree — SIL Open Font License. Bundle freely. CDN delivery via jsdelivr / unpkg / @fontsource is recommended.
  • Logos + wordmark — see /brand/logo for the canonical SVGs, colorways, and clearance rules. Partner usage follows /press.
  • Cobalt#354CEF is product-facing on the vnext design lineage (Button secondary, LogoApp gradient). Earlier marketing-only guidance is superseded — see /foundations/color for current surface mappings.
Working from a Dashbook component page

The repeatable flow for porting any component to your stack.

  1. Open /components/<name> in this site.
  2. Read the Anatomy tab — every dimension, token, composition rule, and non-feature is listed with resolved values.
  3. Open the Preview tab in your design tool of choice and screenshot the canonical rendering for reference.
  4. Implement in your stack using the px / hex values from the anatomy, the token sheet above, and your stack's component model.
  5. If your component diverges from the canonical (e.g., adds a prop the canonical does not have), flag it as an intentional fork at the call site — re-implementations are expected to be 1:1 with Dashbook unless explicitly diverged.