@matteogiuditta/sbu-thematic v1.0.5
@matteogiuditta/sbu-thematic
@matteogiuditta/sbu-thematic is a professional React Native theming toolkit designed to help developers build visually consistent applications. It provides:
- Centralized Design Tokens: Colors, typography, and spacing are organized into strictly typed enums and objects.
- Context-Based Theming: A
ThemeProvideranduseThemehook allow dynamic theme switching at runtime. - Sbu-Prefixed Wrappers:
SbuThemedViewandSbuThemedTextwrap React Native’sViewandText, automatically applying theme values and common layout props. - TypeScript-First Approach: Strong typing ensures compile-time safety for theme properties.
- Custom Theme Support: Pass partial overrides or entirely new theme objects.
- Automatic Persistence: Selected theme is saved and restored via
AsyncStorage.
Whether you need light/dark mode or want to define entirely custom themes, @matteogiuditta/sbu-thematic provides a robust foundation for theming in React Native.
Table of Contents
- Features
- Installation
- Getting Started
- Usage
- API Reference
- Project Structure
- Scripts
- Testing
- Publishing to npm
- Contributing
- License
Features
- Strict Typing: Leverage TypeScript enums for
ColorKey,TypographyKey,SpacingKey. - Built-in Light & Dark Themes: Default themes include sensible color palettes, typography scales, and spacing.
- Custom Theme Overrides: Easily override built-in tokens or add entirely new theme objects.
- Persistent Theme Selection: Automatically stores the selected theme in AsyncStorage and restores it on app launch.
- Layout Props:
SbuThemedViewsupports layout props likeflex,justifyContent,alignItems,padding, andmargin. - Dynamic Typography:
SbuThemedTextsupportsvariantprop (heading,subheading,body,caption). - Easy to Extend: Add new properties to
Themeinterface and include them in custom themes. - Zero Dependencies Beyond RN & AsyncStorage: Only external runtime dependency is deepmerge for merging theme objects.
Installation
Peer Dependencies
Ensure your React Native app meets the peer dependencies:# Using npm npm install react@^19.0.0 react-native@^0.79.0 @react-native-async-storage/async-storage@^1.17.11 # Or using Yarn yarn add react@^19.0.0 react-native@^0.79.0 @react-native-async-storage/async-storage@^1.17.11Install the Library
Install@matteogiuditta/sbu-thematic:# Using npm npm install @matteogiuditta/sbu-thematic # Or using Yarn yarn add @matteogiuditta/sbu-thematicTypeScript Setup (Optional)
If you use TypeScript, ensure yourtsconfig.jsonincludes:{ "compilerOptions": { "target": "ES2017", "module": "ESNext", "lib": ["ES2020", "DOM"], "declaration": true, "declarationDir": "dist", "outDir": "dist", "rootDir": "src", "strict": true, "jsx": "react", "moduleResolution": "node", "esModuleInterop": true, "skipLibCheck": true, "sourceMap": true }, "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": ["node_modules", "dist", "tests"] }Add
README.mdandLICENSE
EnsureREADME.mdandLICENSEare present at the root of your package so npm can display documentation.
Getting Started
After installing, follow these steps to integrate theming into your React Native app.
1. Wrap Your App
Wrap your application’s root component with ThemeProvider. This sets up the theme context and loads any stored theme name from AsyncStorage.
// App.tsx
import React from "react";
import { SafeAreaView } from "react-native";
import { ThemeProvider } from "@matteogiuditta/sbu-thematic";
const App: React.FC = () => {
return (
<ThemeProvider>
<SafeAreaView style={{ flex: 1 }}>
{/* Your App Content Here */}
</SafeAreaView>
</ThemeProvider>
);
};
export default App;ThemeProviderProps:customThemes?: Partial override of built-in or new theme definitions.initialThemeName?: If provided, overrides storage and uses this theme on mount.
Usage
2. Consume the Theme
Use the useTheme hook anywhere inside the ThemeProvider subtree to access:
themeName: Current theme name (e.g.,"light","dark","ocean")theme: The completeThemeobject, containingcolors,typography, andspacing.setThemeName(name: ThemeName): Function to switch themes at runtime (updates state and AsyncStorage).
import React from "react";
import { View, Button } from "react-native";
import { useTheme } from "@matteogiuditta/sbu-thematic";
export const SampleComponent: React.FC = () => {
const { themeName, setThemeName, theme } = useTheme();
return (
<View style={{ flex: 1, backgroundColor: theme.colors.background }}>
<Button
title={`Switch to ${themeName === "light" ? "dark" : "light"}`}
color={theme.colors.primary}
onPress={() =>
setThemeName(themeName === "light" ? "dark" : "light")
}
/>
</View>
);
};3. Using SbuThemedView and SbuThemedText
SbuThemedView and SbuThemedText are wrappers around React Native’s View and Text, respectively. They automatically apply theme values and accept common layout props.
SbuThemedView
<SbuThemedView
flex={1}
justifyContent="center"
alignItems="center"
padding={theme.spacing.medium}
backgroundColor={theme.colors.background}
style={{ margin: theme.spacing.small }}
>
{/* Children */}
</SbuThemedView>- Props:
flex?: numberjustifyContent?: FlexStyle['justifyContent']alignItems?: FlexStyle['alignItems']padding?: numberpaddingHorizontal?: numberpaddingVertical?: numbermargin?: numbermarginHorizontal?: numbermarginVertical?: numberbackgroundColor?: string(overrides theme background)style?: ViewStyle | ViewStyle[]
SbuThemedText
<SbuThemedText
variant="heading"
color={theme.colors.primary}
margin={theme.spacing.small}
textAlign="center"
>
Themed Heading
</SbuThemedText>- Props:
variant?: TypographyKey("heading" | "subheading" | "body" | "caption")color?: string(override theme text color)margin?: numbermarginHorizontal?: numbermarginVertical?: numbertextAlign?: TextStyle['textAlign']style?: TextStyle | TextStyle[]
4. Custom Themes & Overrides
By default, two built-in themes are provided: light and dark. You can override these or add entirely new theme objects.
Built-In Themes
Light Theme:
{ colors: { primary: "#45e59c", secondary: "#4d73ff", background: "#ffffff", text: "#000000", border: "#dddddd", accent: "#4d73ff" }, typography: { heading: 26, subheading: 22, body: 16, caption: 12 }, spacing: { small: 8, medium: 16, large: 24 } }Dark Theme:
{ colors: { primary: "#67eaae", secondary: "#6e86ff", background: "#000000", text: "#ffffff", border: "#333333", accent: "#6e86ff" }, typography: { heading: 26, subheading: 22, body: 16, caption: 12 }, spacing: { small: 8, medium: 16, large: 24 } }
Providing Custom Overrides
To override built-in tokens, pass a customThemes object to ThemeProvider. Keys should match theme names.
const customThemes = {
light: {
colors: {
primary: "#FF5722", // override only primary color
background: "#FAFAFA"
},
typography: {
heading: 28, // override heading font size
}
},
ocean: {
colors: {
primary: "#009688",
secondary: "#004D40",
background: "#E0F2F1",
text: "#004D40",
border: "#B2DFDB",
accent: "#00796B"
},
typography: {
heading: 30,
body: 18
},
spacing: {
small: 10,
medium: 18,
large: 28
}
}
};
<ThemeProvider customThemes={customThemes}>
{/* ... */}
</ThemeProvider>- Merging Logic:
- If you override
light, only specified keys merge with built-inlight. - If you specify a new key (e.g.,
"ocean"), you provide a fullThemeobject.
- If you override
API Reference
Types & Enums
export enum ColorKey {
Primary = "primary",
Secondary = "secondary",
Background = "background",
Text = "text",
Border = "border",
Accent = "accent"
}
export enum TypographyKey {
Heading = "heading",
Subheading = "subheading",
Body = "body",
Caption = "caption"
}
export enum SpacingKey {
Small = "small",
Medium = "medium",
Large = "large"
}
export type ColorPalette = { [K in ColorKey]: string } & { [key: string]: string | undefined };
export type Typography = { [K in TypographyKey]: number } & { [key: string]: number | undefined };
export type Spacing = { [K in SpacingKey]: number } & { [key: string]: number | undefined };
export interface Theme {
colors: ColorPalette;
typography: Typography;
spacing: Spacing;
[key: string]: any; // allow additional properties
}
export type ThemeName = "light" | "dark" | string;
export interface ThemeContextType {
themeName: ThemeName;
theme: Theme;
setThemeName: (name: ThemeName) => void;
}ThemeProvider
<ThemeProvider
customThemes?: Partial<Record<ThemeName, Partial<Theme>>>
initialThemeName?: ThemeName
>
{children}
</ThemeProvider>Props:
customThemes?: Partial overrides or new theme definitions.initialThemeName?: Force a starting theme instead of reading from AsyncStorage.
Context Values (via
useTheme()):themeName: ThemeNametheme: ThemesetThemeName: (name: ThemeName) => void
useTheme
const { themeName, theme, setThemeName } = useTheme();Returns:
themeName: Current theme identifier.theme: MergedThemeobject.setThemeName(name): Switches theme and persists selection.
Thrown Error:
If used outside ofThemeProvider, throwsError("useTheme must be used within a ThemeProvider").
ThemeContext
export const ThemeContext = React.createContext<ThemeContextType>({
themeName: "light",
theme: placeholderTheme,
setThemeName: () => {}
});- Contains default placeholder values until
ThemeProviderinitializes actual theme.
SbuThemedView
export interface SbuThemedViewProps extends ViewProps {
flex?: number;
justifyContent?: FlexStyle["justifyContent"];
alignItems?: FlexAlignType;
padding?: number;
paddingHorizontal?: number;
paddingVertical?: number;
margin?: number;
marginHorizontal?: number;
marginVertical?: number;
backgroundColor?: string;
style?: ViewStyle | ViewStyle[];
}
export const SbuThemedView: React.FC<SbuThemedViewProps> = (props) => { /* ... */ };Behavior:
- Default
backgroundColorcomes fromtheme.colors.background. - Applies any provided layout props (
flex,justifyContent, etc.). - Merges inline
styleprop at the end (overrides computed props).
- Default
Example:
<SbuThemedView flex={1} justifyContent="center" alignItems="center" padding={theme.spacing.large} backgroundColor="#e0f7fa" // overrides theme background > {/* Content */} </SbuThemedView>
SbuThemedText
export interface SbuThemedTextProps extends TextProps {
variant?: keyof Typography;
color?: string;
margin?: number;
marginHorizontal?: number;
marginVertical?: number;
textAlign?: TextStyle["textAlign"];
style?: TextStyle | TextStyle[];
}
export const SbuThemedText: React.FC<SbuThemedTextProps> = (props) => { /* ... */ };Behavior:
- Default
fontSizecomes fromtheme.typography[variant](defaults tobody). - Default
colorcomes fromtheme.colors.text. - Applies margin and
textAlignif provided. - Merges inline
stylelast.
- Default
Example:
<SbuThemedText variant="heading" marginVertical={theme.spacing.small} color={theme.colors.secondary} > Welcome to My App </SbuThemedText>
Project Structure
sbu-thematic/
├── dist/ # Bundled output (CJS, ESM) and generated .d.ts
├── src/
│ ├── components/
│ │ ├── SbuThemedText.tsx
│ │ ├── SbuThemedView.tsx
│ │ └── index.ts # Export all components
│ ├── context/
│ │ ├── ThemeContext.ts
│ │ └── ThemeProvider.tsx
│ ├── hooks/
│ │ └── useTheme.ts
│ ├── themes/
│ │ ├── defaultDark.ts
│ │ ├── defaultLight.ts
│ │ └── index.ts # Export builtInThemes
│ ├── utils/
│ │ ├── storage.ts # AsyncStorage helpers
│ │ └── fontLoader.ts # (If font loading is used; optional)
│ ├── types.ts # Theme interfaces & enums
│ └── index.ts # Main entry point: re-export everything
├── tests/ # Jest test files
├── package.json
├── tsconfig.json
├── rollup.config.js
├── jest.config.cjs
├── README.md # <— This file
└── LICENSEScripts
In package.json, you have the following scripts:
"scripts": {
"build": "rollup -c",
"test": "jest --passWithNoTests",
"prepare": "npm run build"
}npm run build: Bundles source files fromsrc/intodist/(CommonJS & ESModule) via Rollup.npm test: Runs Jest tests (no tests = no failures).npm run prepare: Automatically triggered onnpm installornpm publish, ensuresdist/is up to date.
Testing
We use Jest and React Test Renderer (or equivalent) to verify functionality. Example tests include:
tests/useTheme.test.tsx: Verifies default theme, switching themes, and fallback behavior.tests/ThemeProvider.test.tsx: Ensures custom theme overrides merge correctly.tests/SbuThemedView.test.tsx: ChecksSbuThemedViewapplies background and layout props.tests/SbuThemedText.test.tsx: ChecksSbuThemedTextapplies typography and color props.tests/fontLoader.test.ts(optional): Tests font loading logic if included.
To run tests:
npm testPublishing to npm
- Bump version in
package.jsonfollowing semver (e.g.,1.0.0→1.0.1). - Build the library:
npm run build - Run tests to ensure everything passes:
npm test - Login to npm (if not already):
npm login - Publish with public access (for a scoped package):
npm publish --access public
After publishing, README.md content appears automatically on the npm package page.
Contributing
- Fork the repository.
- Create a branch for your feature/fix:
git checkout -b feature/awesome-feature - Implement changes in
src/and add tests intests/. - Build & Test locally:
npm run build npm test - Commit & Push:
git commit -m "feat: add awesome feature" git push origin feature/awesome-feature - Open a Pull Request on GitHub.
License
This project is licensed under the MIT License. See LICENSE for details.