Cajón Design System

Installation

How to consume @cajon-ui/react inside the monorepo.

@cajon-ui/react resolves through the pnpm workspace, so there is no install step.

1. Import the stylesheet once

At your app root (for example, the App Router root layout), import the single design-system stylesheet. It ships the tokens, all three themes, dark mode, and component styles:

import '@cajon-ui/react/styles.css';

2. Wrap your tree in the provider

import { ThemeProvider } from '@cajon-ui/react';

export default function RootLayout({ children }) {
  return <ThemeProvider>{children}</ThemeProvider>;
}

3. Use components

import { Button, Card, Badge, Input } from '@cajon-ui/react';

export function Example() {
  return (
    <Card header="Profile">
      <Badge tone="success">Active</Badge>
      <Input label="Name" placeholder="Ada Lovelace" />
      <Button>Save</Button>
    </Card>
  );
}

Fonts

The brand and default font families — Inter (sans), Plus Jakarta Sans (default display), Roboto (default body), and Geist Mono — are self-hosted: their woff2 ship inside the package and styles.css declares them via @font-face. Production needs no runtime connection to Google Fonts, so it works under a strict CSP and offline.

The 14 public "mood" themes' fonts still load from the Google Fonts CDN (also via styles.css). If you use a mood theme, warm the connection in your <head>:

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />

If you use only the tokens (@cajon-ui/react/styles/tokens-only.css) and skip the component stylesheet, add the self-hosted faces explicitly:

import '@cajon-ui/react/styles/tokens-only.css';
import '@cajon-ui/react/styles/fonts.css';

next/font opt-in (Next.js)

For zero layout shift, a Next app can load the same woff2 through next/font/local and point a --cajon-font-* token at the generated family. This replaces the package's @font-face for that family — use the full styles.css for the mood themes, or tokens-only.css and skip fonts.css if you self-host every brand family this way. Copy the woff2 from the package (@cajon-ui/react/src/styles/fonts/) into your app's fonts/ dir so the src path is stable under pnpm:

import localFont from 'next/font/local';

const sans = localFont({
  src: [
    { path: './fonts/inter-latin-400-normal.woff2', weight: '400', style: 'normal' },
    { path: './fonts/inter-latin-600-normal.woff2', weight: '600', style: 'normal' },
  ],
  variable: '--cajon-font-sans',
  display: 'swap',
});
// add `sans.variable` to your <html> className; the token now resolves to Inter

This is exactly what apps/harmonica already does with its own copies — the opt-in just lets it (and new apps) share the design system's font files.

Next.js note

@cajon-ui/react ships TypeScript and CSS from source, so a Next.js consumer must list it under transpilePackages:

// next.config.mjs
const config = {
  transpilePackages: ['@cajon-ui/react'],
};

React and react-dom are peer dependencies (range >=18), so the consuming app supplies the single React copy — there is never a second copy in the bundle.