@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.
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
Update your Vite configuration to support Emotion (MeoNode UI's CSS engine):
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [ react({ jsxImportSource: '@emotion/react', babel: { plugins: ['@emotion/babel-plugin'], }, }), ], optimizeDeps: { include: ['@meonode/ui'], }, })
Create a theme management slice for Redux:
import { createSlice, type PayloadAction } from '@reduxjs/toolkit' // Define your theme objects export const lightTheme = { colors: { primary: '#3B82F6', secondary: '#6B7280', background: '#FFFFFF', text: '#111827', }, spacing: { sm: '8px', md: '16px', lg: '24px', xl: '32px' }, typography: { sizes: { sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.5rem', '2xl': '2rem' } } } export const darkTheme = { colors: { primary: '#2563EB', secondary: '#9CA3AF', background: '#111827', text: '#F3F4F6', }, spacing: { sm: '8px', md: '16px', lg: '24px', xl: '32px' }, typography: { sizes: { sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.5rem', '2xl': '2rem' } } } export interface ThemeState { mode: 'light' | 'dark' system: typeof lightTheme | typeof darkTheme } const initialState: ThemeState = { mode: 'light', system: lightTheme, } const themeSlice = createSlice({ name: 'theme', initialState, reducers: { setInitialTheme: (state, action: PayloadAction<'light' | 'dark'>) => { state.mode = action.payload state.system = action.payload === 'light' ? lightTheme : darkTheme }, toggleTheme: state => { const newMode = state.mode === 'light' ? 'dark' : 'light' state.mode = newMode state.system = newMode === 'light' ? lightTheme : darkTheme localStorage.setItem('theme', newMode) }, setTheme: (state, action: PayloadAction<'light' | 'dark'>) => { state.mode = action.payload state.system = action.payload === 'light' ? lightTheme : darkTheme localStorage.setItem('theme', action.payload) }, }, }) export const { toggleTheme, setTheme, setInitialTheme } = themeSlice.actions export default themeSlice.reducer
Set up your Redux store:
import { configureStore } from '@reduxjs/toolkit' import { Provider, useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux' import themeSlice, { ThemeState } from '@src/redux/slice/theme.slice' import { ReduxProviderWrapper } from '@src/redux/store' import { Node, NodeProps } from '@meonode/ui' export interface RootState { theme: ThemeState } export const store = configureStore({ reducer: { theme: themeSlice, }, }) export type AppDispatch = typeof store.dispatch export const useAppDispatch: () => AppDispatch = useDispatch export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector // Wrapper to use Redux Provider with MeoNode's Node API export const ReduxProviderWrapper = ( props?: Omit<NodeProps<typeof Provider>, 'store'> ) => Node(Provider, { ...props, store })
Create a hook to access the theme:
import { useAppSelector } from '@src/redux/store' import { useEffect } from 'react' export const useTheme = () => { const theme = useAppSelector(state => state.theme) useEffect(() => { const currentTheme = localStorage.getItem('theme') if (currentTheme && currentTheme !== theme.mode) { localStorage.setItem('theme', theme.mode) } document.head.querySelector('meta[name="theme-color"]')?.setAttribute('content', theme.system.base.default) const root = document.documentElement if (theme.mode === 'dark') { root.classList.add('dark-theme') root.classList.remove('light-theme') } else { root.classList.add('light-theme') root.classList.remove('dark-theme') } }, [theme.mode]) return theme }
Create a provider component to wrap your application:
import { ReactNode, useEffect } from 'react' import { ReduxProviderWrapper } from '@src/redux/store' interface ProvidersProps { children: ReactNode } export const PortalProviders = Node(StrictMode, { children: ReduxProviderWrapper() })
import { createBrowserRouter, type RouteObject } from 'react-router' import { lazy, Suspense } from 'react' import { Absolute, Center, Node, type NodeElement, Text } from '@meonode/ui' import { RouterProvider } from 'react-router' type RouteType = Omit<RouteObject, 'children' | 'element'> & { element?: NodeElement 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 Fallback = Absolute({ inset: 0, children: Center({ height: '100%', children: Text('Loading...') }).render(), }) const wrapElement = (routes: RouteType[]): RouteObject[] => { return routes.map(route => ({ ...route, element: route.element ? Node(Suspense, { fallback: Fallback, 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 })
Create your main App component using MeoNode UI:
import { Component, Center, Column, H1, Button, Text } from '@meonode/ui' import useTheme from '@src/hooks/useTheme' import { useAppDispatch } from '@src/redux/store/store' import lightTheme from '@src/constants/themes/lightTheme' import darkTheme from '@src/constants/themes/darkTheme' const App = () => { const { theme, setTheme } = useTheme() const dispatch = useAppDispatch() return Center({ minHeight: '100vh', padding: '2rem', backgroundColor: 'theme.colors.background', children: Column({ gap: '1.5rem', maxWidth: '800px', textAlign: 'center', children: [ H1('Welcome to MeoNode UI + Vite', { fontSize: 'theme.typography.sizes.2xl', color: 'theme.colors.text', marginBottom: '1rem' }), Text('Build React UIs with type-safe fluency without JSX syntax', { fontSize: 'theme.typography.sizes.lg', color: 'theme.colors.text', lineHeight: 1.6, marginBottom: '2rem' }), Button('Get Started', { backgroundColor: 'theme.colors.primary', color: 'white', padding: '1rem 2rem', borderRadius: '8px', fontSize: 'theme.typography.sizes.lg', cursor: 'pointer', css: { '&:hover': { transform: 'translateY(-2px)', boxShadow: '0 8px 16px rgba(59, 130, 246, 0.3)' } } }), Button('Toggle Theme', { backgroundColor: 'theme.colors.secondary', color: 'white', padding: '1rem 2rem', borderRadius: '8px', fontSize: 'theme.typography.sizes.lg', cursor: 'pointer', marginTop: '1rem', onClick: () => setTheme(theme.mode === 'dark' ? lightTheme : darkTheme) }) ] }) }).render() } export default App
Update your main.tsx to include the providers:
import React, {StrictMode} from 'react' import ReactDOM from 'react-dom/client' import Router from '@src/router' import './index.css' import { ReduxProviderWrapper } from "@src/redux/store"; ReactDOM.createRoot(document.getElementById('root')!).render( Node(StrictMode, { children: ReduxProviderWrapper({ children }) }).render() )
Create a CSS file for global styles:
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } * { box-sizing: border-box; }
Ensure your TypeScript configuration supports Emotion:
{ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "jsxImportSource": "@emotion/react", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "baseUrl": ".", "rootDir": ".", "paths": { "@src/*": ["./src/*"] } }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] }
Start your development server:
npm run dev
Build for production:
npm run build
For a boilerplate, explore the meonode-vite repository.
Repository Details:
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.
Hot Module Replacement: Vite provides excellent HMR support. Changes to your MeoNode UI components will reflect instantly in the browser.
Code Splitting: Use Vite's dynamic import() to code-split your application at logical points.
Theme Persistence: Use cookies or localStorage to persist theme preferences across sessions.
Performance Monitoring: Use Vite's built-in bundle analyzer to monitor your bundle size and optimize imports.