diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css
index a2dc41e..6f9b30c 100644
--- a/apps/web/app/globals.css
+++ b/apps/web/app/globals.css
@@ -1,26 +1,19 @@
+/**
+ * Import Tailwind CSS
+ */
@import "tailwindcss";
-:root {
- --background: #ffffff;
- --foreground: #171717;
-}
-
-@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --font-sans: var(--font-geist-sans);
- --font-mono: var(--font-geist-mono);
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
-}
+/**
+ * Import BridgeWise UI Components theme variables
+ */
+@import "@bridgewise/ui-components/styles/globals.css";
+/**
+ * Application-specific styles
+ * Use BridgeWise theme variables for consistency
+ */
body {
- background: var(--background);
- color: var(--foreground);
- font-family: Arial, Helvetica, sans-serif;
+ background: var(--bw-colors-background-primary);
+ color: var(--bw-colors-foreground-primary);
+ font-family: var(--bw-typography-font-family-sans);
}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index e9454a2..5fba724 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
+import { ThemeProvider, ThemeScript, TransactionProvider } from "@bridgewise/ui-components";
import "./globals.css";
const geistSans = Geist({
@@ -24,10 +25,17 @@ export default function RootLayout({
}>) {
return (
+
+
+
- {children}
+
+
+ {children}
+
+
);
diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
index ee8bc73..9b109fe 100644
--- a/apps/web/app/page.tsx
+++ b/apps/web/app/page.tsx
@@ -2,7 +2,7 @@
'use client';
import { useEffect } from 'react';
-import { TransactionHeartbeat, TransactionProvider, useTransaction } from '../components/ui-lib';
+import { TransactionHeartbeat, useTransaction } from '@bridgewise/ui-components';
function TransactionDemo() {
const { state, updateState, startTransaction, clearState } = useTransaction();
@@ -64,9 +64,5 @@ function TransactionDemo() {
}
export default function Home() {
- return (
-
-
-
- );
+ return ;
}
diff --git a/examples/custom-theme.example.tsx b/examples/custom-theme.example.tsx
new file mode 100644
index 0000000..45c00d0
--- /dev/null
+++ b/examples/custom-theme.example.tsx
@@ -0,0 +1,80 @@
+/**
+ * Custom Theme Configuration Example
+ * Shows how external developers can customize the BridgeWise theme
+ */
+
+import { ThemeProvider } from '@bridgewise/ui-components/theme';
+import type { Theme, DeepPartial } from '@bridgewise/ui-components';
+
+/**
+ * Example: Custom brand colors
+ */
+const customTheme: DeepPartial = {
+ colors: {
+ background: {
+ primary: '#fafafa',
+ secondary: '#f4f4f5',
+ },
+ foreground: {
+ primary: '#18181b',
+ link: '#3b82f6',
+ },
+ transaction: {
+ background: '#ffffff',
+ border: '#e4e4e7',
+ progressBar: {
+ success: '#22c55e',
+ error: '#ef4444',
+ pending: '#3b82f6',
+ },
+ },
+ },
+ spacing: {
+ xs: '0.5rem',
+ sm: '0.75rem',
+ md: '1.25rem',
+ },
+ typography: {
+ fontFamily: {
+ sans: 'Inter, system-ui, sans-serif',
+ },
+ },
+};
+
+/**
+ * Example: Dark-first theme
+ */
+const darkFirstTheme: DeepPartial = {
+ colors: {
+ background: {
+ primary: '#0a0a0a',
+ secondary: '#171717',
+ },
+ foreground: {
+ primary: '#ffffff',
+ secondary: '#a3a3a3',
+ },
+ },
+};
+
+/**
+ * Usage in your app
+ */
+export function App() {
+ return (
+
+ {/* Your application components */}
+
+ );
+}
+
+/**
+ * Force dark mode
+ */
+export function DarkApp() {
+ return (
+
+ {/* Your application components */}
+
+ );
+}
diff --git a/examples/headless-component.example.tsx b/examples/headless-component.example.tsx
new file mode 100644
index 0000000..be678d1
--- /dev/null
+++ b/examples/headless-component.example.tsx
@@ -0,0 +1,110 @@
+/**
+ * Headless Component Example
+ * Shows how to use the headless TransactionHeartbeat with custom styling
+ */
+
+import { TransactionHeartbeatHeadless } from '@bridgewise/ui-components/headless';
+import { TransactionProvider } from '@bridgewise/ui-components';
+
+/**
+ * Example 1: Minimal custom notification
+ */
+export function MinimalHeartbeat() {
+ return (
+
+ {({ state, clearState, isSuccess, isFailed, isPending }) => (
+
+
+
+
+ {isSuccess ? '✅ Done!' : isFailed ? '❌ Failed' : '⏳ Processing...'}
+
+
{state.step}
+
+
+
+
+
+ )}
+
+ );
+}
+
+/**
+ * Example 2: Toast-style notification
+ */
+export function ToastHeartbeat() {
+ return (
+
+ {({ state, clearState, isSuccess, isFailed }) => {
+ const bgColor = isSuccess ? 'bg-green-50' : isFailed ? 'bg-red-50' : 'bg-blue-50';
+ const textColor = isSuccess ? 'text-green-900' : isFailed ? 'text-red-900' : 'text-blue-900';
+
+ return (
+
+
+
+ {isSuccess ? '🎉' : isFailed ? '💔' : '🔄'}
+
+
+
+
+
+ );
+ }}
+
+ );
+}
+
+/**
+ * Example 3: Compact progress bar
+ */
+export function CompactHeartbeat() {
+ return (
+
+ {({ state, isSuccess, isFailed }) => {
+ const color = isSuccess ? '#22c55e' : isFailed ? '#ef4444' : '#3b82f6';
+
+ return (
+
+ );
+ }}
+
+ );
+}
+
+/**
+ * Complete app example
+ */
+export function App() {
+ return (
+
+
+
My Application
+ {/* Choose your style */}
+
+ {/* or */}
+ {/* or */}
+
+
+ );
+}
diff --git a/examples/theme-toggle.example.tsx b/examples/theme-toggle.example.tsx
new file mode 100644
index 0000000..6f6895e
--- /dev/null
+++ b/examples/theme-toggle.example.tsx
@@ -0,0 +1,127 @@
+/**
+ * Theme Toggle Examples
+ * Shows how to build theme switching controls
+ */
+
+'use client';
+
+import { useTheme } from '@bridgewise/ui-components/theme';
+
+/**
+ * Example 1: Simple toggle button
+ */
+export function SimpleThemeToggle() {
+ const { mode, toggleMode } = useTheme();
+
+ return (
+
+ );
+}
+
+/**
+ * Example 2: Three-way toggle (Light / System / Dark)
+ */
+export function AdvancedThemeToggle() {
+ const { mode, setMode } = useTheme();
+
+ return (
+
+ {(['light', 'system', 'dark'] as const).map((themeMode) => (
+
+ ))}
+
+ );
+}
+
+/**
+ * Example 3: Dropdown menu
+ */
+export function ThemeDropdown() {
+ const { mode, setMode } = useTheme();
+
+ return (
+
+
+
+
+ );
+}
+
+/**
+ * Example 4: Icon-only toggle with animation
+ */
+export function AnimatedThemeToggle() {
+ const { mode, toggleMode } = useTheme();
+ const isDark = mode === 'dark';
+
+ return (
+
+ );
+}
+
+/**
+ * Example 5: With current theme display
+ */
+export function ThemeToggleWithInfo() {
+ const { theme, mode, toggleMode } = useTheme();
+
+ return (
+
+
+
+
Mode: {mode}
+
Background: {theme.colors.background.primary}
+
+
+ );
+}
diff --git a/libs/ui-components/package-lock.json b/libs/ui-components/package-lock.json
new file mode 100644
index 0000000..6efef90
--- /dev/null
+++ b/libs/ui-components/package-lock.json
@@ -0,0 +1,92 @@
+{
+ "name": "@bridgewise/ui-components",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@bridgewise/ui-components",
+ "version": "0.1.0",
+ "devDependencies": {
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "typescript": "^5.0.0"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.9",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
+ "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
+ "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.3"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ }
+ }
+}
diff --git a/libs/ui-components/package.json b/libs/ui-components/package.json
index e27ed2f..0bd8bad 100644
--- a/libs/ui-components/package.json
+++ b/libs/ui-components/package.json
@@ -2,6 +2,12 @@
"name": "@bridgewise/ui-components",
"version": "0.1.0",
"main": "src/index.ts",
+ "exports": {
+ ".": "./src/index.ts",
+ "./theme": "./src/theme/index.ts",
+ "./headless": "./src/components/headless/index.ts",
+ "./styles/globals.css": "./src/styles/globals.css"
+ },
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
diff --git a/libs/ui-components/src/components/TransactionHeartbeat/TransactionContext.tsx b/libs/ui-components/src/components/TransactionHeartbeat/TransactionContext.tsx
new file mode 100644
index 0000000..c5fe48f
--- /dev/null
+++ b/libs/ui-components/src/components/TransactionHeartbeat/TransactionContext.tsx
@@ -0,0 +1,104 @@
+/**
+ * Transaction Context
+ * Manages transaction state across the application
+ */
+
+'use client';
+
+import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
+
+export interface TransactionState {
+ id: string;
+ status: 'idle' | 'pending' | 'success' | 'failed';
+ progress: number;
+ step: string;
+ txHash?: string;
+ timestamp: number;
+}
+
+interface TransactionContextType {
+ state: TransactionState;
+ updateState: (updates: Partial) => void;
+ clearState: () => void;
+ startTransaction: (id: string) => void;
+}
+
+const TransactionContext = createContext(undefined);
+
+const STORAGE_KEY = 'bridgewise_tx_state';
+
+export const TransactionProvider = ({ children }: { children: ReactNode }) => {
+ const [state, setState] = useState({
+ id: '',
+ status: 'idle',
+ progress: 0,
+ step: '',
+ timestamp: 0,
+ });
+
+ // Load from storage on mount
+ useEffect(() => {
+ if (typeof window === 'undefined') return;
+ try {
+ const stored = localStorage.getItem(STORAGE_KEY);
+ if (stored) {
+ const parsed = JSON.parse(stored);
+ // Expiry check (24 hours)
+ if (Date.now() - parsed.timestamp < 24 * 60 * 60 * 1000) {
+ setState(parsed);
+ } else {
+ localStorage.removeItem(STORAGE_KEY);
+ }
+ }
+ } catch (e) {
+ console.error('Failed to load transaction state', e);
+ }
+ }, []);
+
+ // Save to storage on change
+ useEffect(() => {
+ if (typeof window === 'undefined') return;
+ if (state.status !== 'idle') {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+ }
+ }, [state]);
+
+ const updateState = useCallback((updates: Partial) => {
+ setState((prev) => ({ ...prev, ...updates, timestamp: Date.now() }));
+ }, []);
+
+ const clearState = useCallback(() => {
+ setState({
+ id: '',
+ status: 'idle',
+ progress: 0,
+ step: '',
+ timestamp: 0,
+ });
+ localStorage.removeItem(STORAGE_KEY);
+ }, []);
+
+ const startTransaction = useCallback((id: string) => {
+ setState({
+ id,
+ status: 'pending',
+ progress: 0,
+ step: 'Initializing...',
+ timestamp: Date.now()
+ });
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useTransaction = () => {
+ const context = useContext(TransactionContext);
+ if (!context) {
+ throw new Error('useTransaction must be used within a TransactionProvider');
+ }
+ return context;
+};
diff --git a/libs/ui-components/src/components/TransactionHeartbeat/TransactionHeartbeat.headless.tsx b/libs/ui-components/src/components/TransactionHeartbeat/TransactionHeartbeat.headless.tsx
new file mode 100644
index 0000000..b646661
--- /dev/null
+++ b/libs/ui-components/src/components/TransactionHeartbeat/TransactionHeartbeat.headless.tsx
@@ -0,0 +1,83 @@
+/**
+ * TransactionHeartbeat Headless Component
+ * Provides transaction state and logic without any styling
+ * Uses render props pattern for maximum flexibility
+ */
+
+'use client';
+
+import React from 'react';
+import { useTransaction } from './TransactionContext';
+import type { TransactionState } from './TransactionContext';
+
+export interface TransactionHeartbeatRenderProps {
+ /** Full transaction state object */
+ state: TransactionState;
+ /** Function to clear/dismiss the transaction state */
+ clearState: () => void;
+ /** Update transaction state with partial updates */
+ updateState: (updates: Partial) => void;
+ /** Start a new transaction */
+ startTransaction: (id: string) => void;
+ /** Convenience boolean: true if status is 'success' */
+ isSuccess: boolean;
+ /** Convenience boolean: true if status is 'failed' */
+ isFailed: boolean;
+ /** Convenience boolean: true if status is 'pending' */
+ isPending: boolean;
+}
+
+export interface TransactionHeartbeatHeadlessProps {
+ /**
+ * Render function that receives transaction state and controls
+ * Return null to hide the component
+ */
+ children: (props: TransactionHeartbeatRenderProps) => React.ReactNode;
+}
+
+/**
+ * Headless transaction heartbeat component
+ *
+ * @example
+ * ```tsx
+ *
+ * {({ state, clearState, isSuccess, isPending }) => (
+ *
+ *
{isSuccess ? '✅ Done!' : isPending ? '⏳ Processing...' : '❌ Failed'}
+ *
+ *
{state.step}
+ *
+ *
+ * )}
+ *
+ * ```
+ */
+export const TransactionHeartbeatHeadless: React.FC = ({
+ children,
+}) => {
+ const { state, clearState, updateState, startTransaction } = useTransaction();
+
+ // Don't render if transaction is idle
+ if (state.status === 'idle') {
+ return null;
+ }
+
+ // Derive convenience booleans from state
+ const isSuccess = state.status === 'success';
+ const isFailed = state.status === 'failed';
+ const isPending = state.status === 'pending';
+
+ return (
+ <>
+ {children({
+ state,
+ clearState,
+ updateState,
+ startTransaction,
+ isSuccess,
+ isFailed,
+ isPending,
+ })}
+ >
+ );
+};
diff --git a/libs/ui-components/src/components/TransactionHeartbeat/TransactionHeartbeat.tsx b/libs/ui-components/src/components/TransactionHeartbeat/TransactionHeartbeat.tsx
new file mode 100644
index 0000000..12dc512
--- /dev/null
+++ b/libs/ui-components/src/components/TransactionHeartbeat/TransactionHeartbeat.tsx
@@ -0,0 +1,212 @@
+/**
+ * TransactionHeartbeat Component
+ * Styled notification component for transaction status
+ * Uses BridgeWise theme CSS variables for styling
+ */
+
+'use client';
+
+import React from 'react';
+import { TransactionHeartbeatHeadless } from './TransactionHeartbeat.headless';
+
+/**
+ * Transaction heartbeat notification component
+ * Displays transaction progress with themed styling
+ *
+ * @example
+ * ```tsx
+ * import { TransactionProvider, TransactionHeartbeat } from '@bridgewise/ui-components';
+ *
+ * function App() {
+ * return (
+ *
+ *
+ *
+ *
+ * );
+ * }
+ * ```
+ */
+export const TransactionHeartbeat: React.FC = () => {
+ return (
+
+ {({ state, clearState, isSuccess, isFailed, isPending }) => (
+
+
+ {/* Header */}
+
+
+ {isSuccess
+ ? 'Transaction Complete'
+ : isFailed
+ ? 'Transaction Failed'
+ : 'Bridging Assets...'}
+
+
+
+
+ {/* Progress bar */}
+
+
+
+ {state.progress}%
+
+
+
+ {/* Status text */}
+
+ {state.step}
+
+
+ {/* Transaction hash link */}
+ {state.txHash && (
+
+ )}
+
+
+ {/* Status indicator bar at bottom */}
+
+
+ {/* Pulse animation keyframes */}
+
+
+ )}
+
+ );
+};
diff --git a/libs/ui-components/src/components/TransactionHeartbeat/index.ts b/libs/ui-components/src/components/TransactionHeartbeat/index.ts
new file mode 100644
index 0000000..3f1196d
--- /dev/null
+++ b/libs/ui-components/src/components/TransactionHeartbeat/index.ts
@@ -0,0 +1,12 @@
+/**
+ * TransactionHeartbeat Component Exports
+ */
+
+export { TransactionHeartbeat } from './TransactionHeartbeat';
+export { TransactionHeartbeatHeadless } from './TransactionHeartbeat.headless';
+export { TransactionProvider, useTransaction } from './TransactionContext';
+export type { TransactionState } from './TransactionContext';
+export type {
+ TransactionHeartbeatHeadlessProps,
+ TransactionHeartbeatRenderProps,
+} from './TransactionHeartbeat.headless';
diff --git a/libs/ui-components/src/components/headless/index.ts b/libs/ui-components/src/components/headless/index.ts
new file mode 100644
index 0000000..bd242ba
--- /dev/null
+++ b/libs/ui-components/src/components/headless/index.ts
@@ -0,0 +1,10 @@
+/**
+ * Headless Components Exports
+ * Unstyled components for maximum customization
+ */
+
+export { TransactionHeartbeatHeadless } from '../TransactionHeartbeat/TransactionHeartbeat.headless';
+export type {
+ TransactionHeartbeatHeadlessProps,
+ TransactionHeartbeatRenderProps,
+} from '../TransactionHeartbeat/TransactionHeartbeat.headless';
diff --git a/libs/ui-components/src/index.ts b/libs/ui-components/src/index.ts
new file mode 100644
index 0000000..c044b38
--- /dev/null
+++ b/libs/ui-components/src/index.ts
@@ -0,0 +1,42 @@
+/**
+ * BridgeWise UI Components
+ * Main entry point for the component library
+ */
+
+// Theme System
+export {
+ ThemeProvider,
+ useTheme,
+ ThemeScript,
+ defaultTheme,
+ darkTheme,
+ primitiveColors,
+ mergeTheme,
+ generateCSSVariables,
+} from './theme';
+
+export type {
+ Theme,
+ ThemeMode,
+ ThemeColors,
+ ThemeSpacing,
+ ThemeTypography,
+ ThemeShadows,
+ ThemeRadii,
+ ThemeTransitions,
+ ThemeContextValue,
+ DeepPartial,
+ ThemeConfig,
+ CSSVariables,
+} from './theme';
+
+// Components
+export {
+ TransactionHeartbeat,
+ TransactionProvider,
+ useTransaction,
+} from './components/TransactionHeartbeat';
+
+export type {
+ TransactionState,
+} from './components/TransactionHeartbeat';
diff --git a/libs/ui-components/src/styles/globals.css b/libs/ui-components/src/styles/globals.css
new file mode 100644
index 0000000..2a8db69
--- /dev/null
+++ b/libs/ui-components/src/styles/globals.css
@@ -0,0 +1,170 @@
+/**
+ * BridgeWise UI Components Global Styles
+ * Theme system base styles and CSS variables
+ *
+ * Note: This file does NOT import Tailwind CSS.
+ * The consuming application should import Tailwind separately.
+ */
+
+/**
+ * CSS Variable Fallbacks
+ * These provide defaults before JavaScript loads the theme
+ */
+:root {
+ /* Colors - Light mode fallbacks */
+ --bw-colors-background-primary: #ffffff;
+ --bw-colors-background-secondary: #f8fafc;
+ --bw-colors-background-tertiary: #f1f5f9;
+ --bw-colors-foreground-primary: #0f172a;
+ --bw-colors-foreground-secondary: #475569;
+ --bw-colors-foreground-tertiary: #94a3b8;
+ --bw-colors-foreground-link: #2563eb;
+
+ /* Borders */
+ --bw-colors-border-default: #e2e8f0;
+ --bw-colors-border-focus: #3b82f6;
+ --bw-colors-border-error: #ef4444;
+
+ /* Status */
+ --bw-colors-status-success: #22c55e;
+ --bw-colors-status-error: #ef4444;
+ --bw-colors-status-warning: #eab308;
+ --bw-colors-status-info: #3b82f6;
+ --bw-colors-status-pending: #2563eb;
+
+ /* Transaction component */
+ --bw-colors-transaction-background: #ffffff;
+ --bw-colors-transaction-border: #e2e8f0;
+ --bw-colors-transaction-progress-bar-success: #22c55e;
+ --bw-colors-transaction-progress-bar-error: #ef4444;
+ --bw-colors-transaction-progress-bar-pending: #2563eb;
+
+ /* Spacing */
+ --bw-spacing-xs: 0.25rem;
+ --bw-spacing-sm: 0.5rem;
+ --bw-spacing-md: 1rem;
+ --bw-spacing-lg: 1.5rem;
+ --bw-spacing-xl: 2rem;
+ --bw-spacing-2xl: 3rem;
+
+ /* Typography */
+ --bw-typography-font-family-sans: var(--font-geist-sans, system-ui, sans-serif);
+ --bw-typography-font-family-mono: var(--font-geist-mono, ui-monospace, monospace);
+ --bw-typography-font-size-xs: 0.75rem;
+ --bw-typography-font-size-sm: 0.875rem;
+ --bw-typography-font-size-base: 1rem;
+ --bw-typography-font-size-lg: 1.125rem;
+ --bw-typography-font-size-xl: 1.25rem;
+ --bw-typography-font-size-2xl: 1.5rem;
+ --bw-typography-font-weight-normal: 400;
+ --bw-typography-font-weight-medium: 500;
+ --bw-typography-font-weight-semibold: 600;
+ --bw-typography-font-weight-bold: 700;
+ --bw-typography-line-height-tight: 1.25;
+ --bw-typography-line-height-normal: 1.5;
+ --bw-typography-line-height-relaxed: 1.75;
+
+ /* Shadows */
+ --bw-shadows-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --bw-shadows-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --bw-shadows-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --bw-shadows-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+
+ /* Radii */
+ --bw-radii-none: 0;
+ --bw-radii-sm: 0.125rem;
+ --bw-radii-md: 0.375rem;
+ --bw-radii-lg: 0.5rem;
+ --bw-radii-xl: 0.75rem;
+ --bw-radii-full: 9999px;
+
+ /* Transitions */
+ --bw-transitions-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
+ --bw-transitions-base: 300ms cubic-bezier(0.4, 0, 0.2, 1);
+ --bw-transitions-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+/**
+ * Dark Mode Fallbacks
+ * Applied before JavaScript loads when system prefers dark
+ */
+@media (prefers-color-scheme: dark) {
+ :root {
+ /* Colors - Dark mode fallbacks */
+ --bw-colors-background-primary: #0f172a;
+ --bw-colors-background-secondary: #1e293b;
+ --bw-colors-background-tertiary: #334155;
+ --bw-colors-foreground-primary: #f8fafc;
+ --bw-colors-foreground-secondary: #94a3b8;
+ --bw-colors-foreground-tertiary: #64748b;
+ --bw-colors-foreground-link: #60a5fa;
+
+ /* Borders */
+ --bw-colors-border-default: #334155;
+ --bw-colors-border-focus: #60a5fa;
+ --bw-colors-border-error: #f87171;
+
+ /* Status */
+ --bw-colors-status-success: #4ade80;
+ --bw-colors-status-error: #f87171;
+ --bw-colors-status-warning: #facc15;
+ --bw-colors-status-info: #60a5fa;
+ --bw-colors-status-pending: #3b82f6;
+
+ /* Transaction component */
+ --bw-colors-transaction-background: #1e293b;
+ --bw-colors-transaction-border: #334155;
+ }
+}
+
+/**
+ * Tailwind v4 Theme Integration
+ * Maps CSS variables to Tailwind utilities using @theme inline directive
+ */
+@theme inline {
+ /* Colors */
+ --color-bw-bg-primary: var(--bw-colors-background-primary);
+ --color-bw-bg-secondary: var(--bw-colors-background-secondary);
+ --color-bw-bg-tertiary: var(--bw-colors-background-tertiary);
+ --color-bw-fg-primary: var(--bw-colors-foreground-primary);
+ --color-bw-fg-secondary: var(--bw-colors-foreground-secondary);
+ --color-bw-fg-tertiary: var(--bw-colors-foreground-tertiary);
+ --color-bw-fg-link: var(--bw-colors-foreground-link);
+ --color-bw-border: var(--bw-colors-border-default);
+ --color-bw-border-focus: var(--bw-colors-border-focus);
+ --color-bw-success: var(--bw-colors-status-success);
+ --color-bw-error: var(--bw-colors-status-error);
+ --color-bw-warning: var(--bw-colors-status-warning);
+ --color-bw-info: var(--bw-colors-status-info);
+
+ /* Spacing */
+ --spacing-bw-xs: var(--bw-spacing-xs);
+ --spacing-bw-sm: var(--bw-spacing-sm);
+ --spacing-bw-md: var(--bw-spacing-md);
+ --spacing-bw-lg: var(--bw-spacing-lg);
+ --spacing-bw-xl: var(--bw-spacing-xl);
+ --spacing-bw-2xl: var(--bw-spacing-2xl);
+
+ /* Typography */
+ --font-bw-sans: var(--bw-typography-font-family-sans);
+ --font-bw-mono: var(--bw-typography-font-family-mono);
+ --text-bw-xs: var(--bw-typography-font-size-xs);
+ --text-bw-sm: var(--bw-typography-font-size-sm);
+ --text-bw-base: var(--bw-typography-font-size-base);
+ --text-bw-lg: var(--bw-typography-font-size-lg);
+ --text-bw-xl: var(--bw-typography-font-size-xl);
+ --text-bw-2xl: var(--bw-typography-font-size-2xl);
+
+ /* Shadows */
+ --shadow-bw-sm: var(--bw-shadows-sm);
+ --shadow-bw-md: var(--bw-shadows-md);
+ --shadow-bw-lg: var(--bw-shadows-lg);
+ --shadow-bw-xl: var(--bw-shadows-xl);
+
+ /* Radii */
+ --radius-bw-sm: var(--bw-radii-sm);
+ --radius-bw-md: var(--bw-radii-md);
+ --radius-bw-lg: var(--bw-radii-lg);
+ --radius-bw-xl: var(--bw-radii-xl);
+ --radius-bw-full: var(--bw-radii-full);
+}
diff --git a/libs/ui-components/src/theme/ThemeProvider.tsx b/libs/ui-components/src/theme/ThemeProvider.tsx
new file mode 100644
index 0000000..efe44d9
--- /dev/null
+++ b/libs/ui-components/src/theme/ThemeProvider.tsx
@@ -0,0 +1,194 @@
+/**
+ * ThemeProvider Component
+ * Core provider for BridgeWise theming system
+ * Manages theme state, dark mode, and CSS variable injection
+ */
+
+'use client';
+
+import React, { createContext, useContext, useEffect, useState, useMemo, useCallback } from 'react';
+import { mergeTheme } from './utils/merge-theme';
+import { generateCSSVariables } from './utils/css-vars';
+import { defaultTheme, darkTheme } from './tokens';
+import type { Theme, ThemeMode, ThemeContextValue, DeepPartial } from './types';
+
+const ThemeContext = createContext(undefined);
+
+export interface ThemeProviderProps {
+ children: React.ReactNode;
+ /**
+ * Custom theme configuration to merge with defaults
+ */
+ theme?: DeepPartial;
+ /**
+ * Initial theme mode (light/dark/system)
+ * @default 'system'
+ */
+ defaultMode?: ThemeMode;
+ /**
+ * Storage key for persisting theme preference
+ * @default 'bridgewise-theme-mode'
+ */
+ storageKey?: string;
+ /**
+ * Enable/disable system theme detection
+ * @default true
+ */
+ enableSystem?: boolean;
+ /**
+ * Disable all transitions during theme switch
+ * @default true
+ */
+ disableTransitionOnChange?: boolean;
+ /**
+ * Custom attribute to set on root element
+ * @default 'data-theme'
+ */
+ attribute?: 'class' | 'data-theme';
+ /**
+ * Value to set for dark mode
+ * @default 'dark'
+ */
+ darkValue?: string;
+ /**
+ * Value to set for light mode
+ * @default 'light'
+ */
+ lightValue?: string;
+}
+
+export const ThemeProvider: React.FC = ({
+ children,
+ theme: customTheme,
+ defaultMode = 'system',
+ storageKey = 'bridgewise-theme-mode',
+ enableSystem = true,
+ disableTransitionOnChange = true,
+ attribute = 'data-theme',
+ darkValue = 'dark',
+ lightValue = 'light',
+}) => {
+ const [mode, setModeState] = useState(defaultMode);
+ const [resolvedMode, setResolvedMode] = useState<'light' | 'dark'>('light');
+ const [mounted, setMounted] = useState(false);
+
+ // Merge custom theme with defaults
+ const mergedTheme = useMemo(() => {
+ const baseTheme = customTheme ? mergeTheme(defaultTheme, customTheme) : defaultTheme;
+ const themeWithMode = resolvedMode === 'dark'
+ ? mergeTheme(baseTheme, darkTheme)
+ : baseTheme;
+ return themeWithMode;
+ }, [customTheme, resolvedMode]);
+
+ // Initialize theme from storage/system on mount
+ useEffect(() => {
+ setMounted(true);
+
+ // Load from storage
+ if (typeof window !== 'undefined') {
+ const stored = localStorage.getItem(storageKey);
+ if (stored && ['light', 'dark', 'system'].includes(stored)) {
+ setModeState(stored as ThemeMode);
+ }
+ }
+ }, [storageKey]);
+
+ // Handle system preference changes
+ useEffect(() => {
+ if (!enableSystem || !mounted) return;
+
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+
+ const updateResolvedMode = () => {
+ if (mode === 'system') {
+ setResolvedMode(mediaQuery.matches ? 'dark' : 'light');
+ } else {
+ setResolvedMode(mode as 'light' | 'dark');
+ }
+ };
+
+ updateResolvedMode();
+
+ // Listen for system changes
+ mediaQuery.addEventListener('change', updateResolvedMode);
+ return () => mediaQuery.removeEventListener('change', updateResolvedMode);
+ }, [mode, enableSystem, mounted]);
+
+ // Apply theme to DOM
+ useEffect(() => {
+ if (!mounted) return;
+
+ const root = document.documentElement;
+
+ // Disable transitions temporarily
+ if (disableTransitionOnChange) {
+ const css = document.createElement('style');
+ css.textContent = '* { transition: none !important; }';
+ document.head.appendChild(css);
+
+ // Force reflow
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ document.head.removeChild(css);
+ });
+ });
+ }
+
+ // Set theme attribute
+ if (attribute === 'class') {
+ root.classList.remove(lightValue, darkValue);
+ root.classList.add(resolvedMode === 'dark' ? darkValue : lightValue);
+ } else {
+ root.setAttribute(attribute, resolvedMode === 'dark' ? darkValue : lightValue);
+ }
+
+ // Generate and inject CSS variables
+ const cssVars = generateCSSVariables(mergedTheme);
+ Object.entries(cssVars).forEach(([key, value]) => {
+ root.style.setProperty(key, value);
+ });
+
+ }, [resolvedMode, mergedTheme, mounted, attribute, darkValue, lightValue, disableTransitionOnChange]);
+
+ const setMode = useCallback((newMode: ThemeMode) => {
+ setModeState(newMode);
+ if (typeof window !== 'undefined') {
+ localStorage.setItem(storageKey, newMode);
+ }
+ }, [storageKey]);
+
+ const toggleMode = useCallback(() => {
+ setMode(mode === 'light' ? 'dark' : mode === 'dark' ? 'light' : 'light');
+ }, [mode, setMode]);
+
+ const value: ThemeContextValue = useMemo(() => ({
+ theme: mergedTheme,
+ mode: resolvedMode,
+ setMode,
+ toggleMode,
+ }), [mergedTheme, resolvedMode, setMode, toggleMode]);
+
+ // Prevent flash on SSR
+ if (!mounted) {
+ return <>{children}>;
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Hook to access theme context
+ * @throws Error if used outside ThemeProvider
+ */
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+};
diff --git a/libs/ui-components/src/theme/ThemeScript.tsx b/libs/ui-components/src/theme/ThemeScript.tsx
new file mode 100644
index 0000000..872956c
--- /dev/null
+++ b/libs/ui-components/src/theme/ThemeScript.tsx
@@ -0,0 +1,80 @@
+/**
+ * ThemeScript Component
+ * Inline script that runs before React hydration to prevent flash of wrong theme
+ * This should be placed in the of your document
+ */
+
+import React from 'react';
+
+export interface ThemeScriptProps {
+ /**
+ * Storage key for theme preference
+ * @default 'bridgewise-theme-mode'
+ */
+ storageKey?: string;
+ /**
+ * Attribute to set on root element
+ * @default 'data-theme'
+ */
+ attribute?: 'class' | 'data-theme';
+ /**
+ * Default theme mode
+ * @default 'system'
+ */
+ defaultMode?: 'light' | 'dark' | 'system';
+ /**
+ * Value to use for dark mode
+ * @default 'dark'
+ */
+ darkValue?: string;
+ /**
+ * Value to use for light mode
+ * @default 'light'
+ */
+ lightValue?: string;
+}
+
+export const ThemeScript: React.FC = ({
+ storageKey = 'bridgewise-theme-mode',
+ attribute = 'data-theme',
+ defaultMode = 'system',
+ darkValue = 'dark',
+ lightValue = 'light',
+}) => {
+ // Inline script that runs synchronously before React hydration
+ const script = `
+(function() {
+ try {
+ var storageKey = '${storageKey}';
+ var attribute = '${attribute}';
+ var defaultMode = '${defaultMode}';
+ var darkValue = '${darkValue}';
+ var lightValue = '${lightValue}';
+
+ var stored = localStorage.getItem(storageKey);
+ var mode = stored || defaultMode;
+
+ var resolvedMode = mode === 'system'
+ ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
+ : mode;
+
+ var value = resolvedMode === 'dark' ? darkValue : lightValue;
+
+ if (attribute === 'class') {
+ document.documentElement.classList.add(value);
+ } else {
+ document.documentElement.setAttribute(attribute, value);
+ }
+ } catch (e) {
+ // Fail silently in case of errors
+ }
+})();
+ `.trim();
+
+ return (
+
+ );
+};
diff --git a/libs/ui-components/src/theme/hooks/useTheme.ts b/libs/ui-components/src/theme/hooks/useTheme.ts
new file mode 100644
index 0000000..ed7435f
--- /dev/null
+++ b/libs/ui-components/src/theme/hooks/useTheme.ts
@@ -0,0 +1,6 @@
+/**
+ * useTheme Hook
+ * Access theme context and theme controls
+ */
+
+export { useTheme } from '../ThemeProvider';
diff --git a/libs/ui-components/src/theme/index.ts b/libs/ui-components/src/theme/index.ts
new file mode 100644
index 0000000..ac114b6
--- /dev/null
+++ b/libs/ui-components/src/theme/index.ts
@@ -0,0 +1,23 @@
+/**
+ * Theme System Exports
+ * Main entry point for BridgeWise theming
+ */
+
+export { ThemeProvider, useTheme } from './ThemeProvider';
+export { ThemeScript } from './ThemeScript';
+export { defaultTheme, darkTheme, primitiveColors } from './tokens';
+export { mergeTheme, generateCSSVariables } from './utils';
+export type {
+ Theme,
+ ThemeMode,
+ ThemeColors,
+ ThemeSpacing,
+ ThemeTypography,
+ ThemeShadows,
+ ThemeRadii,
+ ThemeTransitions,
+ ThemeContextValue,
+ DeepPartial,
+ ThemeConfig,
+ CSSVariables,
+} from './types';
diff --git a/libs/ui-components/src/theme/tokens/dark.ts b/libs/ui-components/src/theme/tokens/dark.ts
new file mode 100644
index 0000000..b5a9cc8
--- /dev/null
+++ b/libs/ui-components/src/theme/tokens/dark.ts
@@ -0,0 +1,46 @@
+/**
+ * Dark Theme Overrides
+ * Semantic token overrides specifically for dark mode
+ */
+
+import { primitiveColors } from './primitives';
+import type { DeepPartial, Theme } from '../types';
+
+export const darkTheme: DeepPartial = {
+ colors: {
+ background: {
+ primary: primitiveColors.slate[900],
+ secondary: primitiveColors.slate[800],
+ tertiary: primitiveColors.slate[700],
+ inverse: primitiveColors.white,
+ },
+ foreground: {
+ primary: primitiveColors.slate[50],
+ secondary: primitiveColors.slate[400],
+ tertiary: primitiveColors.slate[500],
+ inverse: primitiveColors.slate[900],
+ link: primitiveColors.blue[400],
+ },
+ border: {
+ default: primitiveColors.slate[700],
+ focus: primitiveColors.blue[400],
+ error: primitiveColors.red[400],
+ },
+ status: {
+ success: primitiveColors.green[400],
+ error: primitiveColors.red[400],
+ warning: primitiveColors.yellow[400],
+ info: primitiveColors.blue[400],
+ pending: primitiveColors.blue[500],
+ },
+ transaction: {
+ background: primitiveColors.slate[800],
+ border: primitiveColors.slate[700],
+ progressBar: {
+ success: primitiveColors.green[500],
+ error: primitiveColors.red[500],
+ pending: primitiveColors.blue[600],
+ },
+ },
+ },
+};
diff --git a/libs/ui-components/src/theme/tokens/defaults.ts b/libs/ui-components/src/theme/tokens/defaults.ts
new file mode 100644
index 0000000..514de69
--- /dev/null
+++ b/libs/ui-components/src/theme/tokens/defaults.ts
@@ -0,0 +1,100 @@
+/**
+ * Default Theme
+ * Semantic tokens that map primitive colors to meaningful contexts
+ */
+
+import { primitiveColors } from './primitives';
+import type { Theme } from '../types';
+
+export const defaultTheme: Theme = {
+ colors: {
+ background: {
+ primary: primitiveColors.white,
+ secondary: primitiveColors.slate[50],
+ tertiary: primitiveColors.slate[100],
+ inverse: primitiveColors.slate[900],
+ },
+ foreground: {
+ primary: primitiveColors.slate[900],
+ secondary: primitiveColors.slate[600],
+ tertiary: primitiveColors.slate[400],
+ inverse: primitiveColors.white,
+ link: primitiveColors.blue[600],
+ },
+ border: {
+ default: primitiveColors.slate[200],
+ focus: primitiveColors.blue[500],
+ error: primitiveColors.red[500],
+ },
+ status: {
+ success: primitiveColors.green[500],
+ error: primitiveColors.red[500],
+ warning: primitiveColors.yellow[500],
+ info: primitiveColors.blue[500],
+ pending: primitiveColors.blue[600],
+ },
+ transaction: {
+ background: primitiveColors.white,
+ border: primitiveColors.slate[200],
+ progressBar: {
+ success: primitiveColors.green[500],
+ error: primitiveColors.red[500],
+ pending: primitiveColors.blue[600],
+ },
+ },
+ },
+ spacing: {
+ xs: '0.25rem', // 4px
+ sm: '0.5rem', // 8px
+ md: '1rem', // 16px
+ lg: '1.5rem', // 24px
+ xl: '2rem', // 32px
+ '2xl': '3rem', // 48px
+ },
+ typography: {
+ fontFamily: {
+ sans: 'var(--font-geist-sans, system-ui, sans-serif)',
+ mono: 'var(--font-geist-mono, ui-monospace, monospace)',
+ },
+ fontSize: {
+ xs: '0.75rem', // 12px
+ sm: '0.875rem', // 14px
+ base: '1rem', // 16px
+ lg: '1.125rem', // 18px
+ xl: '1.25rem', // 20px
+ '2xl': '1.5rem', // 24px
+ '3xl': '1.875rem', // 30px
+ '4xl': '2.25rem', // 36px
+ },
+ fontWeight: {
+ normal: '400',
+ medium: '500',
+ semibold: '600',
+ bold: '700',
+ },
+ lineHeight: {
+ tight: '1.25',
+ normal: '1.5',
+ relaxed: '1.75',
+ },
+ },
+ shadows: {
+ sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
+ md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
+ lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
+ xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
+ },
+ radii: {
+ none: '0',
+ sm: '0.125rem', // 2px
+ md: '0.375rem', // 6px
+ lg: '0.5rem', // 8px
+ xl: '0.75rem', // 12px
+ full: '9999px',
+ },
+ transitions: {
+ fast: '150ms cubic-bezier(0.4, 0, 0.2, 1)',
+ base: '300ms cubic-bezier(0.4, 0, 0.2, 1)',
+ slow: '500ms cubic-bezier(0.4, 0, 0.2, 1)',
+ },
+};
diff --git a/libs/ui-components/src/theme/tokens/index.ts b/libs/ui-components/src/theme/tokens/index.ts
new file mode 100644
index 0000000..80b0409
--- /dev/null
+++ b/libs/ui-components/src/theme/tokens/index.ts
@@ -0,0 +1,8 @@
+/**
+ * Theme Tokens
+ * Export all theme token definitions
+ */
+
+export { primitiveColors } from './primitives';
+export { defaultTheme } from './defaults';
+export { darkTheme } from './dark';
diff --git a/libs/ui-components/src/theme/tokens/primitives.ts b/libs/ui-components/src/theme/tokens/primitives.ts
new file mode 100644
index 0000000..decb66b
--- /dev/null
+++ b/libs/ui-components/src/theme/tokens/primitives.ts
@@ -0,0 +1,79 @@
+/**
+ * Primitive Color Tokens
+ * Raw color values and scales used throughout the theme system
+ */
+
+export const primitiveColors = {
+ white: '#ffffff',
+ black: '#000000',
+
+ slate: {
+ 50: '#f8fafc',
+ 100: '#f1f5f9',
+ 200: '#e2e8f0',
+ 300: '#cbd5e1',
+ 400: '#94a3b8',
+ 500: '#64748b',
+ 600: '#475569',
+ 700: '#334155',
+ 800: '#1e293b',
+ 900: '#0f172a',
+ 950: '#020617',
+ },
+
+ blue: {
+ 50: '#eff6ff',
+ 100: '#dbeafe',
+ 200: '#bfdbfe',
+ 300: '#93c5fd',
+ 400: '#60a5fa',
+ 500: '#3b82f6',
+ 600: '#2563eb',
+ 700: '#1d4ed8',
+ 800: '#1e40af',
+ 900: '#1e3a8a',
+ 950: '#172554',
+ },
+
+ green: {
+ 50: '#f0fdf4',
+ 100: '#dcfce7',
+ 200: '#bbf7d0',
+ 300: '#86efac',
+ 400: '#4ade80',
+ 500: '#22c55e',
+ 600: '#16a34a',
+ 700: '#15803d',
+ 800: '#166534',
+ 900: '#14532d',
+ 950: '#052e16',
+ },
+
+ red: {
+ 50: '#fef2f2',
+ 100: '#fee2e2',
+ 200: '#fecaca',
+ 300: '#fca5a5',
+ 400: '#f87171',
+ 500: '#ef4444',
+ 600: '#dc2626',
+ 700: '#b91c1c',
+ 800: '#991b1b',
+ 900: '#7f1d1d',
+ 950: '#450a0a',
+ },
+
+ yellow: {
+ 50: '#fefce8',
+ 100: '#fef9c3',
+ 200: '#fef08a',
+ 300: '#fde047',
+ 400: '#facc15',
+ 500: '#eab308',
+ 600: '#ca8a04',
+ 700: '#a16207',
+ 800: '#854d0e',
+ 900: '#713f12',
+ 950: '#422006',
+ },
+} as const;
diff --git a/libs/ui-components/src/theme/types.ts b/libs/ui-components/src/theme/types.ts
new file mode 100644
index 0000000..ce39d4c
--- /dev/null
+++ b/libs/ui-components/src/theme/types.ts
@@ -0,0 +1,200 @@
+/**
+ * Theme Type Definitions
+ * Comprehensive TypeScript types for the BridgeWise theming system
+ */
+
+export type ThemeMode = 'light' | 'dark' | 'system';
+
+/**
+ * Color scale interface for primitive colors
+ */
+export interface ColorScale {
+ 50: string;
+ 100: string;
+ 200: string;
+ 300: string;
+ 400: string;
+ 500: string;
+ 600: string;
+ 700: string;
+ 800: string;
+ 900: string;
+ 950?: string;
+}
+
+/**
+ * Theme color tokens
+ * Semantic color mappings for various UI contexts
+ */
+export interface ThemeColors {
+ background: {
+ primary: string;
+ secondary: string;
+ tertiary: string;
+ inverse: string;
+ };
+ foreground: {
+ primary: string;
+ secondary: string;
+ tertiary: string;
+ inverse: string;
+ link: string;
+ };
+ border: {
+ default: string;
+ focus: string;
+ error: string;
+ };
+ status: {
+ success: string;
+ error: string;
+ warning: string;
+ info: string;
+ pending: string;
+ };
+ transaction: {
+ background: string;
+ border: string;
+ progressBar: {
+ success: string;
+ error: string;
+ pending: string;
+ };
+ };
+}
+
+/**
+ * Spacing tokens
+ * Consistent spacing scale throughout the application
+ */
+export interface ThemeSpacing {
+ xs: string;
+ sm: string;
+ md: string;
+ lg: string;
+ xl: string;
+ '2xl': string;
+ [key: string]: string;
+}
+
+/**
+ * Typography tokens
+ * Font families, sizes, weights, and line heights
+ */
+export interface ThemeTypography {
+ fontFamily: {
+ sans: string;
+ mono: string;
+ };
+ fontSize: {
+ xs: string;
+ sm: string;
+ base: string;
+ lg: string;
+ xl: string;
+ '2xl': string;
+ '3xl': string;
+ '4xl': string;
+ [key: string]: string;
+ };
+ fontWeight: {
+ normal: string;
+ medium: string;
+ semibold: string;
+ bold: string;
+ [key: string]: string;
+ };
+ lineHeight: {
+ tight: string;
+ normal: string;
+ relaxed: string;
+ [key: string]: string;
+ };
+}
+
+/**
+ * Shadow tokens
+ * Elevation and depth effects
+ */
+export interface ThemeShadows {
+ sm: string;
+ md: string;
+ lg: string;
+ xl: string;
+ [key: string]: string;
+}
+
+/**
+ * Border radius tokens
+ * Corner rounding values
+ */
+export interface ThemeRadii {
+ none: string;
+ sm: string;
+ md: string;
+ lg: string;
+ xl: string;
+ full: string;
+ [key: string]: string;
+}
+
+/**
+ * Transition tokens
+ * Animation timing and easing
+ */
+export interface ThemeTransitions {
+ fast: string;
+ base: string;
+ slow: string;
+ [key: string]: string;
+}
+
+/**
+ * Complete theme interface
+ * All theme token categories combined
+ */
+export interface Theme {
+ colors: ThemeColors;
+ spacing: ThemeSpacing;
+ typography: ThemeTypography;
+ shadows: ThemeShadows;
+ radii: ThemeRadii;
+ transitions: ThemeTransitions;
+}
+
+/**
+ * Deep partial utility type
+ * Allows partial theme customization at any depth
+ */
+export type DeepPartial = {
+ [P in keyof T]?: T[P] extends object
+ ? T[P] extends Array
+ ? Array>
+ : DeepPartial
+ : T[P];
+};
+
+/**
+ * Theme configuration for customization
+ */
+export interface ThemeConfig {
+ theme?: DeepPartial;
+ extend?: DeepPartial;
+}
+
+/**
+ * Theme context value interface
+ * Used by ThemeProvider context
+ */
+export interface ThemeContextValue {
+ theme: Theme;
+ mode: 'light' | 'dark';
+ setMode: (mode: ThemeMode) => void;
+ toggleMode: () => void;
+}
+
+/**
+ * CSS variables record type
+ * Generated CSS custom properties
+ */
+export type CSSVariables = Record;
diff --git a/libs/ui-components/src/theme/utils/css-vars.ts b/libs/ui-components/src/theme/utils/css-vars.ts
new file mode 100644
index 0000000..0809ac2
--- /dev/null
+++ b/libs/ui-components/src/theme/utils/css-vars.ts
@@ -0,0 +1,62 @@
+/**
+ * CSS Variables Generator
+ * Converts theme tokens to CSS custom properties
+ */
+
+import type { Theme, CSSVariables } from '../types';
+
+/**
+ * Converts camelCase to kebab-case
+ * @param str - String in camelCase
+ * @returns String in kebab-case
+ */
+function camelToKebab(str: string): string {
+ return str.replace(/([A-Z])/g, '-$1').toLowerCase();
+}
+
+/**
+ * Recursively flatten nested object into CSS variables
+ * @param obj - Object to flatten
+ * @param prefix - Current key prefix
+ * @param result - Accumulated result object
+ */
+function flattenObject(
+ obj: any,
+ prefix: string = '',
+ result: CSSVariables = {}
+): CSSVariables {
+ for (const key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ const value = obj[key];
+ const cssKey = prefix ? `${prefix}-${key}` : key;
+
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
+ // Recursively flatten nested objects
+ flattenObject(value, cssKey, result);
+ } else {
+ // Convert to kebab-case and add --bw- prefix
+ const kebabKey = camelToKebab(cssKey);
+ result[`--bw-${kebabKey}`] = String(value);
+ }
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Generate CSS custom properties from theme object
+ * @param theme - Theme configuration
+ * @returns Object of CSS variable names and values
+ * @example
+ * generateCSSVariables(theme)
+ * // Returns: {
+ * // '--bw-colors-background-primary': '#ffffff',
+ * // '--bw-colors-foreground-primary': '#171717',
+ * // '--bw-spacing-md': '1rem',
+ * // ...
+ * // }
+ */
+export function generateCSSVariables(theme: Theme): CSSVariables {
+ return flattenObject(theme);
+}
diff --git a/libs/ui-components/src/theme/utils/index.ts b/libs/ui-components/src/theme/utils/index.ts
new file mode 100644
index 0000000..59e6d9a
--- /dev/null
+++ b/libs/ui-components/src/theme/utils/index.ts
@@ -0,0 +1,7 @@
+/**
+ * Theme Utilities
+ * Export all theme utility functions
+ */
+
+export { mergeTheme } from './merge-theme';
+export { generateCSSVariables } from './css-vars';
diff --git a/libs/ui-components/src/theme/utils/merge-theme.ts b/libs/ui-components/src/theme/utils/merge-theme.ts
new file mode 100644
index 0000000..9b1fda2
--- /dev/null
+++ b/libs/ui-components/src/theme/utils/merge-theme.ts
@@ -0,0 +1,49 @@
+/**
+ * Theme Merging Utility
+ * Deep merge function for combining custom themes with defaults
+ */
+
+import type { Theme, DeepPartial } from '../types';
+
+/**
+ * Deep merge two objects
+ * Later object properties override earlier ones
+ */
+function deepMerge>(
+ target: T,
+ source: Record
+): T {
+ const output: any = { ...target };
+
+ for (const key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ const sourceValue = source[key];
+ const targetValue = output[key];
+
+ if (
+ sourceValue &&
+ typeof sourceValue === 'object' &&
+ !Array.isArray(sourceValue) &&
+ targetValue &&
+ typeof targetValue === 'object' &&
+ !Array.isArray(targetValue)
+ ) {
+ output[key] = deepMerge(targetValue, sourceValue);
+ } else if (sourceValue !== undefined) {
+ output[key] = sourceValue;
+ }
+ }
+ }
+
+ return output as T;
+}
+
+/**
+ * Merge custom theme configuration with base theme
+ * @param base - Base theme to start with
+ * @param custom - Custom theme overrides
+ * @returns Merged theme object
+ */
+export function mergeTheme(base: Theme, custom: DeepPartial): Theme {
+ return deepMerge(base, custom);
+}