# TSX, h() and the build

**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 [3.0 browser build options](https://github.com/getforma-dev/forma/blob/v3.0.0/packages/build/src/build.ts#L564-L566)
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](/docs/3.0/declared-props/).

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](/docs/3.0/native-subset/) for supported forms and diagnostics.

The implementation is visible in the 3.0
[module loader](https://github.com/getforma-dev/forma/blob/v3.0.0/packages/compiler/src/module-loader.ts)
and [declared schema reader](https://github.com/getforma-dev/forma/blob/v3.0.0/packages/compiler/src/props-schema.ts).

## Use the same build commands

TSX does not add commands to the [installation steps](/docs/3.0/installation/).
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](/docs/3.0/application-configuration/) for that wiring.

On a rendered page, [island activation](/docs/3.0/islands/) 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.

This website also has an application-owned browser navigator for its generated
portal pages. It fetches destination HTML and replaces the portal content region.
That code is part of the website, not a published general-purpose Forma router.
The site's native lab pages use document navigation; their islands belong to the
server-rendered document that declared them.
