@chan.run/design
v0.3.0
Published
Shared Chakra UI v3 theme and component library for chan.run
Readme
@chan.run/design
Shared Chakra UI v3 theme and brand-asset library for the chan.run ecosystem. Single source of truth for tokens, components, product icons, wordmarks, and favicons. Every site, app, and product UI imports from this package — never inline an SVG, never copy a PNG.
Install
The package is published to npm and pinned by every workspace consumer:
// package.json
{
"dependencies": {
"@chakra-ui/react": "3.35.0",
"@chan.run/design": "^0.2.0",
"next-themes": "*",
"react": "19.2.4"
}
}@chakra-ui/react, next-themes, and react are peer deps — keep them aligned to the versions in this package's peerDependencies to avoid duplicate Chakra installs (which produce structural type errors when passing system between packages).
Theme + provider
import { ChakraProvider } from "@chakra-ui/react";
import { system } from "@chan.run/design";
import { ThemeProvider } from "next-themes";
<ChakraProvider value={system}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</ChakraProvider>Tokens live in the chan.* namespace (chan.bg, chan.text, chan.orange, …) — see src/theme.ts and docs/design-system.md for the full taxonomy.
Brand marks
Two flavors: the chan.run umbrella brand (CHAN[•]RUN, dot accent) and per-product marks (SEA[M], ENTROP[Y], …, last-letter accent). Rules locked per docs/brand-marks.md.
React components
import {
Wordmark, // CHAN[•]RUN — umbrella brand
SeamIcon, SeamWordmark,
EntropyIcon, EntropyWordmark,
RestunnelIcon, RestunnelWordmark,
EnsureIcon, EnsureWordmark,
SlidesIcon, SlidesWordmark,
PRODUCT_ICONS, // Record<name, ComponentType>
PRODUCT_WORDMARKS,
} from "@chan.run/design";
// Themed React component — switches with light/dark mode automatically
<HStack gap="3">
<SeamIcon />
<SeamWordmark fontSize="22px" />
</HStack>
// Loop products from the registry
{Object.entries(PRODUCT_ICONS).map(([name, Icon]) => <Icon key={name} />)}<Wordmark pulseDot /> opacity-cycles the dot via the chan-pulse keyframe — used by Splash for loading states.
Raw SVG assets
When you need the SVG as an image (e.g. inside Chakra's <Image>):
import seamIconUrl from "@chan.run/design/svg/seam-icon-dark.svg?url";
import { Image } from "@chakra-ui/react";
<Image src={seamIconUrl} alt="" boxSize="44px" />Variants under @chan.run/design/svg/:
| Pattern | What |
| ------------------------------------------- | -------------------------------------------- |
| chan-wordmark-{dark,light}.svg | CHAN[•]RUN, dark or light text |
| <name>-wordmark-{dark,light}.svg | Product wordmark (text-as-paths) |
| <name>-icon-{dark,light}.svg | 176×176 squircle monogram |
| <name>-favicon.svg | 32×32 single-letter favicon source |
Where <name> ∈ chan (wordmark only) | ensure | entropy | restunnel | seam | slides. SVGs use path-converted text (no font dependency) and survive pnpm build (live in assets/, not dist/).
For theme-aware rendering, prefer the React component. The static -dark/-light SVGs are for surfaces where you can't use Chakra (Rust dashboards, native apps, README screenshots, etc.).
Favicons
Each product ships a full favicon bundle (16/32/48 PNG, ICO, Apple touch, Android Chrome, Windows mstile, manifest, head snippet). Three ways to consume:
1. Head component (drop-in)
import { SeamFaviconHead } from "@chan.run/design";
<head>
<SeamFaviconHead />
{/* opt into PWA: <SeamFaviconHead manifest /> */}
</head>Per-product components: ChanFaviconHead, EnsureFaviconHead, EntropyFaviconHead, RestunnelFaviconHead, SeamFaviconHead, SlidesFaviconHead. Each emits the full set of <link>/<meta> tags with hashed asset URLs the bundler emits at build time.
2. Asset URL imports (raw URLs for custom wiring)
import faviconIco from "@chan.run/design/favicons/seam/favicon.ico?url";
import appleTouchIcon from "@chan.run/design/favicons/seam/apple-touch-icon.png?url";
import manifestUrl from "@chan.run/design/favicons/seam/manifest.webmanifest?url";
<link rel="icon" href={faviconIco} />3. Asset-URL maps (programmatic)
import { seamFaviconAssets } from "@chan.run/design";
seamFaviconAssets.ico; // bundler-emitted hashed URL
seamFaviconAssets.png16;
seamFaviconAssets.apple;
seamFaviconAssets.android192;
seamFaviconAssets.manifest;Browser extensions
Vite-style ?url imports don't work inside wxt.config.ts (it's loaded by Node, not Vite). Use createRequire to get a filesystem path, then let @wxt-dev/auto-icons downscale to the manifest sizes:
// wxt.config.ts
import { createRequire } from 'node:module';
import { defineConfig } from 'wxt';
const require = createRequire(import.meta.url);
const seamIconPath = require.resolve(
'@chan.run/design/favicons/seam/android-chrome-512x512.png',
);
export default defineConfig({
modules: ['@wxt-dev/module-react', '@wxt-dev/auto-icons'],
autoIcons: { baseIconPath: seamIconPath },
});See products/seam/seam-extension/wxt.config.ts for the working example.
Adding a new product mark
- Add to
src/products/<name>/<Name>Icon.tsxand<Name>Wordmark.tsx. UseProductIcon/ProductWordmarkfromsrc/products/_internal.tsx— they encode the locked visual rules so you only specify the letter splits. - Register in
src/products/index.ts(PRODUCT_ICONS,PRODUCT_WORDMARKS). - Add an entry to
scripts/export-svg.ts(PRODUCTSarray) andscripts/gen-favicons.ts(MARKSarray) so SVG/favicon variants are generated. - Run
pnpm gen-brandto produce theassets/outputs. - Run
pnpm typecheck && pnpm exec biome check .to confirm.
See docs/brand-marks.md for the full hard-rules reference.
Repo layout
packages/design/
├── src/
│ ├── brand/ # CHAN[•]RUN umbrella wordmark
│ ├── components/ # Splash, Topbar, slides, ColorModeButton, …
│ ├── favicons/ # Per-product head components + asset URL maps
│ ├── products/ # Per-product Icon + Wordmark components
│ │ ├── _internal.tsx # Locked visual primitives (do not export)
│ │ ├── <name>/ # ensure | entropy | restunnel | seam | slides
│ │ └── index.ts # Exports + PRODUCT_ICONS / PRODUCT_WORDMARKS
│ ├── styled-system/ # Chakra typegen output (do not edit by hand)
│ ├── theme.ts # `system` — Chakra v3 theme config
│ ├── TokenShowcase.tsx # Visual reference for all tokens
│ └── index.ts
├── scripts/
│ ├── export-svg.ts # Wordmarks/icons/favicon-source SVGs → assets/svg/
│ └── gen-favicons.ts # Per-product favicon bundles → assets/favicons/
└── assets/ # Committed build outputs (subpath-exported)
├── svg/
└── favicons/<name>/Scripts
| Command | What |
| ------------------------- | --------------------------------------------------------------- |
| pnpm build | Build TS bundle into dist/ (consumed by published package) |
| pnpm typecheck | tsc --noEmit for src/ and scripts/ |
| pnpm gen-brand | Regenerate all SVG and favicon assets into assets/ |
| pnpm export-svg | Just the SVGs |
| pnpm gen-favicons | Just the favicon bundles (depends on SVGs being generated) |
