Back to Abp

Styling with NativeWind

docs/en/framework/ui/react-native/styling-with-nativewind.md

10.5.07.5 KB
Original Source
json
//[doc-seo]
{
    "Description": "Learn how the ABP React Native startup template is styled with NativeWind v4 (Tailwind CSS for React Native), including the semantic color tokens, dark mode and theme customization."
}

Styling with NativeWind

The ABP React Native startup template uses NativeWind v4 — Tailwind CSS for React Native. Most components are styled through utility className props that are compiled at build time, so there is almost no styling runtime cost. The design system is based on the shadcn/ui neutral palette and supports light and dark themes out of the box.

This page documents the styling system that ships with the template. For the general getting-started flow (installing tools, running the app, configuring the backend), see Getting Started with React Native.


1. Project Files

NativeWind is wired in through a small set of configuration files at the root of the React Native project:

FilePurpose
tailwind.config.jsDefines the design tokens (colors, spacing, border radius), enables darkMode: 'class', and registers the NativeWind preset. This is the source of truth for the theme.
global.cssTailwind entry point with the three base directives (@tailwind base; @tailwind components; @tailwind utilities;).
metro.config.jsWraps the default Expo Metro config with withNativeWind(...) and points it at global.css.
babel.config.jsAdds the nativewind/babel preset and sets jsxImportSource: 'nativewind' on babel-preset-expo so JSX understands the className prop.
nativewind-env.d.tsTypeScript triple-slash reference (/// <reference types="nativewind/types" />) that adds className typings to React Native components.

All of these files come pre-configured in the template. You normally only need to edit tailwind.config.js to customize the theme.


2. Semantic Color Tokens

Instead of raw color names like bg-zinc-900, the template exposes a small set of semantic tokens that follow the shadcn/ui convention. Each token has a default (light) value and a dark variant, and many also carry a paired foreground color for text/icons placed on top.

TokenUse it for
background / foregroundScreen background and primary text.
card / card-borderSurface containers (settings rows, feature cards) and their 1px border.
primary / primary-foregroundFilled call-to-action buttons.
secondary / secondary-foregroundSubtle filled elements such as icon chips inside cards.
muted / muted-foregroundBackgrounds and text for secondary information (subtitles, helper text).
accent / accent-foregroundHighlighted/active state — also used as the active tint of the bottom tab bar.
destructive / destructive-foregroundDelete/danger actions and error states.
borderGeneric separators and outline borders.
inputBorder color for text inputs (Paper TextInput outline).
ringFocus ring/outline color.

All token values live in tailwind.config.js under theme.extend.colors. Refer to that file for the exact hex values.

The template also extends Tailwind's spacing and border-radius scales so paddings and corners stay consistent across screens:

  • Spacing: xs (4px), sm (8px), md (16px), lg (24px), xl (32px) — usable as p-md, mt-lg, gap-sm, etc.
  • Border radius: rounded-sm (4px), rounded-md (8px), rounded-lg (12px), rounded-xl (16px), rounded-2xl (20px).

3. Dark Mode

Dark mode is enabled with darkMode: 'class' in tailwind.config.js and is driven by the device color scheme via NativeWind's useColorScheme() hook. Each color token has a sibling *-dark (and where applicable *-dark-foreground) variant, and components opt into it with the dark: prefix on their classes:

tsx
<View className="flex-1 bg-background dark:bg-background-dark">
  <Text className="text-foreground dark:text-foreground-dark">
    Hello
  </Text>
</View>

Pattern note. The token shape used in this template is { DEFAULT, dark } (and { foreground, 'dark-foreground' } where applicable), so the dark-mode class is dark:bg-background-dark — not dark:bg-background. Follow this convention when you add new screens so the theme switch behaves consistently across the app.

The user can also switch themes manually from the Settings screen (system / light / dark), and the choice is persisted via Redux.


4. The useThemeColors Hook

A handful of APIs in the template do not accept a className — they need a color value. Examples:

  • react-native-paper's TextInput (which the template still uses for outlined inputs and validation styling).
  • React Navigation's screenOptions (header background, tab bar tint, etc.).
  • The native status bar.

For these cases the template ships a useThemeColors hook at src/hooks/UseThemeColors.ts. It returns theme-aware values that mirror the Tailwind tokens:

ts
const {
  primaryContainer,   // Paper TextInput surface
  headerBg,           // Navigator headers
  headerText,
  iconColor,          // Inactive icon tint
  accentColor,        // Active icon tint / focused tab
  destructiveColor,
  inputBorderColor,
} = useThemeColors();

Rule of thumb: prefer className with dark: variants whenever the component supports it; reach for useThemeColors only for the components listed above.


5. React Native Paper

react-native-paper is still in the template's package.json, but its usage has been narrowed down to a single component: TextInput in outlined mode (used for forms because of its strong validation/error UI). Buttons, lists, modals, drawers, and icons are all NativeWind + @expo/vector-icons (Ionicons) now.

If you add new forms, follow the same split:

  • Use Paper's TextInput (with values from useThemeColors) for text fields.
  • Use plain View/Text/Pressable with NativeWind classes for everything else.

6. Customizing the Theme

Most customization happens in tailwind.config.js. To introduce a brand color, extend the colors map and reuse the { DEFAULT, dark } shape so the dark: variants keep working:

js
// tailwind.config.js
module.exports = {
  // ...
  theme: {
    extend: {
      colors: {
        // ...
        brand: {
          DEFAULT: '#2563eb',
          dark: '#3b82f6',
          foreground: '#ffffff',
          'dark-foreground': '#ffffff',
        },
      },
    },
  },
};

You can then use it from any component:

tsx
<Pressable className="bg-brand dark:bg-brand-dark rounded-lg px-md py-sm">
  <Text className="text-brand-foreground dark:text-brand-dark-foreground font-semibold">
    Get started
  </Text>
</Pressable>

If the new color also needs to be reachable from non-className APIs (Paper, navigators, status bar), add a matching entry to useThemeColors so the rest of the template stays consistent.


7. Going Further