MeoNode UI
  • Getting Started
    • Overview
    • Installation
    • Usage
    • Styling
    • Theming
    • Rules & Patterns
    • Framework Integration
    • FAQ
    • Release Notes
  • Components

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:

type 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
  }
} & Partial<{
  [key: string]: string | number | boolean | Theme | Record<string, any>
}>

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.


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
Define theme types for autocomplete and compile-time validation.

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
    }
  }
}

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