Why Bonsai?

The successor to Incr_dom, Bonsai is a front-end web framework following the Elm style of a functional user-interface API.


UI Components are implemented as purely functional state-machines, and are composible in multiple ways:

  • Parallel: Components can be built out of sub-components, and these sub-components don’t know about one another.
  • Serial: Components can compute values that are used downstream by other components.


Incrementalization inside the framework means that values don’t get recomputed until necessary. This applies to every value, not just the view.

(* An example of a small Bonsai component for creating counter
widgets ("+" and "-" buttons allow you to increment and decrement a
number). The state of each counter is housed in a shared [Map]. Adding
a new counter is as simple as adding a value to the map. *)

module Model = struct
  type t = int Int.Map.t [@@deriving sexp, equal]

module Action = struct
  type t =
    | New
    | Update of int * int
  [@@deriving sexp]

let default_model = Int.Map.empty

let apply_action ~inject:_ ~schedule_event:_ model = function
  | Action.New -> Map.add_exn model ~key:(Map.length model) ~data:0
  | Update (location, diff) ->
    Map.update model location 
      ~f:(Option.value_map ~default:0 ~f:(( + ) diff))

let component =
  let%sub state =
      (module Model)
      (module Action)
  @@ let%map state, inject = state in
  let button text action =
      ~attr:(Attr.on_click (fun _ -> inject action)) 
      [ Node.text text ]
  let add_button = button "add" New in
  let for_each i c =
      [ button "-1" (Update (i, -1)); 
        Node.textf "%d" c; 
        button "+1" (Update (i, 1)) ]
  let counters = state |> Map.data |> List.mapi ~f:for_each in
  Node.div (add_button :: counters)

Learn More


Read the Getting Started guide.

For API use, we recommend reading the MLI.


If you want to understand the design of the Bonsai API, it might be helpful to read this paper on the arrow calculus by Lindley et al.