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

Vite + React + TypeScript Integration Guide for MeoNode UI

Overview

@meonode/ui is a modern, type-safe React UI library that works perfectly with Vite + React + TypeScript projects. This guide provides a comprehensive setup for integrating MeoNode UI into a Vite-based React application, featuring the new Context-based theming system.

⚠️ Important: This guide is for MeoNode UI v0.3.0+ which introduced breaking changes to the theme system. If you're migrating from v0.2.x, please refer to the Migration Guide.


Vite Vite Integration

Installation & Setup

Create a new Vite project with React and TypeScript support:

# Create a new Vite app with React and TypeScript
npm create vite@latest my-app -- --template react-ts

# Navigate to your project directory
cd my-app

# Install the core MeoNode UI library
npm install @meonode/ui

# Install additional dependencies
npm install @reduxjs/toolkit react-redux

Vite Configuration

Update your Vite configuration to support Emotion (MeoNode UI's CSS engine):

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { visualizer } from 'rollup-plugin-visualizer'
import * as path from 'node:path'
import { dependencies } from './package.json'
import { imagetools } from 'vite-imagetools'
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
import obfuscator from 'vite-plugin-bundle-obfuscator'

function renderChunks(deps: Record<string, string>) {
  const chunks: Record<string, string[]> = {}
  Object.keys(deps).forEach(key => {
    if (['react', 'react-dom'].includes(key)) return
    chunks[key] = [key]
  })
  return chunks
}

// https://vite.dev/config/
export default defineConfig({
  mode: process.env.NODE_ENV,
  plugins: [
    react(),
    imagetools(),
    ViteImageOptimizer(),
    visualizer({ open: true, sourcemap: true, filename: 'bundle_report.html' }),
  ],
  build: {
    minify: 'esbuild',
    cssMinify: 'esbuild',
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          ...renderChunks(dependencies),
        },
      },
    },
  },
  resolve: {
    alias: [{ find: '@src', replacement: path.resolve(__dirname, 'src') }],
  },
})

Theme Configuration

Theme Objects

Create theme objects structure with mode and system properties:

import { Theme } from '@meonode/ui'

const lightTheme: Theme = {
  mode: 'light',
  system: {
    primary: {
      default: '#3B82F6',
      content: '#FFFFFF',
    },
    secondary: {
      default: '#6B7280',
      content: '#FFFFFF',
    },
    accent: {
      default: '#F59E0B',
      content: '#000000',
    },
    base: {
      default: '#FFFFFF',
      content: '#111827',
    },
    surface: {
      default: '#F9FAFB',
      content: '#374151',
    },
    success: {
      default: '#10B981',
      content: '#FFFFFF',
    },
    warning: {
      default: '#F59E0B',
      content: '#000000',
    },
    error: {
      default: '#EF4444',
      content: '#FFFFFF',
    },
    spacing: {
      xs: '4px',
      sm: '8px',
      md: '16px',
      lg: '24px',
      xl: '32px',
      '2xl': '48px',
    },
    text: {
      xs: '0.75rem',
      sm: '0.875rem',
      base: '1rem',
      lg: '1.125rem',
      xl: '1.25rem',
      '2xl': '1.5rem',
      '3xl': '1.875rem',
      '4xl': '2.25rem',
    },
  },
}

export default lightTheme
import { Theme } from '@meonode/ui'

const darkTheme: Theme = {
  mode: 'dark',
  system: {
    primary: {
      default: '#2563EB',
      content: '#FFFFFF',
    },
    secondary: {
      default: '#9CA3AF',
      content: '#000000',
    },
    accent: {
      default: '#D97706',
      content: '#FFFFFF',
    },
    base: {
      default: '#111827',
      content: '#F3F4F6',
    },
    surface: {
      default: '#1F2937',
      content: '#E5E7EB',
    },
    success: {
      default: '#059669',
      content: '#FFFFFF',
    },
    warning: {
      default: '#D97706',
      content: '#FFFFFF',
    },
    error: {
      default: '#DC2626',
      content: '#FFFFFF',
    },
    spacing: {
      xs: '4px',
      sm: '8px',
      md: '16px',
      lg: '24px',
      xl: '32px',
      '2xl': '48px',
    },
    text: {
      xs: '0.75rem',
      sm: '0.875rem',
      base: '1rem',
      lg: '1.125rem',
      xl: '1.25rem',
      '2xl': '1.5rem',
      '3xl': '1.875rem',
      '4xl': '2.25rem',
    },
  },
}

export default darkTheme

Redux Toolkit Redux Toolkit Integration (Optional)

Since MeoNode UI v0.3+ provides built-in theme management through Context, you only need Redux for your application's business logic, not for theme management.

Store Configuration (Simplified)

Set up your Redux store for application state (excluding theme):

import { configureStore } from '@reduxjs/toolkit'
import { Provider, useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'
import { Node } from '@meonode/ui'
// Import your app-specific slices here
// import userSlice from '@src/redux/slice/user.slice'
// import appSlice from '@src/redux/slice/app.slice'

export interface RootState {
  // Define your app-specific state here
  // user: UserState
  // app: AppState
}

export const store = configureStore({
  reducer: {
    // Add your app-specific reducers here
    // user: userSlice,
    // app: appSlice,
  },
})

export type AppDispatch = typeof store.dispatch

export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

Example App Slice (Optional)

If you need application state management, here's an example slice:

import { createSlice, type PayloadAction } from '@reduxjs/toolkit'

export interface AppState {
  isLoading: boolean
  user: {
    name: string
    email: string
  } | null
}

const initialState: AppState = {
  isLoading: false,
  user: null,
}

const appSlice = createSlice({
  name: 'app',
  initialState,
  reducers: {
    setLoading: (state, action: PayloadAction<boolean>) => {
      state.isLoading = action.payload
    },
    setUser: (state, action: PayloadAction<AppState['user']>) => {
      state.user = action.payload
    },
    clearUser: state => {
      state.user = null
    },
  },
})

export const { setLoading, setUser, clearUser } = appSlice.actions
export default appSlice.reducer

Provider Setup

Simplified Providers Component

Create a provider component using MeoNode UI's built-in theme management:

import { StrictMode, useEffect, useMemo, useState } from 'react'
import { store } from '@src/redux/store'
import { type Children, Node, type NodeElement, type Theme } from '@meonode/ui'
import { Provider as ReduxProvider } from 'react-redux'
import { SnackbarProvider } from 'notistack'
import lightTheme from '@src/constants/themes/lightTheme.ts'
import darkTheme from '@src/constants/themes/darkTheme.ts'
import { ThemeProvider as MeoThemeWrapper } from '@meonode/ui'

interface WrappersProps {
  children: NodeElement
}

const ThemeWrapper = ({ children }: { children?: Children }) => {
  const initialTheme = useMemo<Theme>(() => {
    // Initialize from localStorage
    const stored = localStorage.getItem('theme')
    return stored === 'dark' ? darkTheme : lightTheme
  }, [])

  return MeoThemeWrapper({ theme: initialTheme, children }).render()
}

export const Wrapper = ({ children }: WrappersProps) =>
  Node(ReduxProvider, { store, children: Node(ThemeWrapper, { children: Node(SnackbarProvider, { children }) }) })

const PortalThemeWrapper = ({ children }: { children?: Children }) => {
  const [theme, setTheme] = useState<Theme>(() => {
    // Initialize from localStorage
    const stored = localStorage.getItem('theme')
    return stored === 'dark' ? darkTheme : lightTheme
  })

  useEffect(() => {
    const handleStorageChange = (e: StorageEvent) => {
      if (e.key === 'theme') {
        setTheme(e.newValue === 'dark' ? darkTheme : lightTheme)
      }
    }

    // Listen for changes from other tabs/windows
    window.addEventListener('storage', handleStorageChange)

    return () => {
      window.removeEventListener('storage', handleStorageChange)
    }
  }, [])

  return MeoThemeWrapper({ theme, children }).render()
}

export const PortalWrapper = Node(StrictMode, { children: Node(ReduxProvider, { store, children: Node(PortalThemeWrapper) }) })

Router Setup

Route Component

import { createBrowserRouter, type RouteObject } from 'react-router-dom'
import { lazy, Suspense } from 'react'
import { Center, Node, Text } from '@meonode/ui'
import { RouterProvider } from 'react-router-dom'

type RouteType = Omit<RouteObject, 'children' | 'element'> & {
  element?: () => JSX.Element
  children?: RouteType[]
}

const App = lazy(() => import('@src/pages/App'))
const NotFound = lazy(() => import('@src/pages/NotFound'))

const routes: RouteType[] = [
  {
    path: '/',
    element: App,
  },
  {
    path: '*',
    element: NotFound,
  },
]

const LoadingFallback = Center({
  height: '100dvh',
  backgroundColor: 'theme​.base',
  color: 'theme​.base.content',
  children: Text('Loading...', {
    fontSize: 'theme​.text.lg',
  }),
}).render()

const wrapElement = (routes: RouteType[]): RouteObject[] => {
  return routes.map(route => ({
    ...route,
    element: route.element
      ? Node(Suspense, {
          fallback: LoadingFallback,
          children: Node(route.element),
        }).render()
      : undefined,
    children: route.children ? wrapElement(route.children) : undefined,
  })) as RouteObject[]
}

const router = createBrowserRouter(wrapElement(routes))
export default Node(RouterProvider, { router })

Application Components

App Component

Create your main App component using MeoNode UI's built-in theme management:

import { Component, Center, Column, Row, H1, H2, Button, Text, Portal, Div } from '@meonode/ui'
import { useAppTheme } from '@src/hooks/useAppTheme'
import { PortalWrapper } from '@src/components/Wrapper'
import { useState } from 'react'

const App = () => {
  const { theme, mode, toggleTheme, switchToLight, switchToDark } = useAppTheme()
  const [showDemo, setShowDemo] = useState(false)

  return Center({
    minHeight: '100dvh',
    padding: 'theme​.spacing.xl',
    backgroundColor: 'theme​.base',
    color: 'theme​.base.content',
    children: Column({
      gap: 'theme​.spacing.lg',
      maxWidth: '900px',
      textAlign: 'center',
      children: [
        H1('Welcome to MeoNode UI', {
          fontSize: 'theme​.text.4xl',
          color: 'theme​.primary',
          marginBottom: 'theme​.spacing.md',
          fontWeight: 700,
        }),

        Text('Build React UIs with type-safe fluency using Vite + TypeScript', {
          fontSize: 'theme​.text.lg',
          lineHeight: 1.6,
          marginBottom: 'theme​.spacing.xl',
          opacity: 0.9,
        }),

        // Feature highlights
        Row({
          gap: 'theme​.spacing.md',
          justifyContent: 'center',
          flexWrap: 'wrap',
          marginBottom: 'theme​.spacing.xl',
          children: [
            FeatureBadge('🚀 No JSX'),
            FeatureBadge('⚡ Vite Powered'),
            FeatureBadge('🎯 TypeScript'),
            FeatureBadge('🌍 Built-in Themes'),
          ],
        }),

        // Action buttons
        Column({
          gap: 'theme​.spacing.md',
          children: [
            Row({
              gap: 'theme​.spacing.lg',
              justifyContent: 'center',
              flexWrap: 'wrap',
              children: [
                Button('🎯 Interactive Demo', {
                  backgroundColor: 'theme​.primary',
                  color: 'theme​.primary.content',
                  padding: 'theme​.spacing.md theme​.spacing.xl',
                  borderRadius: '12px',
                  fontSize: 'theme​.text.lg',
                  fontWeight: 600,
                  cursor: 'pointer',
                  transition: 'all 0.3s ease',
                  css: {
                    '&:hover': {
                      transform: 'translateY(-2px)',
                      boxShadow: '0 8px 16px rgba(59, 130, 246, 0.3)',
                    },
                  },
                  onClick: () => setShowDemo(true),
                }),

                Button(`Toggle Theme (${mode === 'light' ? 'Dark' : 'Light'})`, {
                  backgroundColor: 'theme​.secondary',
                  color: 'theme​.secondary.content',
                  padding: 'theme​.spacing.md theme​.spacing.xl',
                  borderRadius: '12px',
                  fontSize: 'theme​.text.lg',
                  fontWeight: 600,
                  cursor: 'pointer',
                  transition: 'all 0.3s ease',
                  css: {
                    '&:hover': {
                      transform: 'translateY(-2px)',
                      boxShadow: '0 8px 16px rgba(107, 114, 128, 0.3)',
                    },
                  },
                  onClick: toggleTheme,
                }),
              ],
            }),

            // Direct theme switcher buttons
            Row({
              gap: 'theme​.spacing.sm',
              justifyContent: 'center',
              children: [
                Button('☀️ Light', {
                  backgroundColor: mode === 'light' ? 'theme​.accent' : 'transparent',
                  color: mode === 'light' ? 'theme​.accent.content' : 'theme​.accent',
                  border: '2px solid theme​.accent',
                  padding: 'theme​.spacing.sm theme​.spacing.md',
                  borderRadius: '8px',
                  fontSize: 'theme​.text.sm',
                  cursor: 'pointer',
                  transition: 'all 0.2s ease',
                  onClick: switchToLight,
                }),
                Button('🌙 Dark', {
                  backgroundColor: mode === 'dark' ? 'theme​.accent' : 'transparent',
                  color: mode === 'dark' ? 'theme​.accent.content' : 'theme​.accent',
                  border: '2px solid theme​.accent',
                  padding: 'theme​.spacing.sm theme​.spacing.md',
                  borderRadius: '8px',
                  fontSize: 'theme​.text.sm',
                  cursor: 'pointer',
                  transition: 'all 0.2s ease',
                  onClick: switchToDark,
                }),
              ],
            }),
          ],
        }),

        // Theme info display
        ThemeInfo(),

        // Demo modal trigger
        showDemo ? InteractiveDemo(() => setShowDemo(false)) : null,
      ],
    }),
  }).render()
}

// Feature badge component
const FeatureBadge = (text: string) =>
  Div({
    backgroundColor: 'theme​.surface',
    color: 'theme​.surface.content',
    padding: 'theme​.spacing.sm theme​.spacing.md',
    borderRadius: '20px',
    fontSize: 'theme​.text.sm',
    fontWeight: 500,
    border: '1px solid theme​.primary',
    css: {
      '&:hover': {
        backgroundColor: 'theme​.primary',
        color: 'theme​.primary.content',
      },
    },
    children: text,
  })

// Theme information display
const ThemeInfo = Component(() => {
  const { theme, mode } = useTheme()

  return Column({
    gap: 'theme​.spacing.sm',
    padding: 'theme​.spacing.lg',
    backgroundColor: 'theme​.surface',
    borderRadius: '12px',
    marginTop: 'theme​.spacing.xl',
    children: [
      H2('Current Theme Info', {
        fontSize: 'theme​.text.xl',
        color: 'theme​.primary',
        marginBottom: 'theme​.spacing.sm',
      }),
      Text(`Mode: ${mode}`, {
        fontSize: 'theme​.text.base',
      }),
      Text(`Primary Color: ${theme.system.primary.default}`, {
        fontSize: 'theme​.text.base',
      }),
      Text(`Background: ${theme.system.base.default}`, {
        fontSize: 'theme​.text.base',
      }),

      // Color palette preview
      Row({
        gap: 'theme​.spacing.sm',
        justifyContent: 'center',
        marginTop: 'theme​.spacing.md',
        children: [
          ColorSwatch('primary'),
          ColorSwatch('secondary'),
          ColorSwatch('accent'),
          ColorSwatch('success'),
          ColorSwatch('warning'),
          ColorSwatch('error'),
        ],
      }),
    ],
  })
})

// Color swatch component
const ColorSwatch = (colorKey: string) =>
  Div({
    width: '24px',
    height: '24px',
    backgroundColor: `theme​.${colorKey}`,
    borderRadius: '50%',
    border: '2px solid theme​.base.content',
    title: colorKey,
  })

// Interactive demo modal
const InteractiveDemo = (onClose: () => void) =>
  Portal<{}>(PortalWrapper, ({ portal }) => {
    const [counter, setCounter] = useState(0)
    const { toggleTheme, mode } = useTheme()

    return Center({
      position: 'fixed',
      inset: 0,
      backgroundColor: 'rgba(0,0,0,0.6)',
      backdropFilter: 'blur(8px)',
      zIndex: 1000,
      onClick: e => {
        if (e.currentTarget === e.target) {
          portal.unmount()
          onClose()
        }
      },
      children: [
        Column({
          backgroundColor: 'theme​.base',
          color: 'theme​.base.content',
          padding: 'theme​.spacing.2xl',
          borderRadius: '20px',
          maxWidth: '500px',
          margin: 'theme​.spacing.lg',
          boxShadow: '0 20px 40px rgba(0,0,0,0.3)',
          gap: 'theme​.spacing.lg',
          css: {
            animation: 'slideIn 0.3s ease-out',
            '@keyframes slideIn': {
              from: { opacity: 0, transform: 'scale(0.9) translateY(-20px)' },
              to: { opacity: 1, transform: 'scale(1) translateY(0)' },
            },
          },
          children: [
            H1('🎯 Vite + MeoNode UI', {
              fontSize: 'theme​.text.2xl',
              textAlign: 'center',
              color: 'theme​.primary',
            }),

            Text('Experience Context-based theming!', {
              textAlign: 'center',
              opacity: 0.8,
            }),

            // Counter section
            Column({
              gap: 'theme​.spacing.md',
              padding: 'theme​.spacing.lg',
              backgroundColor: 'theme​.surface',
              borderRadius: '12px',
              children: [
                Text(`Counter: ${counter}`, {
                  fontSize: 'theme​.text.xl',
                  textAlign: 'center',
                  fontWeight: 600,
                  color: 'theme​.primary',
                }),
                Row({
                  gap: 'theme​.spacing.md',
                  justifyContent: 'center',
                  children: [
                    Button('-', {
                      backgroundColor: 'theme​.error',
                      color: 'theme​.error.content',
                      padding: 'theme​.spacing.sm theme​.spacing.md',
                      borderRadius: '8px',
                      cursor: 'pointer',
                      fontWeight: 600,
                      onClick: () => setCounter(c => Math.max(0, c - 1)),
                    }),
                    Button('+', {
                      backgroundColor: 'theme​.success',
                      color: 'theme​.success.content',
                      padding: 'theme​.spacing.sm theme​.spacing.md',
                      borderRadius: '8px',
                      cursor: 'pointer',
                      fontWeight: 600,
                      onClick: () => setCounter(c => c + 1),
                    }),
                  ],
                }),
              ],
            }),

            // Theme toggle in modal
            Button(`Toggle to ${mode === 'light' ? 'Dark' : 'Light'}`, {
              backgroundColor: 'theme​.accent',
              color: 'theme​.accent.content',
              padding: 'theme​.spacing.md theme​.spacing.lg',
              borderRadius: '10px',
              cursor: 'pointer',
              fontWeight: 500,
              onClick: toggleTheme,
            }),

            Button('Close Demo', {
              backgroundColor: 'theme​.base.content',
              color: 'theme​.base',
              padding: 'theme​.spacing.md theme​.spacing.lg',
              borderRadius: '10px',
              cursor: 'pointer',
              fontWeight: 500,
              css: {
                '&:hover': {
                  opacity: 0.8,
                },
              },
              onClick: () => {
                portal.unmount()
                onClose()
              },
            }),
          ],
        }),
      ],
    })
  })

export default App

404 Page

import { Component, Center, Column, H1, Text, Button } from '@meonode/ui'
import { useNavigate } from 'react-router-dom'

const NotFound = Component(() => {
  const navigate = useNavigate()

  return Center({
    minHeight: '100dvh',
    backgroundColor: 'theme​.base',
    color: 'theme​.base.content',
    children: Column({
      gap: 'theme​.spacing.lg',
      textAlign: 'center',
      children: [
        H1('404 - Page Not Found', {
          fontSize: 'theme​.text.3xl',
          color: 'theme​.error',
        }),
        Text('The page you are looking for does not exist.', {
          fontSize: 'theme​.text.lg',
          opacity: 0.8,
        }),
        Button('Go Home', {
          backgroundColor: 'theme​.primary',
          color: 'theme​.primary.content',
          padding: 'theme​.spacing.md theme​.spacing.xl',
          borderRadius: '8px',
          cursor: 'pointer',
          onClick: () => navigate('/'),
        }),
      ],
    }),
  })
})

export default NotFound

Main Entry Point

Updated main.ts

Update your main.ts to include the providers:

import { StrictMode } from 'react'
import { Node } from '@meonode/ui'
import Routes from '@src/routes'
import { Wrapper } from '@src/components/Wrapper.ts'
import '@src/assets/global.css'
import { render } from '@meonode/ui/client'

const App = Node(StrictMode, { children: Wrapper({ children: Routes() }) })
render(App, document.getElementById('root')!)

Migration from v0.2.x to v0.3+

Key Changes for Vite Projects

  1. Theme System: Replace component-level theme props with ThemeProvider
  2. Theme Structure: Update theme objects to use mode and system properties
  3. Context Integration: All theme resolution now happens through React Context

Example Migration

Before (v0.2.x):

// ❌ No longer works in v0.3+
const MyApp = () =>
  Div({
    theme: myTheme,
    backgroundColor: 'theme​.colors.primary',
    children: 'Hello World',
  })

After (v0.3+):

// ✅ v0.3+ approach
const MyApp = () =>
  ThemeProvider({
    theme: myTheme, // Must follow new theme structure
    children: [
      Div({
        backgroundColor: 'theme​.primary', // Automatic resolution
        children: 'Hello World',
      }),
    ],
  })

Running the Application

Start your development server:

npm run dev

Build for production:

npm run build

Preview the production build:

npm run preview

Boilerplate Repository

For a complete boilerplate, explore the meonode-vite repository.

Repository Details:

  • Vite latest for the latest build tooling
  • React latest for the React version
  • @meonode/ui ^1.1.1 with Context-based theming
  • @meonode/mui ^1.2.0 for Material-UI integration
  • Redux Toolkit for state management
  • React Router Dom for client-side routing
  • TypeScript for a fully type-safe development experience

Best Practices for Vite Integration

  1. Context-Based Theming: Always wrap your app with ThemeProvider at the root level to ensure theme context is available throughout your component tree.

  2. Tree Shaking: Vite's ES modules approach naturally supports tree shaking, so only the MeoNode UI components you use will be included in your bundle.

  3. Hot Module Replacement: Vite provides excellent HMR support. Changes to your MeoNode UI components and theme switching will reflect instantly in the browser.

  4. Code Splitting: Use Vite's dynamic import() to code-split your application at logical points, especially for heavy components or pages.

  5. Theme Persistence: Use localStorage or sessionStorage to persist theme preferences across sessions.

  6. Performance Monitoring: Use Vite's built-in bundle analyzer to monitor your bundle size and optimize imports. The new Context system reduces bundle size by eliminating theme prop drilling.

  7. Portal Components: Use the PortalWrapper wrapper for modals and overlays to ensure they have access to the theme context outside the main app tree.

  8. Development Experience: Take advantage of Vite's fast refresh and MeoNode UI's Context-based theming for a smooth development experience with instant theme switching.

  9. Build Optimization: Configure Vite's build optimization settings to take full advantage of MeoNode UI's tree-shakable architecture and Emotion's CSS optimization.


Theme-Aware Components

Create reusable components that adapt to the current theme using MeoNode's built-in theming:

import { Component, Column, H3, Text, Div, Row, useTheme } from '@meonode/ui'

interface ThemeCardProps {
  title: string
  description: string
  variant?: 'primary' | 'secondary' | 'accent'
}

export const ThemeCard = Component<ThemeCardProps>(({ title, description, variant = 'primary' }) => {
  const { theme } = useTheme() // MeoNode's built-in hook

  return Div({
    padding: 'theme​.spacing.lg',
    borderRadius: '16px',
    backgroundColor: 'theme​.surface',
    border: `2px solid theme​.${variant}`,
    css: {
      '&:hover': {
        transform: 'translateY(-4px)',
        boxShadow: `0 8px 32px rgba(${theme.system[variant].default.replace('#', '')}, 0.15)`,
      },
    },
    transition: 'all 0.3s ease',
    children: Column({
      gap: 'theme​.spacing.md',
      children: [
        H3(title, {
          color: `theme​.${variant}`,
          fontSize: 'theme​.text.xl',
          fontWeight: 600,
        }),
        Text(description, {
          color: 'theme​.surface.content',
          fontSize: 'theme​.text.base',
          lineHeight: 1.6,
        }),
        Row({
          justifyContent: 'flex-end',
          children: [
            Div({
              width: '12px',
              height: '12px',
              borderRadius: '50%',
              backgroundColor: `theme​.${variant}`,
            }),
          ],
        }),
      ],
    }),
  })
})

Deployment Considerations

Environment Variables

Create environment-specific configurations:

export const config = {
  isDev: import.meta.env.DEV,
  isProd: import.meta.env.PROD,
  baseUrl: import.meta.env.BASE_URL,
  // Add other environment variables as needed
}

Build Configuration

Optimize your build for production:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['@emotion/babel-plugin'],
      },
    }),
  ],
  optimizeDeps: {
    include: ['@meonode/ui'],
  },
  resolve: {
    alias: {
      '@src': new URL('./src', import.meta.url).pathname,
    },
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          meonode: ['@meonode/ui'],
          redux: ['@reduxjs/toolkit', 'react-redux'],
        },
      },
    },
  },
})

Additional Resources

On this page