npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@novakit-app/theme

v1.0.35

Published

Theme system for React Native UI Kit

Readme

@novakit-app/theme

A comprehensive theme management system for React Native applications that integrates seamlessly with @novakit-app/foundation. Provides light/dark mode support, persistent theme storage, and dynamic theme switching.

Features

  • 🌓 Light/Dark Mode: Built-in light and dark theme variants
  • 💾 Persistent Storage: Optional AsyncStorage integration for theme persistence
  • 🔄 System Integration: Automatic system theme detection and following
  • 🎨 Custom Themes: Easy theme customization and extension
  • Performance: Optimized with React.memo and useMemo
  • 🔧 TypeScript: Full TypeScript support with comprehensive types
  • 📱 React Native: Works with React Native, Expo, and React Native Web

Installation

npm install @novakit-app/theme

Note: This package works with @novakit-app/foundation for styling. Install both for the complete experience:

npm install @novakit-app/foundation @novakit-app/theme

Setup Babel Plugin (for className support):

Add to your babel.config.js:

module.exports = {
	plugins: [["@novakit-app/foundation/babel-plugin"]],
};

For theme persistence (optional):

npm install @react-native-async-storage/async-storage

Package Separation

@novakit-app/theme provides:

  • ThemeProvider - React context for theme management
  • useTheme() - Hooks for accessing theme state
  • lightTheme, darkTheme - Pre-built theme configurations
  • Theme switching, persistence, and customization utilities

@novakit-app/foundation provides:

  • parseClassNames() - Style compiler for className strings
  • defaultTheme - Base design tokens
  • Babel plugin for compile-time className transformation
  • All styling utilities and design system tokens
  • Theme utilities like resolveColor(), getSpacing(), etc.

They work together: Theme provides the theme context, Foundation provides the styling engine.

Quick Start

1. Install Dependencies

npm install @novakit-app/foundation @novakit-app/theme

2. Setup Babel Plugin (for className support)

Add to your babel.config.js:

module.exports = {
	plugins: [["@novakit-app/foundation/babel-plugin"]],
};

3. Wrap your app with ThemeProvider

import React from "react";
import { ThemeProvider } from "@novakit-app/theme";

export default function App() {
	return (
		<ThemeProvider
			defaultMode="light" // Initial theme: "light" or "dark"
			followSystem={true} // Follow system theme changes
			persistMode={true} // Save user's theme preference
		>
			<YourApp />
		</ThemeProvider>
	);
}

Light/Dark Theme Setup

The ThemeProvider automatically handles light and dark themes. Here's how it works:

Default Theme Behavior

import { ThemeProvider } from "@novakit-app/theme";

// Option 1: Use built-in light/dark themes (recommended)
<ThemeProvider
	defaultMode="light"
	followSystem={true}
>
	<App />
</ThemeProvider>;

What happens:

  • defaultMode="light" - Starts with light theme
  • followSystem={true} - Automatically switches when user changes system theme
  • Built-in lightTheme and darkTheme are used automatically

Mode-Based Custom Themes (Recommended)

import { ThemeProvider } from "@novakit-app/theme";

// ✅ Define both light and dark themes in one object
const customTheme = {
	light: {
		colors: {
			primary: "#1da1f2", // Twitter blue for light mode
			background: "#ffffff",
			surface: "#f8f9fa",
			text: "#14171a",
		},
	},
	dark: {
		colors: {
			primary: "#00d4ff", // Cyan for dark mode
			background: "#000000",
			surface: "#1a1a1a",
			text: "#ffffff",
		},
	},
};

<ThemeProvider
	customTheme={customTheme}
	defaultMode="light"
	followSystem={true}
>
	<App />
</ThemeProvider>;

What happens:

  • When mode is "light" → uses customTheme.light
  • When mode is "dark" → uses customTheme.dark
  • Automatically switches themes when mode changes
  • Foundation compiler uses the current mode-based theme

Traditional Custom Themes

import { ThemeProvider } from "@novakit-app/theme";
import { createLightTheme, createDarkTheme } from "@novakit-app/theme";

// Create separate themes (legacy approach)
const customLightTheme = createLightTheme({
	colors: {
		primary: "#1da1f2",
		background: "#ffffff",
		surface: "#f8f9fa",
		text: "#14171a",
	},
});

const customDarkTheme = createDarkTheme({
	colors: {
		primary: "#1da1f2", // Same blue for consistency
		background: "#000000",
		surface: "#16181c",
		text: "#ffffff",
	},
});

// Use custom theme (applies to both modes)
<ThemeProvider
	defaultMode="light"
	customTheme={customLightTheme}
>
	<App />
</ThemeProvider>;

Partial Mode Themes

import { ThemeProvider } from "@novakit-app/theme";

// ✅ Only customize light mode, use default dark theme
const customTheme = {
	light: {
		colors: {
			primary: "#1da1f2",
			background: "#ffffff",
			surface: "#f8f9fa",
		},
	},
	// No dark theme - will use default darkTheme
};

<ThemeProvider customTheme={customTheme}>
	<App />
</ThemeProvider>;

Regular Custom Theme (Both Modes)

import { ThemeProvider } from "@novakit-app/theme";

// ✅ Same theme for both light and dark modes
const customTheme = {
	colors: {
		primary: "#1da1f2", // Same color for both modes
		secondary: "#657786",
	},
	spacing: {
		4: 20,
		8: 40,
	},
};

<ThemeProvider customTheme={customTheme}>
	<App />
</ThemeProvider>;

ThemeProvider Configuration Options

Basic Configuration

import { ThemeProvider } from "@novakit-app/theme";

<ThemeProvider
	defaultMode="light" // "light" | "dark" - Initial theme
	followSystem={true} // Follow system theme changes
	persistMode={false} // Save theme preference to storage
	storageKey="@myapp/theme" // Custom storage key
>
	<App />
</ThemeProvider>;

Configuration Examples

// Example 1: Simple setup with system following
<ThemeProvider
	defaultMode="light"
	followSystem={true}
>
	<App />
</ThemeProvider>

// Example 2: With persistence
<ThemeProvider
	defaultMode="dark"
	followSystem={true}
	persistMode={true}
	storageKey="@myapp/theme-mode"
>
	<App />
</ThemeProvider>

// Example 3: Custom theme with overrides
<ThemeProvider
	defaultMode="light"
	customTheme={{
		colors: {
			primary: "#1da1f2",
			secondary: "#657786",
		}
	}}
>
	<App />
</ThemeProvider>

// Example 4: Manual control (no system following)
<ThemeProvider
	defaultMode="light"
	followSystem={false}
	persistMode={true}
>
	<App />
</ThemeProvider>

Understanding the Props

| Prop | Type | Default | Description | | -------------- | ---------------------------------------- | ----------------------- | -------------------------------- | | defaultMode | "light" \| "dark" | "light" | Initial theme mode | | followSystem | boolean | true | Follow system theme changes | | persistMode | boolean | false | Save theme preference to storage | | storageKey | string | "@novakit/theme/mode" | Storage key for persistence | | customTheme | Partial<ThemeConfig> \| ModeBasedTheme | undefined | Custom theme overrides |

customTheme Types:

  • Partial<ThemeConfig> - Regular theme overrides for both modes
  • ModeBasedTheme - Separate themes for light and dark modes

Theme Behavior Flow

// 1. App starts
<ThemeProvider
	defaultMode="light"
	followSystem={true}
	persistMode={true}
>
	<App />
</ThemeProvider>

// 2. ThemeProvider checks:
//    - Is there a saved preference? (if persistMode=true)
//    - What's the system theme? (if followSystem=true)
//    - Use defaultMode as fallback

// 3. User changes system theme (if followSystem=true)
//    - Theme automatically switches
//    - Preference is saved (if persistMode=true)

// 4. User manually toggles theme
//    - Theme switches immediately
//    - System following is disabled
//    - Preference is saved (if persistMode=true)

4. Use className in your components

import React from "react";
import { View, Text } from "react-native";
import { useTheme } from "@novakit-app/theme";

export function MyComponent() {
	const { mode, toggleMode, isLight } = useTheme();

	return (
		<View className="p-4 bg-primary rounded-lg">
			<Text className="text-lg text-white">
				Current mode: {mode}
			</Text>
			<Text onPress={toggleMode}>
				Toggle to {isLight ? "dark" : "light"} mode
			</Text>
		</View>
	);
}

API Reference

ThemeProvider Props

interface ThemeProviderProps {
	children: React.ReactNode;
	customTheme?: Partial<ThemeConfig> | ModeBasedTheme;
	defaultMode?: "light" | "dark";
	followSystem?: boolean;
	persistMode?: boolean;
	storageKey?: string;
}

interface ModeBasedTheme {
	light?: Partial<ThemeConfig>;
	dark?: Partial<ThemeConfig>;
}
  • customTheme: Custom theme overrides to apply (supports mode-based themes)
  • defaultMode: Initial theme mode (default: "light")
  • followSystem: Follow system theme changes (default: true)
  • persistMode: Save theme mode to storage (default: false)
  • storageKey: Storage key for persistence (default: "@novakit/theme/mode")

useTheme Hook

const {
	theme, // Current theme configuration
	mode, // Current mode: "light" | "dark"
	setMode, // Function to set specific mode
	toggleMode, // Function to toggle between modes
	setTheme, // Function to update theme (limited)
	isSystemMode, // Whether following system theme
	isLight, // Boolean: is light mode
	isDark, // Boolean: is dark mode
} = useTheme();

Additional Hooks

// Get only the theme config
const theme = useThemeConfig();

// Get only the mode and mode helpers
const { mode, isLight, isDark } = useThemeMode();

// Get only the control functions
const { setMode, toggleMode, setTheme } = useThemeControls();

Theme Customization

Mode-Based Custom Themes (Recommended)

The easiest way to customize themes is using mode-based custom themes:

import { ThemeProvider } from "@novakit-app/theme";

const customTheme = {
	light: {
		colors: {
			primary: "#1da1f2", // Twitter blue for light mode
			background: "#ffffff",
			surface: "#f8f9fa",
			text: "#14171a",
		},
		spacing: {
			4: 20,
			8: 40,
		},
	},
	dark: {
		colors: {
			primary: "#00d4ff", // Cyan for dark mode
			background: "#000000",
			surface: "#1a1a1a",
			text: "#ffffff",
		},
		spacing: {
			4: 20,
			8: 40,
		},
	},
};

<ThemeProvider customTheme={customTheme}>
	<App />
</ThemeProvider>;

Benefits:

  • Define both light and dark themes in one object
  • Automatic theme switching based on mode
  • Foundation compiler automatically uses the current theme
  • Type-safe with ModeBasedTheme interface

Regular Custom Theme Overrides

import { ThemeProvider } from "@novakit-app/theme";

// Same theme applied to both light and dark modes
const customTheme = {
	colors: {
		primary: "#1da1f2",
		secondary: "#657786",
	},
	spacing: {
		1: 2,
		2: 4,
	},
};

<ThemeProvider customTheme={customTheme}>
	<App />
</ThemeProvider>;

Create Custom Themes

import {
	createLightTheme,
	createDarkTheme,
	extendTheme,
} from "@novakit-app/theme";

// Create a custom light theme
const myLightTheme = createLightTheme({
	colors: {
		primary: "#1da1f2",
		brand: {
			50: "#eef6ff",
			500: "#1da1f2",
			900: "#0a1a3f",
		},
	},
});

// Create a custom dark theme
const myDarkTheme = createDarkTheme({
	colors: {
		primary: "#1da1f2",
		background: "#0a0a0a",
	},
});

// Extend existing themes
const extendedTheme = extendTheme(myLightTheme, {
	typography: {
		fontSize: {
			"2xs": 10,
			"3xl": 32,
		},
	},
});

Theme with Color Palette

import { createThemeWithPalette } from "@novakit-app/theme";

const palette = {
	brand: {
		50: "#eef6ff",
		100: "#dbeafe",
		500: "#3b82f6",
		900: "#1e3a8a",
	},
	accent: "#f59e0b",
};

const theme = createThemeWithPalette(palette);

Advanced Usage

Custom Storage Implementation

import { ThemeProvider } from "@novakit-app/theme";

class CustomStorage {
	async getItem(key: string) {
		// Your custom storage logic
		return localStorage.getItem(key);
	}

	async setItem(key: string, value: string) {
		localStorage.setItem(key, value);
	}

	async removeItem(key: string) {
		localStorage.removeItem(key);
	}
}

<ThemeProvider
	storage={new CustomStorage()}
	storageKey="my-app-theme"
>
	<App />
</ThemeProvider>;

Conditional Theme Application

import { useTheme } from "@novakit-app/theme";

function ConditionalComponent() {
	const { theme, isDark } = useTheme();

	const containerStyle = {
		backgroundColor: isDark
			? theme.colors.surface
			: theme.colors.background,
		padding: theme.spacing[4],
	};

	return <View style={containerStyle} />;
}

Theme-Aware Component

import React from "react";
import { View, Text } from "react-native";

interface ThemedCardProps {
	title: string;
	children: React.ReactNode;
}

export function ThemedCard({ title, children }: ThemedCardProps) {
	return (
		<View className="p-4 bg-surface rounded-lg shadow-md">
			<Text className="text-lg font-semibold text-text mb-2">
				{title}
			</Text>
			{children}
		</View>
	);
}

Mode-Based Theme Examples

Complete App Example

import React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { ThemeProvider, useTheme } from "@novakit-app/theme";

// Define mode-based custom theme
const appTheme = {
	light: {
		colors: {
			primary: "#1da1f2", // Twitter blue
			secondary: "#657786",
			background: "#ffffff",
			surface: "#f8f9fa",
			text: "#14171a",
			textSecondary: "#657786",
		},
	},
	dark: {
		colors: {
			primary: "#00d4ff", // Cyan
			secondary: "#8899a6",
			background: "#000000",
			surface: "#1a1a1a",
			text: "#ffffff",
			textSecondary: "#8899a6",
		},
	},
};

function App() {
	return (
		<ThemeProvider
			customTheme={appTheme}
			defaultMode="light"
			followSystem={true}
			persistMode={true}
		>
			<MainScreen />
		</ThemeProvider>
	);
}

function MainScreen() {
	const { mode, toggleMode, isLight } = useTheme();

	return (
		<View className="flex-1 bg-background">
			<View className="p-6">
				<Text className="text-2xl font-bold text-text mb-4">
					Theme Demo
				</Text>

				<Text className="text-textSecondary mb-6">
					Current mode: {mode}
				</Text>

				<TouchableOpacity
					className="bg-primary p-4 rounded-lg mb-6"
					onPress={toggleMode}
				>
					<Text className="text-white text-center font-semibold">
						Switch to {isLight ? "Dark" : "Light"}{" "}
						Mode
					</Text>
				</TouchableOpacity>

				{/* Color palette demonstration */}
				<View className="space-y-4">
					<View className="bg-primary p-4 rounded-lg">
						<Text className="text-white">
							Primary Color
						</Text>
					</View>
					<View className="bg-secondary p-4 rounded-lg">
						<Text className="text-white">
							Secondary Color
						</Text>
					</View>
					<View className="bg-surface p-4 rounded-lg border border-gray-200">
						<Text className="text-text">
							Surface Color
						</Text>
					</View>
				</View>
			</View>
		</View>
	);
}

Dynamic Theme Creation

import { ThemeProvider } from "@novakit-app/theme";

function createBrandTheme(brandColor: string) {
	return {
		light: {
			colors: {
				primary: brandColor,
				background: "#ffffff",
				text: "#000000",
				surface: "#f8f9fa",
			},
		},
		dark: {
			colors: {
				primary: brandColor,
				background: "#000000",
				text: "#ffffff",
				surface: "#1a1a1a",
			},
		},
	};
}

function App() {
	const brandTheme = createBrandTheme("#1da1f2");

	return (
		<ThemeProvider customTheme={brandTheme}>
			<MyApp />
		</ThemeProvider>
	);
}

How Theme Switching Works with Foundation

The theme package automatically provides the current theme to foundation's styling system. Here's how it works:

Automatic Theme Integration

import { useTheme } from "@novakit-app/theme";
import { resolveColor, getSpacing } from "@novakit-app/foundation";

function MyComponent() {
	const { theme, mode, toggleMode } = useTheme();

	return (
		<View className="p-4 bg-primary rounded-lg">
			<Text className="text-white text-lg">
				Current mode: {mode}
			</Text>
			<Text
				className="text-white underline mt-2"
				onPress={toggleMode}
			>
				Switch to {mode === "light" ? "dark" : "light"} mode
			</Text>
		</View>
	);
}

What happens when you toggle:

  1. toggleMode() changes the theme mode
  2. ThemeProvider updates the theme object
  3. All className styles automatically use the new theme
  4. Foundation's compiler resolves colors using the current theme

Manual Theme Integration

import { useTheme } from "@novakit-app/theme";
import { resolveColor, getSpacing } from "@novakit-app/foundation";

function ManualThemeComponent() {
	const { theme, mode } = useTheme();

	// ✅ Foundation utilities automatically use current theme
	const primaryColor = resolveColor("primary", mode, theme);
	const padding = getSpacing(4, theme);
	const borderRadius = theme.borderRadius.lg;

	return (
		<View
			style={{
				backgroundColor: primaryColor,
				padding,
				borderRadius,
			}}
		>
			<Text style={{ color: theme.colors.white }}>
				Manual theme integration - Mode: {mode}
			</Text>
		</View>
	);
}

Theme-Aware Component Example

import React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { useTheme } from "@novakit-app/theme";

export function ThemeToggleCard() {
	const { mode, toggleMode, isLight, isDark } = useTheme();

	return (
		<View className="p-6 bg-surface rounded-xl shadow-lg mx-4">
			<Text className="text-xl font-bold text-text mb-4">
				Theme Settings
			</Text>

			<Text className="text-textSecondary mb-4">
				Current theme: {mode}
			</Text>

			<TouchableOpacity
				className="bg-primary p-4 rounded-lg"
				onPress={toggleMode}
			>
				<Text className="text-white text-center font-semibold">
					Switch to {isLight ? "Dark" : "Light"} Mode
				</Text>
			</TouchableOpacity>

			{/* These colors automatically change with theme */}
			<View className="flex-row mt-4 space-x-2">
				<View className="w-8 h-8 bg-primary rounded" />
				<View className="w-8 h-8 bg-secondary rounded" />
				<View className="w-8 h-8 bg-success rounded" />
				<View className="w-8 h-8 bg-danger rounded" />
			</View>
		</View>
	);
}

Foundation Integration Deep Dive

import { useTheme } from "@novakit-app/theme";
import {
	resolveColor,
	getSpacing,
	getFontSize,
	parseClassNamesWithTheme,
} from "@novakit-app/foundation";

function AdvancedThemeComponent() {
	const { theme, mode } = useTheme();

	// ✅ All foundation utilities work with current theme
	const styles = {
		container: {
			backgroundColor: resolveColor("surface", mode, theme),
			padding: getSpacing(4, theme),
			borderRadius: theme.borderRadius.lg,
		},
		text: {
			color: resolveColor("text", mode, theme),
			fontSize: getFontSize("lg", theme),
		},
		button: {
			backgroundColor: resolveColor("primary", mode, theme),
			paddingHorizontal: getSpacing(6, theme),
			paddingVertical: getSpacing(3, theme),
		},
	};

	// ✅ Or use className with theme context
	const classNameStyles = parseClassNamesWithTheme(
		"p-4 bg-primary rounded-lg text-white",
		theme
	);

	return (
		<View style={styles.container}>
			<Text style={styles.text}>Advanced theme integration</Text>
			<View style={styles.button}>
				<Text style={{ color: "white" }}>Button</Text>
			</View>
		</View>
	);
}

Best Practices

  1. Wrap your app early: Place ThemeProvider at the root of your app
  2. Use theme tokens: Prefer theme tokens over hardcoded values
  3. Leverage system integration: Use followSystem={true} for better UX
  4. Persist user preference: Enable persistMode for user convenience
  5. Custom themes sparingly: Only override what you need to change
  6. Performance: The theme is memoized, but avoid creating new objects in render

Migration from Basic Theme

If you're migrating from a basic theme setup:

// Before
const theme = { colors: { primary: "#007bff" } };

// After
import { ThemeProvider } from "@novakit-app/theme";

<ThemeProvider customTheme={{ colors: { primary: "#007bff" } }}>
	<App />
</ThemeProvider>;

Troubleshooting

Theme not updating

  • Ensure ThemeProvider wraps your component tree
  • Check that you're using useTheme() hook correctly
  • Verify theme overrides are properly structured

Storage issues

  • AsyncStorage is optional; the package works without it
  • Check storage permissions on your platform
  • Use custom storage implementation if needed

Performance issues

  • Theme is memoized, but custom theme objects should be stable
  • Avoid creating new theme objects in render
  • Use useThemeConfig() if you only need the theme object

TypeScript Support

Full TypeScript support with comprehensive types:

import type {
	ThemeConfig,
	ThemeMode,
	ThemeProviderProps,
	ModeBasedTheme,
	ThemeContextType,
	UseThemeReturn,
} from "@novakit-app/theme";

// Type-safe mode-based theme
const customTheme: ModeBasedTheme = {
	light: {
		colors: { primary: "#1da1f2" },
	},
	dark: {
		colors: { primary: "#00d4ff" },
	},
};

// Type-safe theme provider props
const themeProps: ThemeProviderProps = {
	customTheme,
	defaultMode: "light",
	followSystem: true,
	persistMode: true,
};

Quick Reference

Mode-Based Theme Setup

// ✅ Recommended: Mode-based custom theme
const customTheme = {
	light: { colors: { primary: "#1da1f2" } },
	dark: { colors: { primary: "#00d4ff" } },
};

<ThemeProvider customTheme={customTheme}>
	<App />
</ThemeProvider>;

Usage in Components

// ✅ Automatic theme integration
<View className="p-4 bg-primary rounded-lg">
	<Text className="text-white">Uses current theme</Text>
</View>;

// ✅ Manual theme access
const { theme, mode, toggleMode } = useTheme();
const primaryColor = theme.colors.primary;

Key Benefits

  • Mode-Based Themes: Define light and dark themes in one object
  • Automatic Switching: Themes switch automatically when mode changes
  • Foundation Integration: Compiler automatically uses current theme
  • Type Safety: Full TypeScript support with ModeBasedTheme interface
  • Backward Compatible: Regular custom themes still work

License

MIT