Tally UI

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/theme

Design 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

TokenLightDarkUse
--color-background#f8f9fa#0d1117Page background
--color-foreground#111827#c9d1d9Body text
--color-card#ffffff#161b22Cards and tiles
--color-card-foreground#111827#c9d1d9Text on cards
--color-popover#ffffff#1c2128Popovers and menus
--color-popover-foreground#111827#c9d1d9Text in popovers
--color-muted#f3f4f6#1c2128Subtle panels (a background, never a text colour)
--color-muted-foreground#656c79#8b949eSecondary text and labels
--color-secondary#f3f4f6#1c2128Secondary buttons
--color-secondary-foreground#111827#c9d1d9Text on secondary
--color-accent#f3f4f6#1c2128Hover and inactive chips
--color-accent-foreground#111827#c9d1d9Text on accent

Actions and status

TokenLightDarkForeground (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

TokenLightDarkUse
--color-border#e5e7eb#3d444dCard edges and dividers
--color-input#848a94#636c76Borders of inputs and buttons (≥ 3:1)
--color-ring#5b5ef0#818cf8Focus ring

Commerce

TokenLightDarkUse
--color-price#047857#3fb950Prices
--color-sale#dc2626#f85149Sale 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 defaults

Overriding 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';