Skip to content

Theme System

Zelta Chat requires consistent styling to:

  • Maintain visual consistency across screens
  • Support light and dark mode variations
  • Provide accessible color contrasts
  • Enable rapid UI development with utility classes
  • Scale typography for different screen sizes

Why Tailwind CSS (TWRNC):

  • Utility-first approach for rapid development
  • Consistent spacing and sizing scales
  • Easy responsive design
  • No runtime style sheet generation
  • TypeScript support via twrnc package
twConfig (tailwind.config.ts)
├─ Base Theme (Tailwind defaults)
├─ Extended Colors
│ ├─ Radix UI Light Colors
│ ├─ Radix UI Dark Colors
│ ├─ Black Alpha variations
│ └─ White Alpha variations
├─ Extended Typography
│ ├─ Custom font sizes (xs, cxs, md)
│ └─ Inter font family variants
└─ Plugins

Color Categories:

  1. UI Colors - Neutral interface colors

    • gray1-12, mauve1-12, slate1-12
  2. Nature Colors - Natural tones

    • sage1-12, olive1-12, sand1-12
  3. Primary Colors - Brand and interactive elements

    • blue1-12, indigo1-12, violet1-12
  4. Accent Colors - Alerts and highlights

    • tomato1-12, red1-12, ruby1-12, crimson1-12
  5. Success/Info Colors

    • mint1-12, lime1-12, yellow1-12, amber1-12
  6. Special Colors

    • brown1-12, bronze1-12, gold1-12
  7. Alpha Variations - Transparency support

    • blackA1-12, whiteA1-12

Color Scale:

Each color has 12 steps:

  • 1-3: Backgrounds and subtle elements
  • 4-6: Borders and separators
  • 7-9: Interactive states (hover, active)
  • 10-12: High contrast text and icons

Font Family:

fontFamily: {
'inter-normal-20': ['Inter-400-20'], // Regular 400, optical size 20
'inter-420-20': ['Inter-420-20'], // Light Medium 420
'inter-medium-24': ['Inter-500-24'], // Medium 500, optical size 24
'inter-580-24': ['Inter-580-24'], // Semi Medium 580
'inter-semibold-20': ['Inter-600-20'], // Semi Bold 600
}

Font Sizes:

fontSize: {
xs: '12px', // Extra small
cxs: '13px', // Custom extra small
md: '15px', // Medium (custom, larger than default)
}
Component Render
Import tailwind instance
Apply utility classes: tailwind('bg-blue-500 p-4')
TWRNC parses classes
Generate React Native style object
Apply to component style prop
Render with styles
App Initialization
Load twConfig from tailwind.config.ts
Create tailwind instance with config
Export configured instance
Import in components

Configuration File (src/theme/tailwind.config.ts):

const defaultTheme = require('tailwindcss/defaultTheme');
const radixUILightColors = require('./colors/light');
const radixUIDarkColors = require('./colors/dark');
const blackA = require('./colors/blackA');
const whiteA = require('./colors/whiteA');
const Zelta ChatAppColors = {
...blackA,
...whiteA,
...radixUILightColors,
...radixUIDarkColors,
};
export const twConfig = {
theme: {
...defaultTheme,
extend: {
colors: { ...Zelta ChatAppColors },
fontSize: {
xs: '12px',
cxs: '13px',
md: '15px',
},
fontFamily: {
'inter-normal-20': ['Inter-400-20'],
'inter-420-20': ['Inter-420-20'],
'inter-medium-24': ['Inter-500-24'],
'inter-580-24': ['Inter-580-24'],
'inter-semibold-20': ['Inter-600-20'],
},
},
},
plugins: [],
};

Instance Export (src/theme/tailwind.ts):

import { create } from 'twrnc';
import { twConfig } from './tailwind.config';
const tailwind = create(twConfig);
export default tailwind;

Basic Styling:

import tailwind from '@/theme/tailwind';
import { View, Text } from 'react-native';
export const MyComponent = () => {
return (
<View style={tailwind('bg-gray-2 p-4 rounded-lg')}>
<Text style={tailwind('text-gray-12 font-inter-medium-24 text-md')}>
Hello World
</Text>
</View>
);
};

Conditional Styling:

const MyComponent = ({ isActive }: { isActive: boolean }) => {
return (
<View style={tailwind(`
p-4 rounded-lg
${isActive ? 'bg-blue-9' : 'bg-gray-3'}
`)}>
<Text style={tailwind('text-gray-12')}>Status</Text>
</View>
);
};

Combining with React Native Styles:

import { StyleSheet } from 'react-native';
const MyComponent = () => {
return (
<View style={[
tailwind('p-4'),
styles.shadow, // Custom styles
]}>
<Text>Content</Text>
</View>
);
};
const styles = StyleSheet.create({
shadow: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
});

✅ Good Patterns:

// Use semantic color scales
<View style={tailwind('bg-gray-2 border border-gray-6')}>
<Text style={tailwind('text-gray-12')}>Content</Text>
</View>
// Use consistent spacing scale
<View style={tailwind('p-4 mb-2 mt-6')}>
// Responsive font sizes
<Text style={tailwind('text-md font-inter-medium-24')}>
// Alpha colors for overlays
<View style={tailwind('bg-blackA-11 absolute inset-0')} />

❌ Anti-Patterns:

// DON'T: Use arbitrary values (not in Tailwind config)
<View style={{ backgroundColor: '#ff0000' }}> // ❌ Use color scale
// DON'T: Mix utility classes with inline styles unnecessarily
<Text style={[tailwind('text-md'), { fontSize: 16 }]}> // ❌ Conflicting
// DON'T: Overuse nested ternaries
style={tailwind(`${condition1 ? (condition2 ? 'a' : 'b') : 'c'}`)} // ❌ Hard to read

Memoize computed styles for frequently rendered components:

import { useMemo } from 'react';
const MyListItem = ({ isActive }: { isActive: boolean }) => {
const containerStyle = useMemo(
() => tailwind(`p-4 ${isActive ? 'bg-blue-3' : 'bg-gray-2'}`),
[isActive]
);
return <View style={containerStyle}>...</View>;
};
// ❌ Bad: Generates new style object every render
<View style={tailwind(`bg-gray-${Math.floor(opacity * 12)}`)}>
// ✅ Good: Use predefined classes
<View style={tailwind(opacity > 0.5 ? 'bg-gray-9' : 'bg-gray-3')}>
import tailwind from '@/theme/tailwind';
describe('Theme System', () => {
it('should apply correct background color', () => {
const style = tailwind('bg-blue-9');
expect(style.backgroundColor).toBeDefined();
});
it('should apply custom font family', () => {
const style = tailwind('font-inter-medium-24');
expect(style.fontFamily).toContain('Inter-500-24');
});
});

Use Storybook for visual regression testing:

MyComponent.stories.tsx
export default {
title: 'Components/MyComponent',
component: MyComponent,
};
export const Default = () => <MyComponent />;
export const Active = () => <MyComponent isActive />;
export const Disabled = () => <MyComponent disabled />;
src/theme/colors/custom.ts
export const customColors = {
brand: {
primary: '#1e40af',
secondary: '#3b82f6',
},
};
// Update tailwind.config.ts
const Zelta ChatAppColors = {
...blackA,
...whiteA,
...radixUILightColors,
...customColors,
};
import { useColorScheme } from 'react-native';
const MyComponent = () => {
const colorScheme = useColorScheme();
const isDark = colorScheme === 'dark';
return (
<View style={tailwind(`
p-4
${isDark ? 'bg-gray-12' : 'bg-gray-1'}
`)}>
<Text style={tailwind(isDark ? 'text-gray-1' : 'text-gray-12')}>
Content
</Text>
</View>
);
};
// Extend Tailwind config with custom utilities
export const twConfig = {
theme: {
extend: {
// ... existing config
spacing: {
'128': '32rem',
},
borderRadius: {
'4xl': '2rem',
},
},
},
plugins: [
// Custom plugin for shadows
({ addUtilities }) => {
addUtilities({
'.shadow-card': {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 3,
},
});
},
],
};