From another stack
Building a Dash.fi-compliant UI outside the Svelte monorepo? This is the recipe — tokens, fonts, assets, and a per-stack setup for vanilla JS/HTML/CSS, React (web), React Native, server-rendered backends, and the common build tools.
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.
One CSS file. Drop into any project. Works in vanilla HTML, any framework, any build tool.
/* 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;
}@font-face works in every browser. PP Supply must be self-hosted (commercial license); Bai Jamjuree can come from a CDN.
/* 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;
}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 · 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;
}<!-- 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>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.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.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}
/>
)
);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 — 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.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' }
});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.
# 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>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).
// 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"The token sheet is plain CSS. Every modern bundler imports CSS files; no plugin or loader is required.
// 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 — 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 — 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';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 —
#354CEFis product-facing on the vnext design lineage (Button secondary, LogoApp gradient). Earlier marketing-only guidance is superseded — see /foundations/color for current surface mappings.
The repeatable flow for porting any component to your stack.
- Open /components/<name> in this site.
- Read the Anatomy tab — every dimension, token, composition rule, and non-feature is listed with resolved values.
- Open the Preview tab in your design tool of choice and screenshot the canonical rendering for reference.
- Implement in your stack using the px / hex values from the anatomy, the token sheet above, and your stack's component model.
- 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.