Import any TypeScript library into Floe. Import Floe from TypeScript. Types, functions, and React components work both ways.
If you know TypeScript, you can read Floe. Union types, pattern matching, and pipes replace the boilerplate you already write — with exhaustive checking built in.
import trusted { useState } from "react"
type User {
name: string,
role: string,
active: boolean,
}
type Status {
| Loading
| Failed(string)
| Ready(Array<User>)
}
export fn Dashboard() -> JSX.Element {
const [status, setStatus] = useState<Status>(Loading)
status |> match {
Loading -> <Spinner />,
Failed(msg) -> <Alert message={msg} />,
Ready(users) -> {
const active = users
|> filter(.active)
|> sort_by(.name)
<div>
<h2>{active |> length} active</h2>
{active |> map((u) =>
<Card key={u.name} title={u.name} badge={u.role} />
)}
</div>
},
}
} import { useState } from "react";
type User = {
name: string;
role: string;
active: boolean;
};
type Status =
| { tag: "Loading" }
| { tag: "Failed"; message: string }
| { tag: "Ready"; users: User[] };
export function Dashboard(): JSX.Element {
const [status, setStatus] = useState<Status>(
{ tag: "Loading" }
);
if (status.tag === "Loading") {
return <Spinner />;
}
if (status.tag === "Failed") {
return <Alert message={status.message} />;
}
const active = status.users
.filter((u) => u.active)
.sort((a, b) => a.name.localeCompare(b.name));
return (
<div>
<h2>{active.length} active</h2>
{active.map((u) => (
<Card key={u.name} title={u.name} badge={u.role} />
))}
</div>
);
}
Chain transformations in reading order with |>.
Dot shorthands pull fields. Placeholders slot arguments where you want them.
// Dot shorthand — .field becomes an accessor function
const activeNames = users
|> filter(.isActive)
|> sort_by(.lastLogin)
|> map(.displayName)
// Placeholder _ controls argument position
const discounted = items
|> map(.price)
|> filter(less_than(_, 100))
|> reduce(add, 0, _)
// Tap for side effects without breaking the chain
const result = data
|> validate
|> tap(Console.log)
|> transform
|> save
Add a variant to a union type. The compiler flags every match
that doesn't handle it yet.
type Route {
| Home
| Profile(string)
| Settings
| NotFound
}
fn render(route: Route) -> JSX.Element {
match route {
Home -> <HomePage />,
Profile(id) -> <ProfilePage id={id} />,
Settings -> <SettingsPage />,
NotFound -> <NotFoundPage />,
}
}
Floe has no null, undefined, or exceptions.
Functions return Result or Option, and ? propagates errors up.
fn getUser(id: string) -> Result<User, ApiError> {
const response = fetch("/api/users/{id}")?
const user = response.json()?
Ok(user)
}
// The caller sees exactly what can go wrong
match getUser("123") {
Ok(user) -> renderProfile(user),
Err(NotFound) -> <p>User not found</p>,
Err(Unauthorized) -> redirect("/login"),
}
Add the Vite plugin and write .fl files alongside .ts.
Import in either direction.
import floe from "@floeorg/vite-plugin"
import { defineConfig } from "vite"
export default defineConfig({
plugins: [floe()],
}) import { App } from "./app.fl"
ReactDOM.createRoot(document.getElementById("root")!).render(<App />)