Quick Start

The minimum to get a working reactolith app is:

<!-- index.html (rendered by your backend) --> <div id="reactolith-app"> <h1>Hello world</h1> <my-button>Click me</my-button> </div> import { App } from "reactolith"; import { MyButton } from "./components/my-button"; function resolveComponent({ is }: { is: string }) { // Match each tag name to a real React component. if (is === "my-button") return MyButton; throw new Error(`Unknown component: ${is}`); } new App(resolveComponent);

Any tag name with a hyphen (e.g. <my-button>) is treated as a custom React component and resolved through the function you pass to new App(...). Everything else (<h1>, <div>, <svg>, …) is rendered as a native HTML element.

Lazy-loading a folder of components

For larger apps — including drop-in shadcn directories — createLoader resolves tag names to a folder of files without any per-component setup:

import { App, createLoader } from "reactolith"; const component = createLoader({ modules: import.meta.glob("./components/ui/*.tsx"), prefix: "ui-", // strip "ui-" before resolving }); new App(component);

<ui-button> → ./components/ui/button.tsx (named export Button or default). Names with multiple kebab segments fall back to a parent file — <ui-accordion-item> finds accordion.tsx and picks its AccordionItem export.

To layer custom components on top of shadcn (or any other base set), pass multiple module maps; earlier maps take priority:

const component = createLoader({ modules: [ import.meta.glob("./components/custom/*.tsx"), // wins on conflict import.meta.glob("./components/ui/*.tsx"), ], prefix: "ui-", });

createLoader options

Option Type Description
modulesModuleMap or ModuleMap[]Output of import.meta.glob. Earlier maps take priority.
prefixstringPrefix to strip from is before resolving (e.g. "ui-").
fallbackReactNodeRendered while a component is being lazy-loaded. Default null.
onMissing(name, is) => ComponentType | nullCalled when no module resolves; return a placeholder to render in its place.

Custom Root Component & Selector

import { App, createLoader } from "reactolith"; import { AppProvider } from "./providers/app-provider"; const component = createLoader({ modules: import.meta.glob("./components/**/*.tsx"), }); new App(component, AppProvider, "#app"); import React, { PropsWithChildren } from "react"; import { App, AppProvider as DefaultAppProvider } from "reactolith"; import { ThemeProvider } from "./theme-provider"; export const AppProvider: React.FC<PropsWithChildren<{ app: App }>> = ({ app, children, }) => ( <DefaultAppProvider app={app}> <ThemeProvider>{children}</ThemeProvider> </DefaultAppProvider> );

Continue with How It Works to learn how reactolith hydrates and updates your app.