JSX & React
Floe supports JSX natively. Write React components with Floe’s type system.
Components
Section titled “Components”import { useState } from "react"
export let Counter() -> JSX.Element = { let (count, setCount) = useState(0)
<div> <h1>Count: {count}</h1> <button onClick={() -> setCount(count + 1)}>Increment</button> </div>}Components are exported let declarations with a JSX.Element return type. The last expression is the return value.
type ButtonProps = { label: string, onClick: () -> (), disabled: boolean,}
export let Button(props: ButtonProps) -> JSX.Element = { <button onClick={props.onClick} disabled={props.disabled} > {props.label} </button>}Conditional Rendering
Section titled “Conditional Rendering”Use match expressions:
<div> {match isLoggedIn { true -> <UserProfile user={user} />, false -> <LoginForm />, }}</div>Optional Rendering
Section titled “Optional Rendering”For the common “render if present, nothing if absent” pattern, use Option.map. Since None compiles to undefined and React ignores undefined children, Option<JSX.Element> works directly in JSX:
// Instead of matching with a None -> <></> arm:<div> {user.nickname |> Option.map((nick) -> <span className="badge">{nick}</span>)}</div>This replaces the TypeScript {x && <Component />} pattern, without the truthiness pitfalls (e.g. {count && <Tag />} rendering 0).
Use pipes with map:
<ul> {items |> map((item) -> <li key={item.id}>{item.name}</li>)}</ul>Spread Attributes
Section titled “Spread Attributes”Forward all props to a child element:
export let Card(props: CardProps) -> JSX.Element = { <div {...props} className="card" />}Compiles to <div {...props} className={"card"} /> in TypeScript.
Member Expressions
Section titled “Member Expressions”Compound component patterns using dot notation are supported:
import { Select, ListBox } from "ui"
export let Picker() -> JSX.Element = { <Select> <Select.Trigger>Choose...</Select.Trigger> <Select.Value /> <Select.Popover> <ListBox> <ListBox.Item key="a">Alpha</ListBox.Item> <ListBox.Item key="b">Beta</ListBox.Item> </ListBox> </Select.Popover> </Select>}This is common in component libraries like React Aria, Radix UI, and similar. The tag name compiles as-is to TypeScript.
Fragments
Section titled “Fragments”<> <Header /> <Main /> <Footer /></>JSX Detection
Section titled “JSX Detection”The compiler automatically emits .tsx when JSX is detected, and .ts otherwise. No configuration needed.
The JSX namespace in the emitted file
Section titled “The JSX namespace in the emitted file”React 19 removed the global JSX namespace and moved it under React.JSX. You still write JSX.Element in Floe. The compiler adds the import the emitted file needs:
import type { JSX } from "react";
export function Counter(): JSX.Element { // ...}The import is type-only, so every bundler erases it and the emitted module gains no runtime dependency on React. Import the namespace yourself, as import { JSX } from "react", and the compiler leaves your import alone.
What’s Different from React + TypeScript
Section titled “What’s Different from React + TypeScript”- No
classcomponents - only function components - No
anyin props - every prop must be typed - Pipes instead of method chaining for data transformations
- Pattern matching instead of ternaries for complex conditionals