The flexible, type-safe theming engine for Tinky
tinky-theme provides a robust architecture for building themeable React components. It decouples component logic from visual styling using a powerful slot-based system, enabling deeply customizable UIs without the maintenance headache.
Designed for scalability, it supports global overrides, dynamic prop-based styling, and automatic deep merging—all fully typed with TypeScript.
- 🧩 Slot-Based Architecture: Define independent styles for every part of a component (e.g.,
root,label,icon). - 🎨 Dynamic Styling: Use functions to drive styles based on component props (e.g.,
disabled,variant). - ⚡ Global Overrides: Instantly rebrand components application-wide via a simple context provider.
- 🔄 Smart Deep Merging: User overrides intelligently merge with default themes—change a color without resetting the padding.
- 📐 TypeScript First: First-class type definitions ensuring autocompletion and type safety for your custom themes.
Install the package via your preferred package manager:
npm install tinky-theme
# or
yarn add tinky-theme
# or
pnpm add tinky-themeNote: This package requires
react(>=16.8.0) andtinkyas peer dependencies.
A Component Theme acts as the blueprint for a component's appearance. It is divided into two main sections:
styles: Not just a single style object, but a collection of styles targeting specific slots (internal parts) of the component. Each slot can be a static object or a dynamic function.config: Non-visual settings (e.g., animation speeds, default icon sets) that consumers might want to override globally.
Instead of prop drilling dozens of style props (labelStyle, containerStyle), tinky-theme maps styles to semantic names.
Example component structure:
root: The outer container.icon: The visual indicator.label: The text content.
Users can target these specific slots in their theme overrides.
When you build a component, you define its "contract" by exporting a default theme. This sets the baseline look and feel.
import type { ComponentTheme } from "tinky-theme";
// 1. Define your component props
interface BadgeProps {
variant?: "solid" | "subtle";
status?: "success" | "warning" | "error";
children: React.ReactNode;
}
// 2. Create the default theme definition
const defaultBadgeTheme: ComponentTheme<BadgeProps> = {
styles: {
// Dynamic style function for the root container
root: (props) => ({
display: "inline-flex",
padding: "4px 8px",
borderRadius: "4px",
// Change color based on status prop
backgroundColor: props.status === "success" ? "green" : "red",
opacity: props.variant === "subtle" ? 0.8 : 1,
}),
// Static style for the text label
label: {
fontSize: "0.875rem",
fontWeight: 600,
color: "white",
},
},
};Inside your component, use the useComponentTheme hook to resolve the final styles. This hook automatically merges the default theme with any global context overrides.
import { useComponentTheme } from "tinky-theme";
import { Box, Text } from "tinky";
export function Badge(props: BadgeProps) {
// 3. Resolve the theme
const theme = useComponentTheme("Badge", defaultBadgeTheme, props);
return (
// 4. Apply styles to slots
<Box {...theme.styles?.root}>
<Text {...theme.styles?.label}>{props.children}</Text>
</Box>
);
}Consumers of your library can override specific parts of the component without rewriting the CSS.
import { ThemeProvider, type Theme } from "tinky-theme";
// Define an application-level theme
const appTheme: Theme = {
components: {
Badge: {
styles: {
root: {
// Override just the border radius, keep everything else
borderRadius: "999px",
textTransform: "uppercase",
},
},
},
},
};
function App() {
return (
<ThemeProvider theme={appTheme}>
<Badge status="success">Approved</Badge>
</ThemeProvider>
);
}function useComponentTheme<P>(
componentName: string,
defaultTheme: ComponentTheme<P>,
props: P,
): ComponentTheme<P>;Returns: A resolved theme object where all functions are executed and merged.
componentName: The unique key used in the global theme registry.defaultTheme: The fallback styles defined by the component author.props: The current props, passed to style functions to generate dynamic values.
A wrapper component that injects the theme context.
<ThemeProvider theme={myTheme}>
<App />
</ThemeProvider>| Prop | Type | Description |
|---|---|---|
theme |
Partial<Theme> |
The global theme object. Can be updated dynamically. |
children |
ReactNode |
The app or component tree. |
A utility to deep-merge standard themes with user customizations.
const finalTheme = extendTheme(baseTheme, userOverrides);tinky-theme exports all necessary types (Theme, ComponentTheme, StyleObject) to ensure your theme definitions are type-checked.
When merging styles:
- Global Overrides take precedence over Default Themes.
- Dynamic Prop Styles are re-evaluated on every render if
propschange. - Deep Merging is used, so valid nested properties (like
config) are merged rather than replaced, unless a conflict occurs at a primitive level.
MIT © ByteLand Technology Limited