Theming — MeoNode UI
Context-based theming with automatic value resolution. Define your theme object, wrap your app with ThemeProvider, and
reference theme tokens anywhere using string paths—no prop drilling required.
Theme Structure
Every theme requires two root properties:
interface Theme { mode: 'light' | 'dark' | string system: { 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 custom tokens as needed } }
Both mode and system are user-typeable via the MeoTheme interface — see
Typed Theme Tokens below.
Required:
mode— Theme mode identifier (light, dark, or custom)system— Design tokens (colors, spacing, typography, etc.)
Example Theme:
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', }, }, // Add custom properties for your design system }
ThemeProvider
Wrap your app with ThemeProvider to enable theme access throughout the component tree:
import { ThemeProvider, Column, H1, Text } from '@meonode/ui' import { lightTheme } from './theme' const AppContent = () => Column({ padding: 20, children: [ H1('Themed Application', { color: 'theme.primary', fontSize: '2.5rem', }), Text('Automatic theme resolution', { backgroundColor: 'theme.base', color: 'theme.base.content', padding: 10, }), ], }).render() const App = () => ThemeProvider({ theme: lightTheme, children: AppContent(), }).render()
All MeoNode components inside ThemeProvider automatically access the theme—no prop passing required.
Accessing Theme Values
String Path Notation
Reference theme tokens using dot-separated paths prefixed with theme.:
import { Column, Button } from '@meonode/ui' Column({ backgroundColor: 'theme.primary', padding: 'theme.base.accent', children: Button('Submit', { backgroundColor: 'theme.secondary', color: 'theme.secondary.content', }), })
Default Key Resolution:
Partial paths (e.g., 'theme.primary') automatically resolve to the default key within that object. If no default
exists, it throws an error.
// These are equivalent: backgroundColor: 'theme.primary' backgroundColor: 'theme.primary.default'
Direct Object Access
Use the useTheme hook for direct access with full TypeScript support:
import { useTheme, Column, Text } from '@meonode/ui' const ThemedComponent = () => { const { theme } = useTheme() return Column({ padding: theme.system.accent.default, backgroundColor: theme.system.base.default, children: Text('Direct access', { color: theme.system.base.content, }), }).render() }
Theme Functions in css Prop
Pass functions to css prop properties for dynamic theme-based computations:
import { Div } from '@meonode/ui' import tinycolor from 'tinycolor2' Div({ backgroundColor: 'theme.primary', css: { // Simple theme access color: theme => theme.system.primary.content, // Computed values boxShadow: theme => `0 4px 14px 0 ${tinycolor(theme.system.primary.default).setAlpha(0.38).toString()}`, }, })
This provides maximum flexibility for complex styling logic based on theme tokens.
Typed Theme Tokens
By default, any 'theme.<anything>' string is accepted on style props — useful, but you get no autocomplete and typos are silent until runtime.
To opt in to autocomplete and compile-time validation of token paths, augment the MeoTheme interface once in your project (e.g. in a meonode.d.ts file at your src root):
declare module '@meonode/ui' { interface MeoTheme { mode: 'light' | 'dark' | 'sepia' system: { primary: { default: string; content: string } secondary: { default: string; content: string } base: { default: string; content: string } spacing: { sm: number; md: number; lg: number } } } }
Once declared, token strings narrow to a literal union and theme.mode / theme.system reflect your shape:
import { Div, useTheme } from '@meonode/ui' // ✅ Autocompletes and type-checks every path Div({ color: 'theme.primary', // suggested backgroundColor: 'theme.base', // suggested padding: 'theme.spacing.md', // suggested }) // ❌ Compile error — no such token Div({ color: 'theme.primry' }) // ✅ Typed mode + system in function form Div({ css: { color: theme => theme.mode === 'sepia' ? theme.system.primary.default : theme.system.secondary.default, }, })
Notes:
- Both
modeandsystemkeys are independently optional — augment whichever you want typed. - Without augmentation, behavior is unchanged: any
'theme.…'string is allowed. - Plain CSS values (
'red','flex',16) keep working alongside token strings — autocomplete for native CSS keywords is preserved. - The
useThemehook returns the same augmentedThemetype, sotheme.system.<path>autocompletes too.
Function Children
Function children automatically inherit theme context:
import { ThemeProvider, Column } from '@meonode/ui' ThemeProvider({ theme: myTheme, children: () => Column({ color: 'theme.accent', padding: 'theme.spacing.md', }), })
The child function's returned nodes receive the theme automatically.
Complete Example
import { ThemeProvider, Column, H1, Button, Text } from '@meonode/ui' const lightTheme = { mode: 'light', system: { primary: { default: '#3B82F6', content: '#FFFFFF' }, secondary: { default: '#10B981', content: '#FFFFFF' }, base: { default: '#F9FAFB', content: '#1F2937' }, spacing: { sm: 8, md: 16, lg: 24 }, }, } const ThemedApp = () => Column({ padding: 'theme.spacing.lg', backgroundColor: 'theme.base', children: [ H1('MeoNode Theming', { color: 'theme.primary', marginBottom: 'theme.spacing.md', }), Text('Context-based theme system with automatic resolution', { color: 'theme.base.content', marginBottom: 'theme.spacing.md', }), Button('Primary Action', { backgroundColor: 'theme.primary', color: 'theme.primary.content', padding: '12px 24px', borderRadius: 8, }), ], }).render() const App = () => ThemeProvider({ theme: lightTheme, children: ThemedApp(), }).render() export default App
Multiple Themes
Switch themes dynamically or nest ThemeProvider for different theme contexts:
import { useState } from 'react' import { ThemeProvider, Column, Button } from '@meonode/ui' const darkTheme = { mode: 'dark', system: { primary: { default: '#60A5FA', content: '#1E3A8A' }, base: { default: '#1F2937', content: '#F9FAFB' }, }, } const App = () => { const [theme, setTheme] = useState(lightTheme) return ThemeProvider({ theme, children: Column({ children: [ Button('Toggle Theme', { onClick: () => setTheme(theme.mode === 'light' ? darkTheme : lightTheme), }), ], }), }).render() }
Best Practices
Structure system Property
Organize design tokens logically: colors, spacing, typography, shadows, borders.
Use Semantic Names
primary, secondary, success, warning over blue, green.
Default Keys for Variants
Use default as the base value, add content, hover, active for variants.
system: { primary: { default: '#3B82F6', content: '#FFFFFF', hover: '#2563EB', active: '#1D4ED8' } }
TypeScript for Type Safety
Augment the MeoTheme interface to get autocomplete and compile-time validation on 'theme.<path>' strings — see Typed Theme Tokens.
Function-Based Computations
Use theme functions in css prop for dynamic color transformations or complex calculations.
Common Patterns
Color System
system: { // Core colors primary: { default: '#3B82F6', content: '#FFFFFF' }, secondary: { default: '#10B981', content: '#FFFFFF' }, // Semantic colors success: { default: '#10B981', content: '#FFFFFF' }, warning: { default: '#F59E0B', content: '#78350F' }, error: { default: '#EF4444', content: '#FFFFFF' }, // Surface colors base: { default: '#FFFFFF', content: '#1F2937' }, surface: { default: '#F9FAFB', content: '#374151' }, overlay: { default: 'rgba(0, 0, 0, 0.5)', content: '#FFFFFF' } }
Spacing Scale
system: { spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, '2xl': 48 } }
Typography
system: { typography: { fontFamily: { sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto', mono: 'ui-monospace, SFMono-Regular, Menlo' }, fontSize: { sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem' }, fontWeight: { normal: 400, medium: 500, semibold: 600, bold: 700 } } }
Related FAQ
How does the theming system work?
Wrap your app with ThemeProvider, define tokens under theme.system, and reference values using token paths like theme.primary or through useTheme().
How does styling work in MeoNode UI?
Styling is prop-driven and theme-aware. Use direct CSS props for simple styles, and use the css prop for selectors, queries, and keyframes.
More details: /docs/getting-started/faq
Next Steps
- Framework Integration — Next.js, Vite, Remix configuration
- FAQ — Common patterns, troubleshooting, migration strategies
- Release Notes — Changelog, breaking changes, upgrade guides
On this page
- Theming — MeoNode UI