Skip to content
forma.
v3.2.0
☰
Browse documentation
Start here
Application guides
Architecture
Build interfaces
Native rendering
Tooling
Security and operations
API reference

Docs/Application guides

FORMA 3.2

TSX, h() and the build

Understand JSX syntax, browser JavaScript, native FMIR and the checks between them.

View source ↗Markdown ↓Site guide · 3.2.0
On this page

TSX is TypeScript with JSX syntax. It lets you write markup such as <h1>Hello</h1> inside TypeScript. Forma's build turns that markup into calls to Forma's h() function. You can also write those calls yourself in a .ts file. Both styles use the same rendering model.

You do not need to run a separate JSX → TypeScript → JavaScript command chain. The browser build handles types and JSX together; a native build also compiles the supported page structure into FMIR for Rust.

Choose TSX or h()#

These components describe the same heading. With TSX, save the source as .tsx:

tsx
import { h } from "@getforma/core";

export function Heading(props: { title: string }) {
  return <h1 class="page-title">{props.title}</h1>;
}

With explicit h() calls, it can be a .ts file:

ts
import { h } from "@getforma/core";

export function Heading(props: { title: string }) {
  return h("h1", { class: "page-title" }, props.title);
}

Choose the form you find easier to read. JSX does not require React: Forma's configured JSX factory is h, with Fragment for fragments. The TypeScript jsx: "react" setting in the starter names the classic JSX transform; its jsxFactory: "h" setting selects Forma's function.

Reactivity is the same in either syntax. Components run once, so a changing value needs an accessor, such as {() => count()} in TSX or () => count() as an h() child. An event handler such as onClick remains a function that handles the event.

Follow the browser build#

@getforma/build uses esbuild to remove TypeScript types, transform JSX and bundle the browser code. Those operations are integrated into the build; there is no intermediate .ts file you need to create or manage.

text
Browser UI:
  .tsx or .ts → esbuild → browser JavaScript using Forma h()

Native page, when SSR is enabled:
  .tsx → internal JSX-to-h() transform ─┐
  .ts with h() ────────────────────────┴→ supported tree → FMIR → Rust → HTML
  original TypeScript declarations ─────→ declared props schema

The browser receives JavaScript, so it does not parse JSX or TypeScript at runtime. A browser-only application using @getforma/core needs no FMIR output or Rust server. You still use a bundler for TypeScript source, including plain h() calls in a .ts file.

The published browser build options set jsx: "transform", jsxFactory: "h" and jsxFragment: "Fragment".

Follow the native build#

For native SSR, the compiler internally lowers .tsx/.jsx markup to h() calls, then analyzes the supported component tree and emits FMIR. Rust loads that representation and renders HTML with the request's typed props. The Rust renderer does not run JSX, React or a JavaScript engine.

The compiler also reads the original TypeScript declarations to build the props schema before JSX lowering removes their type annotations. Types are removed from browser JavaScript, but the declared page contract is retained in the native output. See the declared props guide.

Native rendering supports a defined subset of expressions. Valid TypeScript and valid JSX are not enough to guarantee native compilation. Compute business logic or unsupported derived values in Rust, then pass explicit presentation props to the page. Writing h() by hand does not expand that subset. See the native subset for supported forms and diagnostics.

The implementation is visible in the current module loader and declared schema reader.

Use the same build commands#

TSX does not add commands to the installation steps. In the published minimal starter, run npm run build:ssr from admin/ to produce FMIR and its paired browser assets, then run Cargo from the application root. The starter's ordinary npm run build selects browser rendering instead.

Choosing TSX adds syntax transformation work during the build. This guide does not establish a measured build-time difference between TSX and handwritten h(), and does not claim that the transform is free. Both TypeScript forms still need the build; neither adds a JSX parser to the browser or Rust renderer. Keep native modules, browser assets and the manifest from the same build.

Check types separately#

Transpilation turns source into runnable output. Type checking checks whether your TypeScript uses types consistently. Native compilation checks whether the page can be represented in FMIR. These are distinct checks.

For a project with TypeScript installed and an appropriate tsconfig.json, run the type checker from that frontend project's directory:

sh
npx tsc --noEmit

Add this to your own validation scripts if you want it as a required check. The published minimal starter's build commands do not implicitly run this type-checking step or provide a typecheck script. Passing it also does not replace the native build's subset checks or Rust's props binding.

Keep routing and activation separate#

The application server owns HTTP routing. In the minimal starter, Axum matches a request to the application's handler; the handler supplies typed props and returns Forma's complete page response. The build's route configuration pairs that route with its page and assets. Follow application configuration for that wiring.

On a rendered page, island activation attaches interaction to declared islands and adopts their existing DOM. It does not implement browser navigation between pages. Ordinary links can request another document from the server, including when JavaScript is disabled.

The optional @getforma/router package adds navigation between compatible pages. This website uses its generated-HTML adapter for portal pages and its native adapter for the router lab. Ordinary native pages retain document navigation. Use one coordinator per document and preserve each island's owner. The routing guide explains the three adapters and their boundaries.

Find your next answer.

Search Forma documentation, guides and examples.