Skip to content

A flexible, type-safe system for building React UIs with plain functions, eliminating the need for JSX

License

Notifications You must be signed in to change notification settings

renueljs/renuel

Repository files navigation

Renuel Renuel

tag v0.0.21-next.0 npm version npm v0.0.21-next.0 version bundle size license


Renuel provides a flexible, type-safe system for building React UIs with plain functions—harnessing the full expressive power of JavaScript while avoiding the context-switching syntax of JSX.

div({ className: "greeting" }, "Hello ", em$("world"))

Features

  • Just functions: Build React components without JSX.
  • Flexible and concise: Factory variants allow props and children to be passed with minimal syntax, reducing boilerplate without compromising type safety.
  • Precision composition: A polymorphic component approach that delivers convenience and correctness
  • Type safety by default: Excess props are disallowed, and prop conflicts must be resolved explicitly.
  • Expressive JavaScript: Take full advantage of JavaScript and TypeScript features with no extra syntax or context-switching.

Installation

npm install renuel # or yarn, pnpm, etc.

Quick start

Here's an example of using Renuel to create a simple counter app:

import { useReducer } from "react";
import { createRoot } from "react-dom/client";
import { button, component, strong$ } from "renuel";

const { App$ } = component("App", () => {
  const [count, onClick] = useReducer((x) => x + 1, 0);
  return button({ onClick }, "Count: ", strong$(count));
});

const rootEl = document.getElementById("root");
if (rootEl) {
  const root = createRoot(rootEl);
  root.render(App$());
}

Edit in CodeSandbox

Custom components

Basic

Here's a simple Button component with a variant prop and children as the label:

import { component, button$ } from "renuel";

const { Button, Button$ } = component(
  "Button",
  ({
    variant = "secondary",
    children,
  }: {
    variant?: "primary" | "secondary";
    children?: React.ReactNode;
  }) =>
    button$(
      {
        style:
          variant === "primary"
            ? {
                background: "blue",
                color: "white",
                padding: "0.5rem 1rem",
                borderRadius: 4,
              }
            : {
                background: "lightgray",
                padding: "0.5rem 1rem",
                borderRadius: 4,
              }
      },
      children
    )
);

// Usage — props + children
Button({ variant: "primary" }, "Click me")

// Usage — skip props (defaults to "secondary" variant)
Button$("Cancel")

Polymorphic

Polymorphic components let you reuse styling while rendering different underlying elements. The canonical example is a Button component that can be rendered as an HTML button element or as an a element, but looks the same either way.

Renuel makes this type of composition explicit through a render prop, ensuring both flexibility and type safety.

To make the Button polymorphic, you can change children to a render prop (aka Function as Child Component):

import { component, button$, _a, _button$ } from "renuel";

const { Button, Button$ } = component(
  "Button",
  ({
    variant = "secondary",
    children
  }: {
    variant?: "primary" | "secondary";
    children: (props: { style: React.CSSProperties }) => React.ReactNode;
  }) =>
    children({
      style:
        variant === "primary"
          ? {
              background: "blue",
              color: "white",
              padding: "0.5rem 1rem",
              borderRadius: 4,
            }
          : {
              background: "lightgray",
              padding: "0.5rem 1rem",
              borderRadius: 4,
            }
    })
);

// Usage — render as a link
Button({ variant: "primary" }, _a({ href: "/docs" }, "Get started"));

// Usage — render as a plain button
Button$(_button$("Default button"));

Factories

Each tag (or custom component) comes with four factory variants:

  1. tag (Component): standard factory; accepts props + children.
  2. tag$ (Component$): skip-props factory; accepts children only.
  3. _tag (_Component): partial factory; returns a new factory after fixing some props.
  4. _tag$ (_Component$): partial skip-props factory; like _tag, but starts with children.

Tip

A way to remember the naming convention is:

  • $ means "skip props", i.e. first argument is a child
  • _ means "partial", i.e. returns another factory

Example with div:

div({ className: "foo" }, "Hello")                 // standard
div$("Hello")                                      // skip-props
_div({ id: "foo" }, "Hello")({ className: "foo" }) // partial
_div$("Hello")({ className: "foo" })               // partial skip-props

Note

In the example above, invoking the curried factories with additional props is for demonstration purposes only. In practice, you’d typically pass the curried factory as a child to a polymorphic component, which is then responsible for supplying the remaining props.

This pattern applies to both native tags and custom components, making composition predictable and type-safe with minimal syntax.

Versus JSX

If you already use JSX, Renuel will feel familiar — but with less syntax overhead and stronger type guarantees. Here are a few common patterns compared directly:

Mapping over data

// JSX
<ul>{items.map(i => (<li key={i.id}>{i.name}</li>))}</ul>

// Renuel
ul$(items.map(i => li({ key: i.id }, i.name)))

Conditional rendering

// JSX
<div>{isLoggedIn ? <p>Welcome back!</p> : <p>Please log in</p>}</div>

// Renuel
div$(isLoggedIn ? p$("Welcome back!") : p$("Please log in"))

Function as Child Component

// JSX
<Button>{({ style }) => <a href="/docs" style={style}>Docs</a>}</Button>

// Renuel
Button(_a({ href: "/docs" }, "Docs"))

Object props

// JSX
<div style={{ background: "blue", color: "white" }}>Hello world</div>

// Renuel
div({ style: { background: "blue", color: "white" } }, "Hello world")

Special characters

// JSX
<footer>&copy; 2025 MyCompany. All rights reserved.</footer>

// Renuel
footer$("© 2025 MyCompany. All rights reserved.")

About

A flexible, type-safe system for building React UIs with plain functions, eliminating the need for JSX

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •