Frequently Asked Questions — MeoNode UI
A collection of common questions and answers to help you get started quickly with MeoNode UI.
18 questions found
Quick Navigation
Common Questions
MeoNode UI is a powerful and opinionated React UI library designed to streamline the development of modern, scalable, and highly maintainable user interfaces. It champions a component-first architecture and type-safe design, offering a unique approach to building UIs that prioritizes structure, predictability, and developer experience.
At its heart, MeoNode UI allows you to define and compose your UI using declarative function calls and plain JavaScript objects, moving away from traditional JSX-heavy component definitions towards a more structured and functional paradigm.
Why MeoNode
- No build step required — works with plain JavaScript/TypeScript, no JSX transform
- Type-safe by design — full TypeScript inference and autocomplete on every prop
- Structured architecture — function-call composition promotes consistent organization
- Integrated theming — design tokens resolve directly inside style props
- Performance-aware — efficient style generation and memoization built in
Who it's for
- Teams building large-scale React apps where consistency matters
- TypeScript-first developers who want type safety on UI structure, not just data
- Projects that need a built-in theming system without extra libraries
- Anyone who prefers function composition over JSX
Why MeoNode
- No build step required — works with plain JavaScript/TypeScript, no JSX transform
- Type-safe by design — full TypeScript inference and autocomplete on every prop
- Structured architecture — function-call composition promotes consistent organization
- Integrated theming — design tokens resolve directly inside style props
- Performance-aware — efficient style generation and memoization built in
Who it's for
- Teams building large-scale React apps where consistency matters
- TypeScript-first developers who want type safety on UI structure, not just data
- Projects that need a built-in theming system without extra libraries
- Anyone who prefers function composition over JSX
Install the package and you're ready — no JSX transform, no build plugin.
npm install @meonode/ui
A minimal first component:
import { Column, H1, Button } from '@meonode/ui' export default function App() { return Column({ padding: 24, gap: 16, children: [ H1('Hello, MeoNode'), Button('Click me', { padding: '10px 20px', backgroundColor: '#3B82F6', color: 'white', borderRadius: 6, onClick: () => alert('Hi'), }), ], }).render() }
That's it — function calls instead of JSX, props instead of attributes. Children-first elements (H1, Button, Text) take content as the first argument; container elements (Column, Row, Div) take children via the children prop.
For theming, wrap your app in ThemeProvider and reference tokens by string ('var(--meonode-theme-primary)') or via useTheme(). See the Theming guide.
Install with your package manager of choice:
npm install @meonode/ui # or yarn add @meonode/ui # or bun add @meonode/ui
React and React-DOM are peer dependencies. No JSX transform, build plugin, or extra configuration is required.
Framework compatibility
MeoNode UI is a plain React library — it works anywhere React works:
- Next.js — App Router and Pages Router, including Server Components
- Vite — standard React templates work out of the box
- Create React App — drop-in
- Remix, Astro, etc. — any React-compatible host
Wrap any JSX/React component with Node() and use it like any MeoNode element. Props, hooks, refs, and event handlers all work unchanged.
import { Node, Column, H1, Button } from '@meonode/ui' import { TextField } from '@mui/material' import { DatePicker } from 'antd' const Form = Column({ gap: 16, children: [ H1('Sign up'), Node(TextField, { label: 'Name', variant: 'outlined' }), Node(DatePicker, { placeholder: 'Birth date', style: { width: '100%' } }), Button('Submit', { backgroundColor: 'green' }), ], })
Key points
- No conversion — JSX components work as-is
- Props, hooks, refs, events all behave exactly like in JSX
- CSS classes and inline styles are preserved
- Mix freely — alternate JSX and MeoNode at any depth
Reusing the same component
If you'll use the same JSX component repeatedly with default props, reach for createNode() or createChildrenFirstNode() instead of repeating Node() calls. See Node() vs createNode() vs createChildrenFirstNode() for the comparison.
Gradual adoption
Start using MeoNode in new components while keeping existing JSX untouched. The two paradigms compose without friction, so there's no migration step.
These three functions serve different purposes for integrating JSX/React components into MeoNode UI:
Node()
Purpose: Quick one-off usage of JSX/React components
When to use: When you need to render an existing component immediately without creating a reusable wrapper.
import { Column, Node } from '@meonode/ui' import { TextField } from '@mui/material' const MyForm = Column({ children: [Node(TextField, { label: 'Username', variant: 'outlined' }), Node(TextField, { label: 'Password', type: 'password' })], })
createNode()
Purpose: Creates a reusable node factory with props-first pattern
Pattern: Single props argument where children must be passed as a prop
When to use: When you'll use a component multiple times and want to set default props
import { createNode } from '@meonode/ui' import { TextField } from '@mui/material' // Create reusable factory with default props const StyledTextField = createNode(TextField, { variant: 'outlined', fullWidth: true, }) // Usage - single props object const MyForm = Column({ children: [StyledTextField({ label: 'Username' }), StyledTextField({ label: 'Password', type: 'password' })], })
createChildrenFirstNode()
Purpose: Creates a reusable node factory with children-first pattern
Pattern: Two arguments - children first, then props object
When to use: For text-focused components like buttons, headings, or typography where content is the primary focus
import { createChildrenFirstNode } from '@meonode/ui' import { Button as MuiButton } from '@mui/material' // Create reusable factory with default props const PrimaryButton = createChildrenFirstNode(MuiButton, { variant: 'contained', color: 'primary', size: 'large', }) // Usage - children first, props second const MyForm = Column({ children: [PrimaryButton('Login', { onClick: handleLogin }), PrimaryButton('Sign Up', { onClick: handleSignup })], })
Quick Comparison
| Function | Usage Pattern | Reusable | Default Props | Best For |
|---|---|---|---|---|
Node() | Node(Component, props) | ❌ No | ❌ No | One-off usage |
createNode() | Factory({ prop, children }) | ✅ Yes | ✅ Yes | Props-first components (inputs, pickers) |
createChildrenFirstNode() | Factory('content', { prop }) | ✅ Yes | ✅ Yes | Content-first components (buttons, text) |
Key Takeaway
- Node(): Direct usage, no abstraction
- createNode(): Reusable factory, 1 argument (props with children inside)
- createChildrenFirstNode(): Reusable factory, 2 arguments (children, then props)
Next.js enforces strict signatures on Page components — they must match Next's PageProps type. The Component HOC produces a function whose props shape may not align with that constraint, which can surface as a TypeScript error at the page boundary.
✅ Recommended: plain function for pages
Top-level pages should be standard React functions that call .render() on the returned MeoNode:
export default function HomePage() { return Column({ children: [Text('Hello World')], }).render() }
✅ Use Component for sub-components
The HOC is the right tool for reusable sub-components — just don't export the HOC-wrapped function as a Next.js page directly:
const MyButton = Component<{ text: string }>(props => Button(props.text, { backgroundColor: 'blue', color: 'white' }), ) export default function HomePage() { return Column({ children: [Node(MyButton, { text: 'Click me!' })], }).render() }
Why
- Next.js page components must match its
PagePropssignature. - The
ComponentHOC addschildrenand optionalpropsoverrides that Next.js doesn't expect at the page boundary. - Always call
.render()at the page's top level so Next.js receives a real React element.
Hot Module Replacement (HMR) requires proper setup for MeoNode UI sub-components:
For Vite Projects:
- Use .tsx file extensions for all component files
- Wrap components with Node() in the parent component
// AnotherComponent.tsx import { Column, Text } from '@meonode/ui' export function AnotherComponent() { return Column({ children: [Text('Hello from sub-component!')], }).render() // ✅ Call .render() to return React Element } // page.tsx import { Column, Node } from '@meonode/ui' import { AnotherComponent } from './AnotherComponent' export default function Page() { return Column({ children: [ Node(AnotherComponent), // ✅ Function that return React Element is wrapped with Node() ], }).render() }
For Next.js Projects:
Next.js is more intelligent with HMR detection:
- File extensions can be .ts or .tsx
- Node() wrapping is still recommended but not always required
- Next.js automatically handles most HMR scenarios
Why This Works:
- Node() wrapper ensures React can properly track component boundaries
- .tsx extensions help Vite's HMR system recognize React components
- Proper module boundaries allow hot reloading to work correctly
Without these patterns, changes to sub-components may require full page refreshes instead of hot updates.
Node functions come in two shapes — matching the two factory functions, createNode and createChildrenFirstNode.
Children-first (H1, Button, Text, Span, …): first argument is the content, second is props.
H1('Welcome!', { fontSize: '2rem', color: '#333' }) Button('Click Me', { backgroundColor: 'blue', onClick: handleClick })
Props-first (Column, Row, Div, …): single props object, children passed via the children key.
Column({ padding: 20, children: [H1('Title'), Text('Content')], })
Use createChildrenFirstNode to build your own children-first wrappers and createNode for props-first ones.
Styling is done through props using a CSS-in-JS approach:
- Raw numbers are interpreted as pixels (
20becomes20px) - CSS properties are passed directly as props
- Theme tokens like
'var(--meonode-theme-primary)'resolve via the activeThemeProvider - Type safety — autocomplete and error checking on every prop
Button('Submit', { padding: '10px 20px', backgroundColor: 'var(--meonode-theme-primary)', color: 'var(--meonode-theme-primary-content)', borderRadius: 5, })
For dynamic styles, use the css prop with a theme function:
Div({ css: { color: theme => var(--meonode-theme-system-primary-content), '&:hover': { backgroundColor: 'var(--meonode-theme-primary)' }, }, })
How do I pass props like height to custom components?
When creating custom components in MeoNode UI, you need to understand how props are processed:
The Rule
- CSS-like prop names (height, width, padding, margin, etc.) used for non-styling purposes → must go in the
propsobject - Non-CSS prop names (data, title, onClick, etc.) → can be passed at root level
Why?
MeoNode UI processes CSS-like prop names as styles. If you have a custom component that needs height for logic (not
styling), passing it directly will be intercepted by the style processor.
Example
import { Component, Div } from '@meonode/ui' type ChartProps = { height: number // For chart logic, not styling data: number[] // Not a CSS prop } const Chart = Component<ChartProps>(({ height, data }) => Div({ children: `Chart with ${data.length} points at ${height}px height`, }), ) // Usage with Node Node(Chart, { backgroundColor: 'white', // Styling prop padding: '20px', // Styling prop data: [1, 2, 3, 4], // Root level - not CSS-like props: { // CSS-like custom props here height: 500, // Goes to component logic, not styling }, })
When to use props
Only use the props object when your custom component prop has the same name as a CSS property but serves a different
purpose. Otherwise, pass props directly at root level.
Use the Component factory to create reusable React components:
interface PrimaryButtonProps { onClick: () => void; } const PrimaryButton = Component<PrimaryButtonProps>((props) => { return Button(props.children, { padding: '12px 24px', backgroundColor: 'darkgreen', color: 'white', borderRadius: 8, ...props // Spread other props }); });
Always spread ...props to ensure additional properties are correctly applied.
The "Rendered fewer hooks than expected" (or similar) error comes from violating the Rules of Hooks. React requires that hooks like useState and useEffect are called in the same order on every render. If a component that uses hooks is only sometimes called (e.g. inside a conditional), the order of hook calls changes, and React throws this error.
Example: DetailComponent
In this guide we use DetailComponent as an example.
It returns a Node instance and uses hooks internally:
const DetailComponent = ({ info }: { info: string }) => { useEffect(() => { console.log('Mounted:', info) return () => console.log('Unmounted:', info) }, [info]) return Row({ alignItems: 'center', gap: 10, padding: 4, border: '2px solid var(--meonode-theme-accent)', borderRadius: 6, backgroundColor: 'var(--meonode-theme-warning)', color: 'var(--meonode-theme-danger)', children: [P(info), TextField({ sx: { background: 'var(--meonode-theme-primary)' } })], }).render() }
Because it calls useEffect, this component must always be rendered consistently.
Here are the safe and unsafe ways to do it:
✅ Correct Pattern: Wrap with Node()
The most idiomatic MeoNode pattern is to wrap hook-using Node components with the Node() HOC.
This makes them safe to conditionally render, because React now sees a proper component boundary.
isShowMore && Node(DetailComponent, { info: 'Safe: Node() wrapper ensures hook order is preserved.', })
✅ Correct Pattern: Inline Function Wrapper
Alternatively, you can wrap the call in an arrow function.
This delays execution until render time and avoids direct conditional hook calls.
isShowMore && (() => DetailComponent({ info: 'Safe: inline function wrapper.', }))
This also works if the component explicitly calls .render() internally (e.g. ReturnRenderedDetailComponent).
✅ Correct Pattern: Component HOC
For components wrapped in the Component HOC, React handles hooks as usual.
This is another safe way to render them conditionally.
const WrappedDetailComponent = Component(DetailComponent) isShowMore && WrappedDetailComponent({ info: 'Safe: Component HOC ensures hook order + theme context.', })
❌ Incorrect Pattern: Direct Conditional Call
Directly calling a hook-using component inside a conditional breaks the Rules of Hooks.
React sees a different hook order on renders and throws.
// ❌ Will throw "Rendered fewer hooks than expected" isShowMore && DetailComponent({ info: 'This violates hook rules.', })
Summary
Node()is the recommended MeoNode-safe wrapper for conditional rendering.- Inline functions and the
ComponentHOC are also safe, but use them when you need ReactNode compatibility or full theme propagation. - Never call a hook-using component directly inside a conditional — that’s what triggers the error.
Standard React event handlers work exactly as expected. Attach them as properties in the props object:
const handleClick = () => { console.log('Button clicked!') } const MyButton = Button('Click Me', { onClick: handleClick, onMouseEnter: () => console.log('Hover!'), backgroundColor: 'purple', })
Conditional Rendering:
const isLoggedIn = true const AuthStatus = Column({ children: isLoggedIn ? Text('Welcome back!', { color: 'green' }) : Button('Login', { backgroundColor: 'blue' }), })
List Rendering:
const items = [{ id: 1, text: 'Item 1' }] const ItemList = Column({ children: items.map(item => Text(item.text, { key: item.id, padding: 8, backgroundColor: '#e9e9e9', }), ), })
MeoNode UI provides several layout components:
- Column: Vertical layout (flexDirection: column)
- Row: Horizontal layout (flexDirection: row)
- Div: Generic container
- Section, Header, Footer, Main, Nav, Form: Semantic HTML containers
All layout components support flexbox properties and theme-aware styling.
Most standard HTML tags are also prebuilt as components, so you can use them directly in your UI code.
MeoNode UI integrates a robust theming system directly into styling:
- Define design tokens once and use them across components
- Theme-aware properties like
var(--meonode-theme-colors-primary),var(--meonode-theme-spacing-md) - Global style changes through centralized theme configuration
- Consistent visual language across your entire application
Augment the MeoTheme interface to get autocomplete and compile-time validation on every 'theme.<path>' string:
declare module '@meonode/ui' { interface MeoTheme { mode: 'light' | 'dark' system: { colors: { primary: string; bg: string } spacing: { sm: number; md: number } } } }
See the Theming guide for the full setup.