Skip to content

Rendering

flexium/dom provides the renderer + a few declarative primitives for async, error, and portal patterns.

render()

Mount a component tree into a DOM container:

tsx
import { render } from 'flexium/dom'

render(<App />, document.getElementById('app')!)

render returns a dispose function — call it to unmount:

tsx
const dispose = render(<App />, container)
// later...
dispose()

Suspense

Pause render at a boundary while async work completes:

tsx
import { Suspense } from 'flexium/dom'

function Page() {
  return (
    <Suspense fallback={<p>Loading user…</p>}>
      <UserProfile id={1} />
    </Suspense>
  )
}

function UserProfile({ id }) {
  // Returns a Promise — Suspense pauses until resolved
  const [user] = use(async () => fetch(`/api/users/${id}`).then(r => r.json()))
  return <h1>{user.name}</h1>
}

While the promise is pending, the fallback is rendered. When the promise resolves, the component re-renders with the value.

Nest Suspense boundaries to control loading granularity:

tsx
<Suspense fallback={<Spinner />}>
  <Header />
  <Suspense fallback={<RowSkeleton />}>
    <DataTable />
  </Suspense>
</Suspense>

If Header is sync, it shows immediately. The DataTable shows its own skeleton until its data resolves.

ErrorBoundary

Catch rendering errors and show a fallback:

tsx
import { ErrorBoundary } from 'flexium/dom'

<ErrorBoundary fallback={(err) => <p style="color:red">Failed: {err.message}</p>}>
  <RiskyComponent />
</ErrorBoundary>

With a render-prop fallback you can offer recovery:

tsx
<ErrorBoundary fallback={(err, reset) => (
  <div>
    <p>{err.message}</p>
    <button onclick={reset}>Try again</button>
  </div>
)}>
  <RiskyComponent />
</ErrorBoundary>

Calling reset re-mounts the children, giving the component a fresh attempt.

Portal

Render content into a different DOM subtree (modals, tooltips, toasts):

tsx
import { Portal } from 'flexium/dom'

function Modal({ children }) {
  return (
    <Portal target={document.body}>
      <div class="modal-overlay">
        <div class="modal-content">{children}</div>
      </div>
    </Portal>
  )
}

The child JSX renders into document.body (or any target element) but stays logically inside its parent — context, signals, and event bubbling work normally.

lazy()

Code-split a component:

tsx
import { lazy } from 'flexium/dom'

const HeavyChart = lazy(() => import('./HeavyChart'))

<Suspense fallback={<Spinner />}>
  <HeavyChart />
</Suspense>

lazy(fn) returns a component that triggers fn() on first render. The dynamic import becomes a separate chunk; Suspense handles the loading.

Hydration (SSR)

For server-rendered HTML, hydrate() attaches event handlers to the existing DOM:

tsx
import { hydrate } from 'flexium/dom'

hydrate(<App />, document.getElementById('app')!)

The server-rendered markup is preserved; signals attach to the existing nodes and updates flow normally from that point. See Server-Side Rendering.

render() vs hydrate()

render()hydrate()
Use casePure client app (CSR)Existing HTML from server (SSR)
BehaviorDiscards container, mounts freshAttaches to existing DOM
FCPAfter JS download + parseBefore JS — HTML loads first

Next

RoutingRoutes, Link, useRouter.

Released under the MIT License.