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 and the Portal system.
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' 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
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)
import { configureStore } from '@reduxjs/toolkit' import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux' export const store = configureStore({ reducer: { // Add your app-specific reducers here }, }) export type RootState = ReturnType<typeof store.getState> export type AppDispatch = typeof store.dispatch export const useAppDispatch: () => AppDispatch = useDispatch export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
Provider Setup
Simplified Providers Component
Create a provider component using MeoNode UI's built-in theme and portal management:
import { useMemo } from 'react' import { store } from '@src/redux/store' import { type Children, Node, type NodeElement, type Theme, PortalProvider, PortalHost } 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>(() => { 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: PortalProvider({ children: [children, PortalHost()], }), }), }), })
Router Setup
Route Component
import { createBrowserRouter, type RouteObject, RouterProvider } from 'react-router' import { lazy, Suspense } from 'react' import { Center, Node, Text } from '@meonode/ui' const App = lazy(() => import('@src/pages/App')) const routes: RouteObject[] = [ { path: '/', element: Node(Suspense, { fallback: Center({ height: '100vh', children: Text('Loading...') }).render(), children: Node(App) }).render(), }, ] const router = createBrowserRouter(routes) const Routes = () => Node(RouterProvider, { router }) export default Routes
Application Components
App Component
import { Center, Column, Row, H1, H2, Button, Text, Div, usePortal, useTheme, type PortalLayerProps } from '@meonode/ui' import { useState } from 'react' import darkTheme from '@src/constants/themes/darkTheme.ts' import lightTheme from '@src/constants/themes/lightTheme.ts' const App = () => { const { setTheme, mode } = useTheme() const portal = usePortal() return Center({ minHeight: '100dvh', backgroundColor: 'theme.base', color: 'theme.base.content', children: Column({ gap: 'theme.spacing.lg', textAlign: 'center', children: [ H1('Vite + MeoNode UI', { fontSize: 'theme.text.4xl' }), Row({ gap: 'theme.spacing.md', children: [ Button('🎯 Open Demo Portal', { onClick: () => portal.open(InteractiveDemo) }), Button('Toggle Theme', { onClick: () => setTheme(t => t.mode === 'light' ? darkTheme : lightTheme) }), ] }) ], }), }).render() } // Interactive demo modal component const InteractiveDemo = ({ close }: PortalLayerProps) => { const [counter, setCounter] = useState(0) return Center({ position: 'fixed', inset: 0, backgroundColor: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(8px)', zIndex: 1000, onClick: e => e.target === e.currentTarget && close(), children: Column({ backgroundColor: 'theme.base', padding: 'theme.spacing.2xl', borderRadius: '20px', gap: 'theme.spacing.lg', children: [ H2('Interactive Portal'), Text(`Counter: ${counter}`), Row({ gap: 'theme.spacing.md', children: [ Button('-', { onClick: () => setCounter(c => c - 1) }), Button('+', { onClick: () => setCounter(c => c + 1) }), ] }), Button('Close', { onClick: close }) ] }) }).render() } export default App
Main Entry Point
Updated main.tsx
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')!)
Best Practices for Vite Integration
- Context-Based Theming: Always wrap your app with
ThemeProvider(usually via aWrappercomponent) to ensure theme context is available everywhere. - Portal System: Include
PortalProviderandPortalHostin your root wrapper. Portals opened viausePortalwill automatically inherit the application context (theme, redux, etc.). - Functional Components: Portal content should be regular functional components that return
.render()and acceptPortalLayerProps. - Tree Shaking: Vite's ES modules approach naturally supports tree shaking for MeoNode UI components.
- Persistence: Use
localStoragein yourWrapperto persist the user's theme preference.
Additional Resources
- Live Example: Check out the meonode-vite repository.
- Redux Toolkit: Official Documentation.
- Vite: Official Documentation.
On this page
- Vite + React + TypeScript Integration Guide for MeoNode UI