Skip to content

Tailwind CSS Integration Guide

Status: ✅ Phase 1.2 Complete
Date: October 11, 2025
Branch: feat/tailwind-integration

This guide documents the Tailwind CSS and NativeWind integration for the CloudAlt monorepo. The setup enables utility-first CSS styling across both web and React Native mobile apps, with a shared design token system.

packages/
config/
tailwind-preset.js # Shared Tailwind preset with design tokens
theme/ # Design tokens (colors, typography, spacing)
apps/
[brand]/[app]/mobile/
tailwind.config.js # App-specific Tailwind config (extends preset)
babel.config.js # Babel config with NativeWind plugin
metro.config.js # Metro config for NativeWind support
[brand]/[app]/web/
tailwind.config.js # App-specific Tailwind config (extends preset)
postcss.config.js # Tailwind v4 PostCSS plugin
vite.config.ts # Vite aliases for web-only bundling

Tailwind CSS and NativeWind are installed at the root level:

{
"devDependencies": {
"tailwindcss": "^4.1.14",
"nativewind": "^4.2.1"
}
}

Location: packages/config/tailwind-preset.js

The shared preset includes:

  • Design token colors (primary, secondary, semantic colors)
  • Typography scales (font families, sizes, line heights)
  • Spacing system (extends Tailwind default)
  • Border radius scale
  • Box shadows
  • Custom animations (fade-in, slide-up, etc.)

Each mobile app extends the shared preset:

apps/[brand]/[app]/mobile/tailwind.config.js
module.exports = {
presets: [require('@cloudalt-frontend/config/tailwind-preset')],
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./App.{js,jsx,ts,tsx}'
],
// App-specific overrides (optional)
theme: {
extend: {
colors: {
brand: '#custom-color'
}
}
}
};

Update babel.config.js to include NativeWind plugin:

module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
'nativewind/babel',
// ... other plugins
],
};
};

Update metro.config.js for NativeWind CSS support:

const { getDefaultConfig } = require('expo/metro-config');
const { withNativeWind } = require('nativewind/metro');
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, {
input: './global.css', // Optional: global CSS file
});

Add type definitions for NativeWind (if using TypeScript):

/// <reference types="nativewind/types" />

In each web app, configure Tailwind and Vite as follows:

  1. PostCSS plugin (Tailwind v4): apps/.../web/postcss.config.js
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
};
  1. Tailwind config: extend the shared preset and set content globs
/** @type {import('tailwindcss').Config} */
module.exports = {
presets: [require('@cloudalt-frontend/config/tailwind-preset')],
content: ['./src/**/*.{js,jsx,ts,tsx,html}', './index.html'],
darkMode: 'class',
};
  1. Vite aliases to avoid React Native code in web bundles and to target the web entry of shared UI
vite.config.ts
resolve: {
alias: {
'react-native$': 'react-native-web',
'react-native-svg': 'react-native-svg-web',
'@cloudalt-frontend/ui': '@cloudalt-frontend/ui/index.web',
},
},
optimizeDeps: {
exclude: ['react-native'],
},

This ensures the web build never pulls Flow-typed RN source files and consistently uses the web barrel of shared UI.

import { View, Text } from 'react-native';
export function MyComponent() {
return (
<View className="flex-1 bg-primary-500 p-4">
<Text className="text-white text-lg font-bold">
Hello, Tailwind!
</Text>
</View>
);
}
<View className="w-full md:w-1/2 lg:w-1/3">
<Text className="text-sm sm:text-base md:text-lg">
Responsive text
</Text>
</View>
<View className="bg-white dark:bg-gray-900">
<Text className="text-gray-900 dark:text-white">
Adaptive text
</Text>
</View>
<View className="animate-fade-in">
<Text>Fading in...</Text>
</View>

The Tailwind preset includes placeholder design tokens. These should be replaced with tokens from packages/theme once the design system is fully implemented.

packages/config/tailwind-preset.js
const designTokens = require('@cloudalt-frontend/theme');
module.exports = {
theme: {
extend: {
colors: designTokens.colors,
fontFamily: designTokens.typography.families,
fontSize: designTokens.typography.sizes,
// ...
}
}
};

NativeWind classes are processed at build time. For testing:

import { render } from '@testing-library/react-native';
test('renders with Tailwind classes', () => {
const { getByText } = render(
<Text className="text-primary-500">Test</Text>
);
expect(getByText('Test')).toBeTruthy();
});

Use Storybook (Phase 1.3) to create stories for components with Tailwind styling:

export const PrimaryButton = () => (
<Button className="bg-primary-500 text-white px-4 py-2 rounded-lg">
Primary Button
</Button>
);

Before:

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#0ea5e9',
padding: 16,
},
text: {
color: '#ffffff',
fontSize: 18,
fontWeight: 'bold',
},
});
<View style={styles.container}>
<Text style={styles.text}>Hello</Text>
</View>

After:

<View className="flex-1 bg-primary-500 p-4">
<Text className="text-white text-lg font-bold">Hello</Text>
</View>

You can mix Tailwind classes with StyleSheet:

<View className="flex-1 bg-primary-500" style={customStyles.container}>
<Text className="text-white">Hybrid styling</Text>
</View>

NativeWind processes className props at build time, converting them to React Native styles. This means:

  • ✅ No runtime performance penalty
  • ✅ Tree-shaking removes unused styles
  • ✅ Type-safe with TypeScript
  • Tailwind utilities are purged based on content glob patterns
  • Only used classes are included in the final bundle
  • Typical overhead: < 5KB gzipped
  1. Check content paths in tailwind.config.js

    content: ['./src/**/*.{js,jsx,ts,tsx}']
  2. Verify Babel plugin is loaded

    Terminal window
    yarn nx run [app]:start --clear
  3. Check Metro bundler output for errors

If you see className type errors, ensure nativewind/types is referenced:

/// <reference types="nativewind/types" />

Clear Metro cache if styles aren’t updating:

Terminal window
yarn nx run [app]:start --clear
# or
npx expo start --clear

Recommended extensions for Tailwind development:

  • Tailwind CSS IntelliSense: Autocomplete, syntax highlighting, linting
  • PostCSS Language Support: Syntax highlighting for CSS

Add to .vscode/settings.json:

{
"tailwindCSS.experimental.classRegex": [
["className\\s*=\\s*['\"`]([^'\"`]*)['\"`]", "([\\w-]+)"]
],
"tailwindCSS.includeLanguages": {
"typescript": "javascript",
"typescriptreact": "javascript"
}
}
Terminal window
npx nx g @nx/react:tailwind-config --project=[app-name]

Nx automatically processes Tailwind during builds:

Terminal window
# Web apps
npx nx build [web-app] --with-deps
# Mobile apps (Metro bundler handles it)
npx nx run [mobile-app]:start
  1. Use the shared preset: Always extend @cloudalt-frontend/config/tailwind-preset for consistency
  2. Semantic class names: Prefer semantic utilities (bg-primary-500) over arbitrary values (bg-[#0ea5e9])
  3. Component composition: Create reusable components rather than repeating class strings
  4. Dark mode first: Design with dark mode in mind from the start
  5. Responsive by default: Use responsive prefixes (sm:, md:, lg:) for adaptive layouts
  6. Avoid !important: Use proper specificity instead of forcing styles
  • ✅ Phase 1.2 Complete: Tailwind preset with design tokens
  • 🔄 Phase 1.3: Storybook integration for visual design system
  • 📅 Phase 2: Migrate pinkguest app to Tailwind styling
  • 📅 Phase 2: Extract reusable styled components to @cloudalt-frontend/ui
  • 📅 Phase 3: Replicate Tailwind setup to all mobile apps

For questions or issues with Tailwind integration:

  1. Check this guide first
  2. Review Metro bundler output for errors
  3. Consult NativeWind documentation
  4. Check docs/WORK_LOG.md for known issues