@crystin001/theme-settings-lib
v1.4.0
Published
Reusable theme and settings library for mobile apps
Maintainers
Readme
Theme & Settings Library
A reusable library for theme management, settings, and internationalization that can be shared across multiple React Native applications.
Features
- 🎨 Multiple Theme Families - 9 theme families with light and dark variants, plus 3 user-customizable slots
- 🌍 Built-in Localization - Settings UI ships translated in 6 languages (en-US, es-ES, ja-JP, pt-BR, zh-CN, zh-TW); apps choose which ones to expose
- 💾 Persistent Settings - Theme, language, custom themes, recent swatches and background pattern preferences saved automatically
- 🟦 Background Patterns - SVG grid/stripes/zigzag overlay with thickness, spacing, opacity, rotation and swap-colors toggle
- 🧩 Drop-in Settings UI -
LanguageSection,ThemeSectionandAboutSectioncomponents you can compose into your settings screen - 🎨 Custom Theme Builder - In-app HSV color picker that generates accessible custom themes from a few seed colors
- ♿ Accessibility - WCAG-compliant contrast utilities and theme generator
- 🔧 Type-Safe - Full TypeScript support with declaration files
Installation
Option A: Git Repository (Recommended)
Add to your package.json:
{
"dependencies": {
"@crystin001/theme-settings-lib": "git+https://github.com/trial-and-code/theme-settings-lib.git"
}
}Then run npm install. The library will automatically build during installation.
Note: The library requires TypeScript to build. It will be installed automatically as a dev dependency and the build will run during npm install.
To install a specific version or branch:
{
"dependencies": {
"@crystin001/theme-settings-lib": "git+https://github.com/trial-and-code/theme-settings-lib.git#v1.0.0"
}
}Option B: Local Package (Development)
For local development, you can use a file path to link the library directly:
{
"dependencies": {
"@crystin001/theme-settings-lib": "file:../theme-settings-lib"
}
}Note: Make sure to run npm run build in the library directory after making changes.
Option C: npm Package (Future)
npm install @crystin001/theme-settings-libUsage
1. Wrap Your App with SettingsProvider
Wrap your root component with SettingsProvider to enable theme and settings management. Pass your app's available language list:
// app/_layout.tsx or App.tsx
import {
SettingsProvider,
BackgroundPatternProvider,
type LanguageOption,
} from '@crystin001/theme-settings-lib';
import { Stack } from 'expo-router';
const MY_LANGUAGES: LanguageOption[] = [
{ code: 'en-US', name: 'English (US)', available: true },
{ code: 'es-ES', name: 'Español (ES)', available: true },
{ code: 'ja-JP', name: '日本語 (JP)', available: false, comingSoonLabel: '近日公開' },
];
export default function RootLayout() {
return (
<SettingsProvider availableLanguages={MY_LANGUAGES}>
<BackgroundPatternProvider>
<Stack>
{/* Your app screens */}
</Stack>
</BackgroundPatternProvider>
</SettingsProvider>
);
}The lib ships its own translations for all
settings.*,common.*anderrors.*keys it consumes (currently in en-US, es-ES, ja-JP, pt-BR, zh-CN and zh-TW). Apps just need to (a) decide which language codes appear in the menu viaavailableLanguages, and (b)i18n.addResourceBundle(...)their own app-specific keys. See Localization below.
2. Use Theme Hooks in Your Components
Access theme colors and settings in your components:
import { View, Text, Button } from 'react-native';
import { useTheme, useSettings } from '@crystin001/theme-settings-lib';
function MyComponent() {
const colors = useTheme();
const { setThemeFamily } = useSettings();
return (
<View style={{ backgroundColor: colors.background }}>
<Text style={{ color: colors.text }}>Hello World</Text>
<Button
onPress={() => setThemeFamily('neon-surf')}
title="Change to Neon Surf"
/>
</View>
);
}3. Use Translations
Access translations using the useTranslation hook:
import { Text } from 'react-native';
import { useTranslation } from '@crystin001/theme-settings-lib';
function MyComponent() {
const { t } = useTranslation();
return (
<Text>{t('common.welcome')}</Text>
);
}4. Get Specific Theme Colors
Use useThemeColor to get specific colors from the current theme:
import { View, Text } from 'react-native';
import { useThemeColor } from '@crystin001/theme-settings-lib';
function MyComponent() {
const backgroundColor = useThemeColor({}, 'background');
const textColor = useThemeColor({}, 'text');
const primaryColor = useThemeColor({}, 'primary');
return (
<View style={{ backgroundColor }}>
<Text style={{ color: textColor }}>Primary: {primaryColor}</Text>
</View>
);
}5. Drop-in Settings UI Sections
The library ships three composable section components for settings screens. Each is optional and accepts a render-prop Icon so your app's icon set (peak-you's IconSymbol, Ionicons, etc.) is used:
import { useState } from 'react';
import { ScrollView } from 'react-native';
import {
LanguageSection,
ThemeSection,
AboutSection,
} from '@crystin001/theme-settings-lib';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { ThemedText } from '@/components/themed-text';
import appIcon from '@/assets/icon.png';
export function SettingsScreen() {
const [open, setOpen] = useState({ theme: false, language: false, about: false });
const toggle = (k: keyof typeof open) => setOpen(p => ({ ...p, [k]: !p[k] }));
return (
<ScrollView>
<ThemeSection
expanded={open.theme}
onToggle={() => toggle('theme')}
Icon={IconSymbol}
Text={ThemedText}
/>
<LanguageSection
expanded={open.language}
onToggle={() => toggle('language')}
Icon={IconSymbol}
Text={ThemedText}
/>
<AboutSection
expanded={open.about}
onToggle={() => toggle('about')}
Icon={IconSymbol}
Text={ThemedText}
appName="My App"
appVersion="1.0.0 (1)"
appIcon={appIcon}
description="Short marketing description."
dataPrivacyText="Your data stays on your device."
supportEmail="[email protected]"
privacyPolicyUrl="https://example.com/privacy"
/>
</ScrollView>
);
}Notes:
Iconis optional. When omitted, sections fall back to plain text glyphs.Textis optional. When omitted, plain RNTextstyled with the theme color is used.iconNameslets you remap default SF-Symbol-style names to your icon set.ThemeSectionautomatically renders the background pattern controls when wrapped in aBackgroundPatternProvider. PassshowBackgroundPattern={false}to hide them.
6. Built-in Localization
The library ships translations for the strings rendered by its UI sections (background pattern controls, custom theme labels, about / language headers, etc.) in:
| Code | Language |
| ------- | ----------------- |
| en-US | English (US) |
| es-ES | Español (España) |
| ja-JP | 日本語 |
| pt-BR | Português (BR) |
| zh-CN | 简体中文 |
| zh-TW | 繁體中文 |
These are loaded automatically when you import anything from @crystin001/theme-settings-lib. You don't have to ship them yourself.
import { LIB_LOCALES, libResources } from '@crystin001/theme-settings-lib';
// LIB_LOCALES is the list of BCP-47 codes the lib has translations for.
// Use it to drive your `availableLanguages` if you want to expose every
// language the lib supports out of the box:
const ALL_LIB_LANGUAGES = LIB_LOCALES.map((code) => ({ code, name: code }));If your app already manages its own i18next instance, you can merge the lib's
bundles by re-using libResources directly.
Add app-specific keys
Apps still own translations for their own UI. Add them with addResourceBundle
using deep = true, overwrite = true so they merge with — rather than replace —
the lib's defaults:
import { i18n } from '@crystin001/theme-settings-lib';
i18n.addResourceBundle(
'en-US',
'translation',
{
myFeature: { title: 'My Feature', description: 'This is my feature' },
// overrides for any lib key are also fine here
settings: { about: 'About this app' },
},
true, // deep merge
true // overwrite individual keys when they collide
);Adding more languages
Future minor releases will add more entries to LIB_LOCALES. Apps don't need
any code changes to pick those up — they just become available the next time
you upgrade the package.
7. Complete Example: Integration with React Navigation
Here's a complete example showing integration with React Navigation:
// app/_layout.tsx
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { SettingsProvider, useSettings } from '@crystin001/theme-settings-lib';
function RootLayoutNav() {
const { currentTheme } = useSettings();
const isDarkTheme = currentTheme.endsWith('-dark');
return (
<ThemeProvider value={isDarkTheme ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
<StatusBar style={isDarkTheme ? 'light' : 'dark'} />
</ThemeProvider>
);
}
export default function RootLayout() {
return (
<SettingsProvider>
<RootLayoutNav />
</SettingsProvider>
);
}Available Theme Families
- System - Follows device theme preference (default)
- Neon Surf - Vibrant neon colors (hot pink, cyan, yellow, orange)
- Pink & Purple - Soft purple and pink tones
- Pastel - Gentle pastel colors
- GitHub - GitHub-inspired theme
- High Contrast - Maximum contrast for accessibility
- Solarized - Solarized color scheme
- Dark Plus - Dark theme with blue accents
Each theme family has both light and dark variants that automatically switch based on device settings.
API Reference
Hooks
useTheme()
Returns all colors from the current theme.
const colors = useTheme();
// Returns: { text, background, tint, icon, ... }useThemeColor(props?, colorName?)
Get a specific color from the current theme.
const backgroundColor = useThemeColor({}, 'background');
const textColor = useThemeColor({ light: '#000', dark: '#fff' }, 'text');useSettings()
Access theme and language settings.
const {
selectedThemeFamily,
currentTheme,
setThemeFamily,
language,
setLanguage,
availableThemeFamilies,
availableLanguages
} = useSettings();useTranslation()
Get the translation function.
const { t } = useTranslation();
const welcomeText = t('common.welcome');Settings Context
The useSettings() hook returns:
selectedThemeFamily- Currently selected theme family (e.g.,'neon-surf')currentTheme- Effective theme being used (includes variant, e.g.,'neon-surf-light')setThemeFamily(family)- Change theme familylanguage- Current language code (e.g.,'en-US')setLanguage(lang)- Change languageavailableThemeFamilies- Array of all available theme families with metadataavailableLanguages- Array of all available languages
Background Pattern
BackgroundPatternProvider
Wraps your app to persist user-chosen background pattern + slider settings.
<BackgroundPatternProvider defaultPattern="grid">
<App />
</BackgroundPatternProvider>useBackgroundPattern()
const {
selectedPattern, // 'none' | 'grid' | 'stripes' | 'zigzag'
setPattern,
settings, // { lineThickness, spacing, opacity, rotation }
setLineThickness, setSpacing, setOpacity, setRotation,
patternOptions, // [{ id, label }]
isLoaded,
} = useBackgroundPattern();<BackgroundPatternOverlay />
Renders the pattern as an absolutely-positioned SVG overlay. Typically used inside a themed background view:
<View style={{ backgroundColor }}>
{isLoaded && (
<BackgroundPatternOverlay
pattern={selectedPattern}
settings={settings}
color={colors.accent2}
/>
)}
{children}
</View>Settings UI Sections
<LanguageSection />— collapsible language picker driven byavailableLanguages.<ThemeSection />— theme family picker plus background pattern controls (when aBackgroundPatternProvideris mounted).<AboutSection />— collapsible "About" panel with app icon, version, support email, privacy policy and (optional) "Rate the app" links.Pass
appStoreId(iOS App Store numeric ID) and/orplayStoreId(Android package name) to add a "Rate the app" row that opens the right store deep-link per platform. PassonRateAppfor fully custom logic (e.g. native StoreKit review prompt).
All three accept expanded, onToggle, optional Icon (e.g. IconSymbol), optional Text (e.g. your ThemedText) and iconNames overrides. See the Usage section above for an example.
Color Utilities
getContrastRatio(color1, color2)
Calculate the contrast ratio between two colors (returns 1-21).
import { getContrastRatio } from '@crystin001/theme-settings-lib';
const ratio = getContrastRatio('#000000', '#FFFFFF'); // Returns ~21getContrastTextColor(backgroundColor, lightColor?, darkColor?, minContrast?)
Get a readable text color for a given background.
import { getContrastTextColor } from '@crystin001/theme-settings-lib';
const textColor = getContrastTextColor('#000000'); // Returns '#FFFFFF'meetsContrastRequirement(foreground, background, level?)
Check if a color combination meets WCAG contrast requirements.
import { meetsContrastRequirement } from '@crystin001/theme-settings-lib';
const isAccessible = meetsContrastRequirement('#000000', '#FFFFFF', 'AA'); // Returns trueensureContrast(foreground, background, level?)
Ensure a color combination has readable contrast, adjusting if needed.
import { ensureContrast } from '@crystin001/theme-settings-lib';
const readableColor = ensureContrast('#888888', '#FFFFFF', 'AA');Contributing / Development
For information about developing the library, including:
- Building the library
- Running the example app for visual theme development
- Project structure
- Development workflow
See SETUP.md in the repository.
Versioning
The library follows Semantic Versioning:
- Major (x.0.0) - Breaking API changes
- Minor (0.x.0) - New themes, features, or languages
- Patch (0.0.x) - Bug fixes and theme adjustments
Troubleshooting
"Unable to resolve path to module" Error
If you get an error like Unable to resolve path to module '@crystin001/theme-settings-lib', try these steps:
The library should build automatically during installation. If you're using a local file path and made changes, rebuild:
cd theme-settings-lib npm run buildNote: When installing from git, the library builds automatically via the
preparescript. You only need to manually build if using a localfile:path and making changes.Clear Metro cache and restart:
# In your app directory npx react-native start --reset-cache # Or for Expo: npx expo start --clearReinstall dependencies:
# In your app directory rm -rf node_modules npm install # Or if using yarn: rm -rf node_modules yarn installIf using local file path, verify the path is correct:
{ "dependencies": { "@crystin001/theme-settings-lib": "file:../theme-settings-lib" } }Make sure the relative path from your app's
package.jsonto the library is correct.For Expo projects, you may need to restart the development server completely:
# Stop the server, then: npx expo start --clearCheck that
dist/folder exists and contains compiled files: The library must be built before it can be used. Runnpm run buildin the library directory.
Peer Dependencies
This library requires the following peer dependencies (installed by your app):
react- React libraryreact-native- React Native frameworki18next- Internationalization frameworkreact-i18next- React bindings for i18nextexpo-localization- Expo localization utilities@react-native-async-storage/async-storage- Async storage for persistencecolor(optional) - Color manipulation libraryreact-native-svg(optional) - Required if you renderBackgroundPatternOverlayor useThemeSection's background pattern controls@react-native-community/slider(optional) - Required for the sliders inThemeSection's background pattern controls
License
MIT
Repository
https://github.com/trial-and-code/theme-settings-lib
For developers: See SETUP.md for development setup, building the library, and running the example app.
