MeoNode UI ships with a clean, integrated theming system. You define your own theme
object (see required structure below), and provide it via the ThemeProvider
component. The engine resolves theme values in style props at runtime using string paths. Function-based children also inherit the theme automatically.
This system is built for complete flexibility and consistency—while now providing a clear, consistent shape for your theme object.
Starting with v0.3, your theme object must have the following shape:
/** * Theme configuration object. * Requires `mode` and `system` as core theme properties, with support for * unlimited nested theme properties for complex styling systems: * - Simple values (strings, numbers, booleans) * - Nested theme objects with unlimited depth * - Common CSS values and units * - Custom theme variables and tokens * Used for consistent styling and dynamic theme application. */ export type Theme = { /** Theme mode, e.g., 'light', 'dark', or custom modes */ mode: 'light' | 'dark' | string /** System theme configuration with colors and tokens */ system: { // Example structure; you can add your own tokens and nesting here primary: { default: string; content: string } secondary: { default: string; content: string } accent: { default: string; content: string } warning: { default: string; content: string } base: { default: string; content: string; accent?: string } // ...add more tokens as needed } } & Partial<{ [key: string]: string | number | boolean | null | undefined | any | Theme | Record<string, Theme | string | number | boolean | null | undefined | any> }>
Every theme must include:
mode
(light, dark, or custom string)system
(your design tokens, usually containing colors, etc.)export const lightTheme: Theme = { mode: 'light', system: { primary: { default: '#FF6B6B', content: '#4A0000', }, secondary: { default: '#6BCB77', content: '#0A3B0F', }, accent: { default: '#4ECDC4', content: '#1A4A47', }, warning: { default: '#FFE66D', content: '#665A00', }, base: { default: '#F8F8F8', content: '#333333', accent: '#88B04B', }, }, // You can add any other custom keys, tokens, or nested objects as needed }
Note:
In previous versions, the theme object could be any shape (for example, thesystem
keys could be at the root).
Now, you must always nest your tokens under thesystem
key, and include amode
property at the top level.
New in v0.3:
You no longer need to pass thetheme
prop to every top-level component. Instead, wrap your app (or subtree) in theThemeProvider
component, and all MeoNode UI components inside will have access to the theme.
import { ThemeProvider, useTheme, Column, Text, H1, Button } from '@meonode/ui' import { lightTheme } from './theme' function AppContent() { // You can still access the theme with useTheme() if needed const { theme } = useTheme() return Column({ flex: 1, padding: 'theme.system.base.accent', children: [ H1('Themed Application', { color: 'theme.system.primary.default', fontSize: '2.5rem', }), Text('This text uses a custom themed background color.', { backgroundColor: 'theme.system.base.default', padding: 10, color: 'theme.system.base.content' }), ], }).render(); } export default function App() { return ThemeProvider({ theme: lightTheme, children: AppContent() }).render() }
You only need to use ThemeProvider
once at the root (or wherever you want to provide a different theme subtree).
All MeoNode UI components inside automatically receive the theme context—no theme
prop required.
MeoNode UI uses a "smart theme resolve" engine to access your theme's values in two primary ways:
For most components, you can reference theme tokens using a dot-separated string path, always prefixed with theme.
. The engine will parse this string at runtime and resolve it against the theme object.
Column({ backgroundColor: 'theme.system.primary.default', children: [...] });
Default Key Resolution: If you reference a theme object with a partial path (e.g., 'theme.system.primary'
), the engine will automatically attempt to resolve to a key named default
within that object. If no default
key exists, it will result in an error.
When you have direct access to your theme object (e.g., via your application's useTheme
hook), you can use standard JavaScript dot notation to access values. This method provides full TypeScript type-checking and autocomplete.
import { useTheme, Column, Text } from '@meonode/ui'; const DynamicThemedComponent = () => { const { theme } = useTheme(); return Column({ padding: theme.system.base.accent, // Direct object access backgroundColor: theme.system.base.default, // Direct object access children: Text('...', { color: theme.system.base.content, // Direct object access fontSize: '1.1rem', }), }); };
ThemeProvider({ theme: myTheme, children: () => Column({ color: 'theme.system.accent.default' }) });
The child function’s returned Node receives myTheme
automatically, so you can continue to use string paths for styling without passing the theme again.
import { ThemeProvider, Column, Node } from '@meonode/ui'; import { lightTheme } from './theme'; const App = () => ThemeProvider({ theme: lightTheme, children: Column({ padding: 'theme.system.base.accent', children: [ Node('h1', { color: 'theme.system.primary.default', children: 'Hello MeoNode!' }), Node('button', { backgroundColor: 'theme.system.success?.default', color: 'theme.system.success?.content', children: 'Click Me' }) ] }) }).render();
mode
and system
at the root of your theme object.system
property for your tokens (like colors, spacing, etc.).system
key.ThemeProvider
at the top of your app to propagate the theme.Migrating from v0.2:
- Previously, your theme object could be any shape, and you passed it directly as a prop (e.g.,
#000000
at the root).- Now, your theme object must have a
mode
and asystem
property at the top level: > - Move all your color/tokens undersystem
.
- Add a
mode
property ('light'
or'dark'
).- Wrap your app (or subtree) in a
ThemeProvider
and provide the theme object as above.- Update all string paths in your styles to reference properties under the
system
key (e.g.,'theme.system.primary.default'
instead of'#000000'
).
You have a solid understanding of MeoNode UI's theming system. To unleash its full potential, explore:
@meonode/ui
with frameworks like Next.js, Vite, etc.