Theming
Theming is hard to get right in React Native. This is the approach we recommend.
There are three files needed for theming:
globals.css
This is where the themes are stored. Each theme should be stored in a React Native supported color format . By default we use the shadcn/ui neutral theme. This file should be kept in the app
directory.
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--radius: 0.625rem;
--background: #ffffff;
--foreground: #252525;
--card: #ffffff;
--card-foreground: #252525;
--popover: #ffffff;
--popover-foreground: #252525;
--primary: #343434;
--primary-foreground: #fbfbfb;
--secondary: #f7f7f7;
--secondary-foreground: #343434;
--success: #22c55e;
--warning: #eab308;
--muted: #f7f7f7;
--muted-foreground: #8e8e8e;
--accent: #f7f7f7;
--accent-foreground: #343434;
--destructive: #ef4444;
--border: #ebebeb;
--input: #ebebeb;
--ring: #b5b5b5;
--chart-1: #f97316;
--chart-2: #06b6d4;
--chart-3: #3b82f6;
--chart-4: #84cc16;
--chart-5: #f59e0b;
--sidebar: #fbfbfb;
--sidebar-foreground: #252525;
--sidebar-primary: #343434;
--sidebar-primary-foreground: #fbfbfb;
--sidebar-accent: #f7f7f7;
--sidebar-accent-foreground: #343434;
--sidebar-border: #ebebeb;
--sidebar-ring: #b5b5b5;
}
.dark:root {
--background: #252525;
--foreground: #fbfbfb;
--card: #343434;
--card-foreground: #fbfbfb;
--popover: #444444;
--popover-foreground: #fbfbfb;
--primary: #ebebeb;
--primary-foreground: #343434;
--secondary: #444444;
--secondary-foreground: #fbfbfb;
--muted: #444444;
--muted-foreground: #b5b5b5;
--accent: #5f5f5f;
--accent-foreground: #fbfbfb;
--destructive: #dc2626;
--success: #16a34a;
--warning: #ca8a04;
--border: rgba(255, 255, 255, 0.1);
--input: rgba(255, 255, 255, 0.15);
--ring: #8e8e8e;
--chart-1: #8b5cf6;
--chart-2: #10b981;
--chart-3: #f59e0b;
--chart-4: #ec4899;
--chart-5: #dc2626;
--sidebar: #343434;
--sidebar-foreground: #fbfbfb;
--sidebar-primary: #8b5cf6;
--sidebar-primary-foreground: #fbfbfb;
--sidebar-accent: #444444;
--sidebar-accent-foreground: #fbfbfb;
--sidebar-border: rgba(255, 255, 255, 0.1);
--sidebar-ring: #707070;
}
}
You can use any of the shadcn/ui themes. See the shadcn/themes for a full list. You will need to convert them from oklch to a supported color format.
You must add :root
to the .dark
class. .dark:root
will not work.
You can also add your own themes. They must follow this format:
/* Tailwind imports, any warnings are safe to ignore */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/*Light and base variables. Anything that is not defined on dark mode will not be overridden.*/
}
.dark:root {
/* Dark only variables */
}
}
/*
You must define the following variables. Below are the default values:
--radius: 0.625rem;
--background: #ffffff;
--foreground: #252525;
--card: #ffffff;
--card-foreground: #252525;
--popover: #ffffff;
--popover-foreground: #252525;
--primary: #343434;
--primary-foreground: #fbfbfb;
--secondary: #f7f7f7;
--secondary-foreground: #343434;
--success: #22c55e;
--warning: #eab308;
--muted: #f7f7f7;
--muted-foreground: #8e8e8e;
--accent: #f7f7f7;
--accent-foreground: #343434;
--destructive: #ef4444;
--border: #ebebeb;
--input: #ebebeb;
--ring: #b5b5b5;
--chart-1: #f97316;
--chart-2: #06b6d4;
--chart-3: #3b82f6;
--chart-4: #84cc16;
--chart-5: #f59e0b;
--sidebar: #fbfbfb;
--sidebar-foreground: #252525;
--sidebar-primary: #343434;
--sidebar-primary-foreground: #fbfbfb;
--sidebar-accent: #f7f7f7;
--sidebar-accent-foreground: #343434;
--sidebar-border: #ebebeb;
--sidebar-ring: #b5b5b5;
*/
tailwind.config.js
This is where you make your theme variables accessible to NativeWind. You should use the configuration below most of the time:
/** @type {import('tailwindcss').Config} */
export default {
darkMode: "class",
content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
presets: [require("nativewind/preset")],
theme: {
extend: {
colors: {
border: "var(--border)",
input: "var(--input)",
ring: "var(--ring)",
background: "var(--background)",
foreground: "var(--foreground)",
primary: {
DEFAULT: "var(--primary)",
foreground: "var(--primary-foreground)",
},
secondary: {
DEFAULT: "var(--secondary)",
foreground: "var(--secondary-foreground)",
},
destructive: {
DEFAULT: "var(--destructive)",
foreground: "var(--destructive-foreground)",
},
success: {
DEFAULT: "var(--success)",
foreground: "var(--success-foreground)",
},
warning: {
DEFAULT: "var(--warning)",
foreground: "var(--warning-foreground)",
},
muted: {
DEFAULT: "var(--muted)",
foreground: "var(--muted-foreground)",
},
accent: {
DEFAULT: "var(--accent)",
foreground: "var(--accent-foreground)",
},
popover: {
DEFAULT: "var(--popover)",
foreground: "var(--popover-foreground)",
},
card: {
DEFAULT: "var(--card)",
foreground: "var(--card-foreground)",
},
sidebar: {
DEFAULT: "var(--sidebar-background)",
foreground: "var(--sidebar-foreground)",
primary: "var(--sidebar-primary)",
"primary-foreground": "var(--sidebar-primary-foreground)",
accent: "var(--sidebar-accent)",
"accent-foreground": "var(--sidebar-accent-foreground)",
border: "var(--sidebar-border)",
ring: "var(--sidebar-ring)",
},
},
borderRadius: {
xl: "calc(var(--radius) + 4px)",
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
full: "100%",
},
},
},
plugins: [],
};
Make sure to update the content property to your tsx paths.
At this point you might be wondering how dark mode is applied. Using the darkMode: "class"
option will automatically apply the dark
class to the root view or body element.
Web usage: You may run into an issue where dark mode is not applied or is broken on web. In that case you can add the following to your root layout:
// Handle dark mode class for web
useEffect(() => {
if (Platform.OS === "web") {
// Type assertion for web platform where document is available
const doc = (globalThis as any).document;
if (doc) {
const root = doc.documentElement;
if (colorScheme === "dark") {
root.classList.add("dark");
} else {
root.classList.remove("dark");
}
}
}
}, [colorScheme]);
theme.ts
This file does not affect your theming with tailwind variables but rather when you need to access the theme variables with JavaScript. This file should be kept in the lib/utils
directory. Keep it in sync with globals.css
.
import {useColorScheme} from "react-native";
export const theme = {
light: {
radius: "0.625rem",
background: "#ffffff",
foreground: "#252525",
card: "#ffffff",
cardForeground: "#252525",
popover: "#ffffff",
popoverForeground: "#252525",
primary: "#343434",
primaryForeground: "#fbfbfb",
secondary: "#f7f7f7",
secondaryForeground: "#343434",
success: "#22c55e",
warning: "#eab308",
muted: "#f7f7f7",
mutedForeground: "#8e8e8e",
accent: "#f7f7f7",
accentForeground: "#343434",
destructive: "#ef4444",
border: "#ebebeb",
input: "#ebebeb",
ring: "#b5b5b5",
chart1: "#f97316",
chart2: "#06b6d4",
chart3: "#3b82f6",
chart4: "#84cc16",
chart5: "#f59e0b",
sidebar: "#fbfbfb",
sidebarForeground: "#252525",
sidebarPrimary: "#343434",
sidebarPrimaryForeground: "#fbfbfb",
sidebarAccent: "#f7f7f7",
sidebarAccentForeground: "#343434",
sidebarBorder: "#ebebeb",
sidebarRing: "#b5b5b5",
},
dark: {
background: "#252525",
foreground: "#fbfbfb",
card: "#343434",
cardForeground: "#fbfbfb",
popover: "#444444",
popoverForeground: "#fbfbfb",
primary: "#ebebeb",
primaryForeground: "#343434",
secondary: "#444444",
secondaryForeground: "#fbfbfb",
muted: "#444444",
mutedForeground: "#b5b5b5",
accent: "#5f5f5f",
accentForeground: "#fbfbfb",
destructive: "#dc2626",
success: "#16a34a",
warning: "#ca8a04",
border: "rgba(255, 255, 255, 0.1)",
input: "rgba(255, 255, 255, 0.15)",
ring: "#8e8e8e",
chart1: "#8b5cf6",
chart2: "#10b981",
chart3: "#f59e0b",
chart4: "#ec4899",
chart5: "#dc2626",
sidebar: "#343434",
sidebarForeground: "#fbfbfb",
sidebarPrimary: "#8b5cf6",
sidebarPrimaryForeground: "#fbfbfb",
sidebarAccent: "#444444",
sidebarAccentForeground: "#fbfbfb",
sidebarBorder: "rgba(255, 255, 255, 0.1)",
sidebarRing: "#707070",
},
};
export const useTheme = () => {
const colorScheme = useColorScheme();
return theme[colorScheme || "light"];
};
// Suppress SVGElement error
class SVGElement {}
// @ts-ignore
globalThis.SVGElement = SVGElement;
At the bottom of the file you will see a global SVGElement class. This is a workaround to fix an issue with react-aria running on native and it must be kept.
To update the file with your theme we recommend the following prompt for LLMs:
Please update the
lib/utils/theme.ts
file with the themes found inapp/globals.css
. Create 2 nested objects, dark and light. Define the radius variable in both light and dark mode. Do not include--
in the variable names. Use camelCase for the variable names.