Fable: F# in the Browser
Prerequisites: Web development with Giraffe, Saturn, and Falco
Fable is a compiler that turns F# into JavaScript (and, in newer versions, TypeScript, Python, Rust, and Dart). It lets you write browser front ends in F# and share domain code between server and client.
Getting started
dotnet new install Fable.Template
dotnet new fable -o MyFableApp
cd MyFableApp
dotnet tool restore
npm install
npm start
Fable is installed as a .NET tool, and the output is bundled with a JavaScript tool such as Vite. Templates change over time — follow the current instructions on fable.io.
Talking to the DOM
// illustrative: compiled with Fable, runs in the browser
open Browser.Dom
let button = document.getElementById "greet" :?> Browser.Types.HTMLButtonElement
let output = document.getElementById "output"
let mutable clicks = 0
button.onclick <- fun _ ->
clicks <- clicks + 1
output.textContent <- $"Hello from F#! Clicked {clicks} times."
Elmish: the Elm architecture
Most Fable apps use Elmish, a port of the Elm architecture: a
Model, a Msg union of everything that can happen, a pure update
function, and a view function — often rendered with React through
Feliz:
// illustrative: Fable + Elmish + Feliz
open Elmish
open Feliz
type Model = { Count: int }
type Msg =
| Increment
| Decrement
let init () = { Count = 0 }, Cmd.none
let update msg model =
match msg with
| Increment -> { model with Count = model.Count + 1 }, Cmd.none
| Decrement -> { model with Count = model.Count - 1 }, Cmd.none
let view model dispatch =
Html.div [
Html.h1 (string model.Count)
Html.button [ prop.text "+"; prop.onClick (fun _ -> dispatch Increment) ]
Html.button [ prop.text "-"; prop.onClick (fun _ -> dispatch Decrement) ]
]
Everything you've learned applies directly: records for state, unions
for messages, pattern matching in update. If you'd like to see the
original, the Elm course covers the architecture it
comes from.
What to know
- Fable supports most of FSharp.Core (lists, maps, options, async) but not arbitrary .NET libraries — only packages written for Fable.
- JavaScript libraries are used through bindings (typed wrappers), many of which are published as NuGet packages.
- Sharing a
Shared.fsfile with domain types and validation between a Giraffe server and a Fable client means the rules can't drift apart.