Skip to content
AgileLogics
5 min read

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.fs file with domain types and validation between a Giraffe server and a Fable client means the rules can't drift apart.