npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@meonode/ui

v3.1.3

Published

A structured approach to component composition, direct CSS-first prop styling, built-in theming, smart prop handling (including raw property pass-through), and dynamic children.

Downloads

11,023

Readme

@meonode/ui

NPM version License: MIT Bundle Size

Type-safe React components without JSX

A production-ready component library that replaces JSX with function-based composition, featuring direct CSS prop styling, context-based theming, automatic memoization, and full React Server Components support—powered by @emotion/react.

Core Concept

MeoNode UI eliminates JSX while maintaining full React compatibility through functional composition. Style components with CSS properties as props, leverage automatic theme resolution via React Context, and benefit from intelligent caching without manual optimization.

JSX Pattern:

<div style={{ padding: '20px', borderRadius: '12px' }}>
  <h2 style={{ fontSize: '1.5rem', marginBottom: '8px' }}>{title}</h2>
  {children}
</div>

MeoNode Pattern:

Div({
  padding: '20px',
  borderRadius: '12px',
  backgroundColor: 'theme.primary',
  children: [H2(title, { fontSize: '1.5rem', marginBottom: '8px' }), ...children],
})

Features

Function-Based Composition

Plain JavaScript functions replace JSX—no build transforms, no syntax extensions. Compose UIs as first-class function trees with full TypeScript inference.

Nothing here requires a build step. An optional build-time plugin exists purely as an optimization—see Optional Build-Time Compiler.

Nested Array Children

A children array can hold arrays, and the nesting carries meaning: a nested array is a list, its siblings are not.

Div({
  children: [
    H2('Members'), // an authored sibling
    members.map(member => Row({ key: member.id, children: member.name })), // a list — React checks these for keys
  ],
})

React skips the missing-key check on children it receives as separate arguments and runs it on the members of an array it receives as one. Nesting keeps those two apart: the heading is exempt, the rows are checked, and a row without a key is reported. Spreading the rows instead — ...members.map(...) — makes each one look like an authored sibling, so the check never runs and the missing key goes unreported while the rows still remount on every render. Spreading was the only option before, because a nested array used to reach React unrendered and throw Objects are not valid as a React child.

Nesting is unlimited, and an array that contains itself is dropped rather than left to overflow the stack.

Keyed Lists with For()

React matches children by position unless they carry a key, so deleting or reordering an unkeyed list hands each surviving row the previous row's fiber — its state, its focus, its half-typed input. children: items.map(...) inherits that rule in full, and position is the one thing that cannot tell you which row is which after a reorder.

The data can. For() takes the data rather than the finished nodes and keys each row from it.

Div({ children: For(todos, todo => TodoRow({ todo })) })

A row is identified by its object reference, which survives the row's contents changing: renaming a row keeps its state, where hashing the contents would throw it away on every keystroke. Pass an identity accessor whenever the items are rebuilt between renders — a fresh fetch, a .map() that spreads — since references cannot see through that.

Div({ children: For(todos, todo => TodoRow({ todo }), todo => todo.id) })

Development says so when it detects that case, rather than letting the list remount silently.

Direct CSS-in-Props

Pass CSS properties directly to components. No separate styled-components declarations, no className juggling. All valid CSS properties work as props with full type safety.

Button('Submit', {
  padding: '12px 24px',
  backgroundColor: 'theme.primary',
  borderRadius: 8,
  transition: 'all 0.2s ease',
  css: {
    ':hover': { transform: 'scale(1.05)' },
  },
})

Advanced Portal System

Manage complex UI layers like modals, drawers, and overlays with a powerful stack-based portal system. Supports nested portals and state synchronization.

Key Features:

  • Global Layout Integration: Define the provider and host at the root for app-wide access.
  • Auto-Sync: Keep portal content in sync with parent state automatically.
  • Nested Portals: Open portals from within other portals easily.
  • Stack-based management: Automatically manages multiple overlapping layers.
// 1. Setup at Layout Level
const RootLayout = ({ children }: { children: ReactNode }) => 
  PortalProvider({
    children: [
      ...children,
      PortalHost() // Portals render here, on top of content
    ]
  })

// 2. Define data interface and portal content
interface MyData {
  count: number;
  setCount: React.Dispatch<React.SetStateAction<number>>;
}

const MyPortalContent = ({ data, close }: PortalLayerProps<MyData>) => 
  Div({
    children: [
      Text(`Count: ${data?.count}`),
      Button('Increment', { onClick: () => data?.setCount(c => c + 1) }),
      Button('Close', { onClick: close })
    ]
  })

// 3. Use anywhere in the tree
const MyComponent = () => {
  const [count, setCount] = useState(0)
  // Auto-sync: portal updates whenever count changes
  const portal = usePortal<MyData>({ count, setCount })

  return Div({
    children: [
      Button('Open Modal', { 
        onClick: () => portal.open(MyPortalContent) 
      })
    ]
  })
}

Context-Based Theming

Theme values resolve automatically through React Context. Reference semantic tokens anywhere without prop drilling. Supports nested themes and dynamic switching.

ThemeProvider({
  theme: {
    mode: 'dark',
    system: {
      primary: { default: '#FF6B6B', content: '#4A0000' },
      spacing: { sm: 8, md: 16, lg: 24 },
    },
  },
  children: [
    /* your app */
  ],
})

Theme Modes Without a Flash

ThemeProvider takes a whole theme object, so a server-rendered page has to know which one to send — from a cookie, or by guessing. A cookie makes the response depend on who asked for it, so no cache can share it; a guess is a flash of the wrong theme for everyone it got wrong.

ThemeModesProvider splits it. Colours are var() references, so one token object serves every mode and the server sends the same bytes to every reader. Which palette applies is decided by data-theme on <html>, written by themeScript before the first paint — there is no flash because there was never a wrong first paint to correct.

import { Body, Head, Html, themeScript, ThemeModesProvider, type ThemeScriptConfig } from '@meonode/ui'

// Mode names are yours -- `light` and `dark` are not required.
const themeConfig = {
  modes: ['morning', 'night'],
  defaultMode: 'morning',       // where everything falls back to when nothing else answers
  defaultPreference: 'system',  // where a reader who has chosen nothing starts
  system: { light: 'morning', dark: 'night' },
} satisfies ThemeScriptConfig

// Place the script first in <head>, ahead of any stylesheet.
Html({
  lang: 'en',
  suppressHydrationWarning: true,
  children: [
    Head({ key: 'theme', children: themeScript(themeConfig) }),
    Body({ key: 'body', children: ThemeModesProvider({ ...themeConfig, tokens, children }) }),
  ],
})

Spreading one literal into both halves is the point: a mode added in one place reaches the script and the provider together. Augment MeoTheme with mode: 'morning' | 'night' and setMode autocompletes your names and rejects a typo.

If your Content-Security-Policy pins hashes, import THEME_SCRIPT_CSP_HASH rather than copying the value — a copied literal goes stale the first time the body changes, and it fails silently.

Do not render markup from mode. The server cannot know it, so a component that branches on it disagrees with the server during hydration for every reader not on the default, and React answers that by re-rendering the whole page on the client — which clears the attributes the script wrote. Key the CSS off [data-theme='…'], or gate on hydrated from useTheme().

Surgical Memoization

Memoize at node-level granularity—not just entire components. A node given a dependency array renders inside a React fiber of its own, and the array is handed straight to useMemo: it means exactly what it means in React, so an empty array freezes the subtree and a node that should follow a value has to name it. Nodes without one are not memoized and rebuild with their parent.

Node-Level Memoization:

const UserCard = ({ user }) =>
  Div(
    {
      padding: 16,
      children: [
        H2(user.name),
        Text(user.bio),
      ],
    },
    [user.id],
  ).render() // The whole subtree rebuilds only when user.id changes

Component-Level Memoization:

Node(ExpensiveComponent, { data }, [data.id]).render()

React Server Components Compatible

Full RSC support with proper client/server component boundaries. Use in Next.js App Router, Remix, or any RSC-enabled environment without configuration.

Emotion-Powered Styling

Built on @emotion/react for:

  • Automatic critical CSS extraction
  • Vendor prefixing
  • Dead code elimination
  • Server-side rendering support

Smart Prop Differentiation

Automatically separates style props from DOM attributes. Pass onClick, aria-*, data-* alongside CSS props—MeoNode routes them correctly.

Button('Click Me', {
  padding: '12px', // → style
  color: 'theme.primary', // → style
  onClick: handleClick, // → DOM attribute
  'aria-label': 'Submit', // → DOM attribute
  disabled: isLoading, // → DOM attribute
})

Debug Mode

There are two levels of diagnostics, and only the second needs a flag.

On in development, nothing to turn on. Each of these is your own code, and each is actionable:

  • a theme.* token the active theme does not define — the common cause of a style that silently fails to apply, since the browser drops a declaration referencing a variable no :root rule declares
  • the two other shapes that emit invalid CSS: a number written as a string (padding: '12', which Emotion will not add px to) and a unitless length in a media query
  • a generated list whose children have no key. React reports the missing key itself; this names the call site it came from, which React's own message cannot (needs @meonode/compiler with callSiteLocations)
  • an error thrown inside a function-as-a-child, which is otherwise swallowed and leaves the element missing with nothing printed
  • For() given a list where every item is a fresh object, so reference identity cannot match renders and every row remounts
  • a children array that contains itself

Behind setDebugMode, in any environment. These are MeoNode's own introspection recovering from itself — the render is correct afterwards and there is nothing for you to fix, so they are off unless you go looking:

  • @meonode/compiler marker buckets colliding with reserved keys
  • the node.prototype.render probe failing
  • the key-name derivation falling back
import { setDebugMode } from '@meonode/ui'

setDebugMode(true)

Set it at module scope in your entry file, not in a useEffect — theme diagnostics fire as nodes are constructed and an effect runs too late to catch the first render's worth.

In a production build the flag also turns the first group back on. That group is quiet there because NODE_ENV says production — read at runtime on Node, or substituted by a bundler, which folds that half of the gate to false. The messages themselves ship either way: the flag is a runtime binding, not a build-time constant, so bundlers cannot strip the text, and a minified bundle built with process.env.NODE_ENV substituted still contains every one of them. That is deliberate — the bugs worth this much logging are usually the ones that only reproduce in production.

📖 Full list of what it logs

Performance Benchmarks

MeoNode is built for high-performance applications, with an iterative rendering engine and memoization delegated to React's own fibers.

How to read these numbers. They come from bun run bench, which runs in jsdom on Node, not a browser. They are microbenchmarks against production React, and absolute timings move with hardware, Node version and background load — 10–20% run-to-run spread on one machine is normal. Treat them as orders of magnitude and as regression guards, not precise figures. Reproduced on an Apple M4 Pro / 24 GB / Node 26 / React 19.2, taking medians of alternating rounds.

Production React matters: these previously ran under vitest, which forces NODE_ENV=test and so measured React's development build. Its validation overhead is large enough to swamp the library — it reported the compiler's client-side gain as 1.03x where the real figure is ~1.4x.

Layout Rendering

| Metric | Value | Description | |:------------------------|:---------|:----------------------------------------------------| | Single-Page Layout | ~2 ms | Full page layout with header, sections and footer | | 10,000 Flat Nodes | ~65 ms | Rendering 10k nodes at the same level | | 10,000 Nested Nodes | ~1200 ms | Deeply nested structure (single parent-child chain) |

The nesting case doubles as a stack-safety check: render() walks iteratively with an explicit work stack precisely so 10,000 levels do not overflow the call stack.

Memory Management

Heap growth measured after a forced collection, against the ceiling each check asserts:

| Check | Growth | Ceiling | |:-----------------------------------|:------------|:---------| | 50 heavy state updates | ~2.5 MB | 150 MB | | 200 mounts/unmounts over 20 cycles | ~14 MB | 20 MB | | 10 navigation cycles | ~1 MB | 20 MB |

bench/memory.mjs exits non-zero if any ceiling is exceeded, so it works as a leak gate you run deliberately.

React Comparison (10k flat nodes, one style property)

| Implementation | Time | |:------------------------------|:---------| | React.createElement | ~44 ms | | React.createElement+Props | ~85 ms | | MeoNode | ~41 ms | | MeoNode+Props | ~93 ms |

Read the bold rows. Bare React.createElement does no prop work at all, while MeoNode classifies every prop into CSS vs DOM attributes. Against the like-for-like pair MeoNode is ~1.08x slower — and roughly at parity (~1.01x) on the 5,000-node nested case.

The style object is kept to a single property on purpose. React writes inline style properties onto the DOM node one at a time while MeoNode emits a single Emotion class, so a larger style object inflates React's side — with three properties this table would show MeoNode as 3x faster, which would be a measurement artifact rather than a result.

Quick Start

import { ThemeProvider, Center, Column, H1, Button, Text } from '@meonode/ui'

const theme = {
  mode: 'light',
  system: {
    primary: { default: '#FF6B6B', content: '#FFFFFF' },
    base: { default: '#F8F8F8', content: '#333333' },
  },
}

const App = () =>
  ThemeProvider({
    theme,
    children: [
      Center({
        padding: 40,
        backgroundColor: 'theme.base',
        children: Column({
          gap: 24,
          children: [
            H1('MeoNode UI', {
              fontSize: '3rem',
              color: 'theme.primary',
            }),
            Text('Type-safe React without JSX', {
              fontSize: '1.2rem',
              color: 'theme.base.content',
            }),
            Button('Get Started', {
              backgroundColor: 'theme.primary',
              color: 'theme.primary.content',
              padding: '12px 24px',
              borderRadius: 8,
              cursor: 'pointer',
              onClick: () => console.log('Started!'),
            }),
          ],
        }),
      }),
    ],
  }).render()

Architecture

Component Factory System Node factory + Component wrapper + semantic elements (Div, Button, etc.) enable both rapid prototyping and sophisticated component architectures.

Theme Resolution Engine Context-based theme propagation with automatic value resolution. Nested theme objects inherit and override parent values without explicit passing.

CSS Engine @emotion/react provides CSS-in-JS with automatic optimization, critical CSS extraction for SSR, and zero-runtime overhead in production builds.

Why MeoNode UI?

For Teams Building Design Systems:

  • Context-based theme propagation
  • Semantic token system ensures visual consistency
  • Component composition patterns scale naturally
  • Full TypeScript support catches design token errors

For Performance-Critical Applications:

  • Emotion's CSS optimization and caching
  • Surgical memoization at node granularity
  • SSR-ready with critical CSS extraction
  • RSC compatibility for modern React architectures

For Developer Productivity:

  • No JSX compilation overhead
  • Direct CSS-in-props reduces context switching
  • Intelligent prop routing (style vs DOM attributes)
  • Full autocomplete for all CSS properties
  • Composable function trees with first-class JavaScript

Documentation

📚 Complete Documentation & Examples

🎮 Interactive Playground

Contributing

We welcome contributions! Please see our contributing guidelines.

License

MIT © Ukasyah Rahmatullah Zada


📖 Full Documentation • 🐛 Issues • * 💬 Discussions*