Design System
Shared design tokens, theming, and styling utilities
Overview
Tally UI uses a shared design system built on Tailwind CSS 4 and Uniwind. The @tallyui/theme package provides semantic design tokens, light/dark theming, and a cn() utility for className composition.
All components use className for styling — no StyleSheet.create() or inline style objects. This works across iOS, Android, and Web through Uniwind's build-time compilation.
pnpm add @tallyui/themeDesign Tokens
Tokens are defined in @tallyui/theme/tokens.css using Tailwind 4's @theme directive. They map to semantic Tailwind classes:
Each colour token becomes Tailwind classes such as bg-<name>, text-<name> and border-<name>. A -foreground token is the text colour to use on its base colour. Contrast is checked by packages/theme/src/contrast.test.ts: 4.5:1 for text and 3:1 for control borders (WCAG 2.x AA).
Surfaces and text
| Token | Light | Dark | Use |
|---|---|---|---|
--color-background | #f8f9fa | #0d1117 | Page background |
--color-foreground | #111827 | #c9d1d9 | Body text |
--color-card | #ffffff | #161b22 | Cards and tiles |
--color-card-foreground | #111827 | #c9d1d9 | Text on cards |
--color-popover | #ffffff | #1c2128 | Popovers and menus |
--color-popover-foreground | #111827 | #c9d1d9 | Text in popovers |
--color-muted | #f3f4f6 | #1c2128 | Subtle panels (a background, never a text colour) |
--color-muted-foreground | #656c79 | #8b949e | Secondary text and labels |
--color-secondary | #f3f4f6 | #1c2128 | Secondary buttons |
--color-secondary-foreground | #111827 | #c9d1d9 | Text on secondary |
--color-accent | #f3f4f6 | #1c2128 | Hover and inactive chips |
--color-accent-foreground | #111827 | #c9d1d9 | Text on accent |
Actions and status
| Token | Light | Dark | Foreground (light / dark) |
|---|---|---|---|
--color-primary | #5b5ef0 | #818cf8 | #ffffff / #1e1b4b |
--color-destructive | #dc2626 | #f85149 | #ffffff / #0d1117 |
--color-success | #047857 | #3fb950 | #ffffff / #0d1117 |
--color-warning | #b45309 | #d29922 | #ffffff / #0d1117 |
--color-info | #2563eb | #58a6ff | #ffffff / #0d1117 |
Borders
| Token | Light | Dark | Use |
|---|---|---|---|
--color-border | #e5e7eb | #3d444d | Card edges and dividers |
--color-input | #848a94 | #636c76 | Borders of inputs and buttons (≥ 3:1) |
--color-ring | #5b5ef0 | #818cf8 | Focus ring |
Commerce
| Token | Light | Dark | Use |
|---|---|---|---|
--color-price | #047857 | #3fb950 | Prices |
--color-sale | #dc2626 | #f85149 | Sale prices |
Theming
Tally UI supports light and dark themes out of the box. Tokens automatically switch values based on the active theme.
Switching Themes
import { Uniwind } from 'uniwind';
// Set a specific theme
Uniwind.setTheme('dark');
Uniwind.setTheme('light');
// Follow device preferences
Uniwind.setTheme('system');Using Dark Variants
You can also use Tailwind's dark: prefix for one-off overrides:
<View className="bg-card dark:bg-muted">
<Text className="text-foreground">Adapts to theme</Text>
</View>The cn() Utility
The cn() function combines clsx (conditional class logic) with tailwind-merge (conflict resolution). Import it from @tallyui/theme:
import { cn } from '@tallyui/theme';
// Conditional classes
cn('text-sm', isActive && 'text-primary')
// → 'text-sm text-primary' (when active)
// Overriding defaults
cn('text-base text-foreground', className)
// → consumer's className wins over defaultsOverriding Component Styles
Every Tally UI component accepts a className prop. Defaults are applied internally, and your classes override them via cn():
// Default styling
<ProductTitle doc={doc} />
// Override with your own classes
<ProductTitle doc={doc} className="text-2xl font-bold text-primary" />This works because components use cn() internally:
// Inside ProductTitle
<Text className={cn('text-base font-semibold text-foreground', className)}>
{getName(doc)}
</Text>Your text-2xl replaces the default text-base, your font-bold replaces font-semibold, and text-primary replaces text-foreground. Non-conflicting classes merge normally.
Opacity Modifiers
Tailwind's opacity syntax works with all tokens. This is useful for backgrounds on status badges:
<View className="bg-success/15"> {/* 15% opacity green background */}
<View className="bg-destructive/10"> {/* 10% opacity red background */}Setup for New Apps
If you're building a new app with Tally UI components, you need to import the design tokens.
React Native (Expo + Uniwind)
Create a global.css at your app root:
@import 'tailwindcss';
@import 'uniwind';
@import '@tallyui/theme/tokens.css';
@source '../../packages/components';Import it in your root layout:
import './global.css';And wrap your Metro config:
const { withUniwindConfig } = require('uniwind/metro');
module.exports = withUniwindConfig(config, {
cssEntryFile: './global.css',
});Web (Next.js / Vite)
Import the tokens in your global CSS file:
@import 'tailwindcss';
@import '@tallyui/theme/tokens.css';