@bomalabs/design
v0.0.2
Published
Boma design system — tokens, themes, CSS utilities, and theme runtime
Downloads
23
Readme
@bomalabs/design
Boma design system — tokens, themes, CSS utilities, and theme runtime.
Monochrome-first. Neutrals drive the chrome. Colour is reserved for status, focus, AI-active states, and recording states.
Installation
npm install @bomalabs/designTypeScript is supported out of the box — type declarations are included.
Bazel (this monorepo)
aspect_rules_js builds the package hermetically:
# Concatenated dist/ tree
bazel build //packages/design:dist
# npm_package suitable for publishing / linking
bazel build //packages/design:pkg
# or: bazel build //packages/designpnpm lockfile lives at packages/design/pnpm-lock.yaml. Refresh with:
cd packages/design && pnpm install --lockfile-onlyUsage by project type
Next.js (App Router) + Tailwind v4
app/globals.css
@import "tailwindcss";
@import "@bomalabs/design/themes";
@theme {
--font-sans: var(--boma-font-sans);
--font-mono: var(--boma-font-mono);
--color-canvas: var(--boma-surface-canvas);
--color-shell: var(--boma-surface-shell);
--color-surface: var(--boma-surface-primary);
--color-surface-secondary: var(--boma-surface-secondary);
--color-surface-hover: var(--boma-surface-hover);
--color-content: var(--boma-text-primary);
--color-content-secondary: var(--boma-text-secondary);
--color-content-muted: var(--boma-text-muted);
--color-border: var(--boma-border-default);
--color-border-subtle: var(--boma-border-subtle);
--color-border-strong: var(--boma-border-strong);
--color-action: var(--boma-action-primary);
--color-action-hover: var(--boma-action-primary-hover);
--color-action-text: var(--boma-action-primary-text);
--color-success: var(--boma-success);
--color-success-surface: var(--boma-success-surface);
--color-warning: var(--boma-warning);
--color-warning-surface: var(--boma-warning-surface);
--color-danger: var(--boma-danger);
--color-danger-surface: var(--boma-danger-surface);
--radius-sm: var(--boma-radius-sm);
--radius-md: var(--boma-radius-md);
--radius-lg: var(--boma-radius-lg);
--shadow-sm: var(--boma-shadow-sm);
--shadow-md: var(--boma-shadow-md);
--shadow-lg: var(--boma-shadow-lg);
}app/layout.tsx — inline boot script before paint to prevent theme flash:
import type { Metadata } from "next";
import "./globals.css";
// Vite / Next with raw import support:
// import bootScript from "@bomalabs/design/js/boot.js?raw";
// Or read it at build time:
import { readFileSync } from "fs";
import { join } from "path";
const bootScript = readFileSync(
join(process.cwd(), "node_modules/@bomalabs/design/dist/js/boot.js"),
"utf8"
);
export const metadata: Metadata = { title: "Your App" };
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: bootScript }} />
</head>
<body>{children}</body>
</html>
);
}
suppressHydrationWarningis required becauseboot.jsmutatesclassanddata-themebefore React hydrates, causing an intentional server/client mismatch.
Next.js (App Router) — no Tailwind
app/globals.css
@import "@bomalabs/design/fonts/urbanist.css";
@import "@bomalabs/design/themes";
@import "@bomalabs/design/css";Then use CSS custom properties directly or with CSS Modules — see Using tokens in CSS Modules below.
Vite + React + TypeScript
src/main.tsx or src/index.tsx
import "@bomalabs/design/themes";
import "@bomalabs/design/css";index.html — add boot script before your CSS link:
<script src="/node_modules/@bomalabs/design/dist/js/boot.js"></script>
<link rel="stylesheet" href="/src/main.css">Or inline it:
<script>
// paste contents of node_modules/@bomalabs/design/dist/js/boot.js
</script>Plain HTML / vanilla JS
<head>
<script src="/path/to/@bomalabs/design/dist/js/boot.js"></script>
<link rel="stylesheet" href="/path/to/@bomalabs/design/dist/boma.css">
<script src="/path/to/@bomalabs/design/dist/js/theme.js" defer></script>
</head>
<body>
<div id="theme-selector"></div>
<script>
document.addEventListener("DOMContentLoaded", function () {
BomaTheme.initTheme();
BomaTheme.mountThemeSelector(document.getElementById("theme-selector"));
});
</script>
</body>Theme switcher — TypeScript + React
"use client";
import { useEffect, useState } from "react";
import { setTheme, getStoredTheme } from "@bomalabs/design";
import type { ThemePreference } from "@bomalabs/design";
const options: { value: ThemePreference; label: string }[] = [
{ value: "light", label: "Light" },
{ value: "system", label: "System" },
{ value: "dark", label: "Dark" },
];
export function ThemeSelector() {
const [current, setCurrent] = useState<ThemePreference>("system");
useEffect(() => {
setCurrent(getStoredTheme() ?? "system");
}, []);
function handleChange(pref: ThemePreference) {
setTheme(pref);
setCurrent(pref);
}
return (
<div role="radiogroup" aria-label="Theme" className="boma-theme-selector">
{options.map(({ value, label }) => (
<button
key={value}
role="radio"
aria-checked={current === value}
aria-label={label}
className="boma-theme-selector-btn"
onClick={() => handleChange(value)}
/>
))}
</div>
);
}Using tokens in CSS Modules
.card {
background: var(--boma-surface-primary);
border: 1px solid var(--boma-border-default);
border-radius: var(--boma-radius-md);
box-shadow: var(--boma-shadow-sm);
padding: var(--boma-space-5);
transition: box-shadow var(--boma-duration-normal) var(--boma-easing-standard);
}
.card:hover {
box-shadow: var(--boma-shadow-md);
}Using JSON tokens in JavaScript / TypeScript
For chart configs, canvas drawing, or anywhere you need token values in JS:
import primitives from "@bomalabs/design/tokens/json/primitives";
import semantic from "@bomalabs/design/tokens/json/semantic";
// Static values
const success = primitives.status.success; // "#059669"
// Live values from the current theme (respects dark mode)
function token(name: string): string {
return getComputedStyle(document.documentElement)
.getPropertyValue(name)
.trim();
}
const canvasBg = token("--boma-surface-canvas");Tailwind v3 preset
// tailwind.config.ts
import type { Config } from "tailwindcss";
import bomaPreset from "@bomalabs/design/tailwind/preset";
export default {
presets: [bomaPreset],
content: ["./src/**/*.{ts,tsx}"],
} satisfies Config;Tailwind coexistence
Pack CSS ships in cascade layers (base / components / utilities) so it
plays cleanly with Tailwind apps that style CTAs in @layer components.
Buttons:
| Class | Use |
|-------|-----|
| .btn-primary / .boma-btn-primary | filled high-emphasis CTA |
| .btn-secondary / .boma-btn-secondary | outlined |
| .btn-quiet / .boma-btn-quiet | ghost / icon chrome |
Prefer importing @bomalabs/design/themes (tokens only) when the app defines
its own component CSS, or load @bomalabs/design/css / boma.css for the
full pack. Do not rely on unlayered button { color: inherit } resets — they
defeat layered button colour in dark mode.
Fonts
Urbanist is the Boma UI typeface. Three loading options:
Option A — Google Fonts (recommended for most projects)
Add to your HTML <head>:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Urbanist:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap" rel="stylesheet">Option B — Fontsource (npm, recommended for Next.js)
npm install @fontsource-variable/urbanist// app/layout.tsx or _app.tsx
import "@fontsource-variable/urbanist";
import "@fontsource-variable/urbanist/wght-italic.css";Option C — next/font/google (Next.js only, best performance)
// app/layout.tsx
import { Urbanist } from "next/font/google";
const urbanist = Urbanist({
subsets: ["latin"],
weight: ["400", "500", "600", "700"],
variable: "--font-urbanist",
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={urbanist.variable} suppressHydrationWarning>
<body>{children}</body>
</html>
);
}Token reference
| Category | CSS custom property prefix | Example |
|---|---|---|
| Neutral scale | --boma-neutral-{0–1000} | --boma-neutral-900 |
| Surfaces | --boma-surface-{role} | --boma-surface-canvas |
| Text | --boma-text-{role} | --boma-text-muted |
| Borders | --boma-border-{role} | --boma-border-default |
| Actions | --boma-action-{role} | --boma-action-primary |
| Status | --boma-{success\|warning\|danger\|information} | --boma-danger |
| Status surfaces | --boma-{status}-surface | --boma-success-surface |
| Focus | --boma-focus-ring | |
| Spacing | --boma-space-{1–8} | --boma-space-4 (16px) |
| Radius | --boma-radius-{sm\|md\|lg\|xl\|2xl\|3xl\|full} | |
| Shadow | --boma-shadow-{sm\|md\|lg} | |
| Typography | --boma-font-{sans\|mono} | |
| Motion | --boma-duration-{fast\|normal\|slow} | |
| Layout | --boma-layout-max, --boma-header-height | |
TypeScript API
import {
initTheme,
setTheme,
getStoredTheme,
getResolvedTheme,
resolveTheme,
prefersDark,
STORAGE_KEY,
} from "@bomalabs/design";
import type {
ThemePreference, // "light" | "dark" | "system"
ResolvedTheme, // "light" | "dark"
ThemeChangeListener,
BomaThemeAPI, // shape of window.BomaTheme
} from "@bomalabs/design";Design principles
- Monochrome chrome — neutrals first, colour only for status, focus, AI, and recording states
- Semantic tokens over primitives — use
--boma-surface-primarynot--boma-neutral-0 - No hard-coded hex in new code
- Urbanist only — no Inter, Georgia, or Google Sans for Boma UI
- Intelligence gradient (
--boma-intelligence-gradient) is reserved for active AI interactions only - 44px minimum touch targets on all interactive elements
Prohibited patterns
/* ❌ Hard-coded hex */
background: #ffffff;
/* ✅ Semantic token */
background: var(--boma-surface-canvas);
/* ❌ Primitive token in component CSS */
color: var(--boma-neutral-900);
/* ✅ Semantic token */
color: var(--boma-text-primary);