Skip to content

Styling & theming

You pick a styling approach when you create the app. All three have the same screens, the same light and dark theme tokens and the same useAppTheme() hook, so the rest of the app doesn’t care which one you chose.

Colors and spacing are defined once in src/styles/themes.ts, as a lightTheme and a darkTheme:

theme.colors.primary // brand color
theme.colors.background // screen background
theme.colors.surface // cards and raised UI, a step above the background in dark mode
theme.colors.typography // main text color
theme.colors.danger // errors
theme.margins.md // spacing scale: sm, md, lg, xl

Colors that read well on both themes (danger, white, onPrimary…) are shared, so each theme only declares what’s different.

For colors you pass as props rather than styles (icon colors, tab bar tints, placeholderTextColor), use the same hook in every option:

import { useAppTheme } from '@/theme';
const theme = useAppTheme();
<Ionicons name="home" color={theme.colors.primary} size={24} />;

The default. Styles are written with Unistyles 3 and receive the theme and runtime info (screen size, insets, breakpoints):

import { StyleSheet } from 'react-native-unistyles';
const styles = StyleSheet.create((theme, rt) => ({
container: {
flex: 1,
backgroundColor: theme.colors.background,
paddingTop: rt.insets.top,
},
title: { color: theme.colors.typography },
}));

Theme changes update styles without re-rendering components. Breakpoints are in src/styles/breakpoints.ts, and src/styles/unistyles.ts registers themes and breakpoints. That file is loaded first by the custom index.ts entry, because styles are created at import time.

src/shared/utils/scale.ts scales sizes from a ~5” phone baseline so layouts look the same on small and large screens:

Helper Scales by Use for
s(size) screen width horizontal padding, widths
vs(size) screen height vertical spacing, heights
ms(size, factor?) width, damped font and icon sizes (factor defaults to 0.5)
mvs(size, factor?) height, damped vertical spacing that shouldn’t grow too much
import { ms } from '@/shared/utils';
<Ionicons name="lock-closed-outline" size={ms(48)} />;

src/shared/components has themed building blocks that every styling option implements: Button (with loading state), TextField, ControlledTextField, ScreenWrapper (safe area and keyboard-aware scrolling), FullScreenLoader, the tab bar icon and label, and AppOverlay (the global loader and toasts).

Toasts are themed too: showSuccessToast, showErrorToast, showInfoToast and showWarningToast from @/shared/utils/toast, each taking { title, message }.