I’m excited to share an early preview of Mosaic, a modern terminal user interface framework for OCaml.
What is Mosaic?
Mosaic is a high-level framework for building terminal user interfaces in OCaml. It provides a TEA (The Elm Architecture) runtime with a CSS-compatible flexbox layout engine and a rich set of composable components. It handles text shaping (styling, wrapping, selection), focus and event bubbling, z-ordering, and responsive layout.
Under the hood, it builds on two libraries that can also be used independently:
- Matrix: a terminal runtime focused on performance and modern protocols. Highlights: near-zero-allocation diffed rendering, immediate- mode API, Kitty keyboard, SGR/URXVT/X10 mouse, bracketed paste, focus tracking, inline/alt/split display modes, and built-in debug overlay/frame dumps. It also provides a virtual terminal emulator (VTE) and pseudo-terminal (PTY) management subsystems.
- Toffee: a CSS-compatible layout engine. It’s a port of Rust’s Taffy with Flexbox, CSS Grid, and Block layout; gap/padding/borders/titles; and layout caching for efficiency.
Why Mosaic?
Terminal UIs are seeing a renaissance. Tools like Claude Code and OpenCode have gotten people excited about what can be built in the terminal and the TUI community is gaining momentum in other ecosystems.
OCaml has had LambdaTerm and Notty for terminal graphics for years, but there’s been a gap when it comes to performance and high-level abstractions for building complex UIs.
Mosaic aims to fill that gap by providing Matrix as a solid terminal foundation, and building a high-level TEA framework with layout and components on top.
On a personal side, I’m building Mosaic to power the two projects I’m currently working on:
- It will be the basis for a TUI dashboard for monitoring model training in Raven. We’re starting an Outreachy internship to build this out this Monday.
- It powers Spice, the upcoming OCaml coding agent I announced at FunOCaml 2025.
Try It Now
The libraries aren’t on opam yet, but you can try them today:
Option 1: Pin from GitHub
opam pin add https://github.com/tmattio/mosaic.git
Option 2: Build from source
git clone https://github.com/tmattio/mosaic
cd mosaic
opam install . --deps-only
dune build
Then run some examples:
# Interactive Game of Life (ported from Notty examples)
dune exec ./matrix/examples/02-life/main.exe
# Particles simulation with multiple display modes
dune exec ./matrix/examples/14-particles/main.exe
# High-level TEA counter
dune exec ./mosaic/examples/01-counter/main.exe
Have a look at the examples directories (Matrix and Mosaic) for more demos to explore!
As a bonus, we also have more complete demos for both projects:
- A graphical terminal emulator built with Matrix: mosaic/matrix/examples/x-emulator at main · tmattio/mosaic · GitHub
- A dashboard app built with Mosaic: mosaic/mosaic/examples/x-dashboard at main · tmattio/mosaic · GitHub
Quick Examples
Mosaic: The Elm Architecture
Mosaic follows TEA for building declarative UIs:
open Mosaic_tea
type msg = Increment | Decrement | Quit
let init () = (0, Cmd.none)
let update msg model =
match msg with
| Increment -> (model + 1, Cmd.none)
| Decrement -> (model - 1, Cmd.none)
| Quit -> (model, Cmd.quit)
let view model =
box ~align_items:Center ~justify_content:Center
~size:{ width = pct 100; height = pct 100 }
[
box ~flex_direction:Column ~align_items:Center ~gap:(gap 1)
~border:true ~padding:(padding 2) ~title:"Counter"
[
text ~content:(Printf.sprintf "Count: %d" model) ();
text ~content:"Press + or - to change, q to quit" ();
];
]
let subscriptions _model =
Sub.on_key (fun ev ->
match (Mosaic_ui.Event.Key.data ev).key with
| Char c when Uchar.equal c (Uchar.of_char '+') -> Some Increment
| Char c when Uchar.equal c (Uchar.of_char '-') -> Some Decrement
| Char c when Uchar.equal c (Uchar.of_char 'q') -> Some Quit
| Escape -> Some Quit
| _ -> None)
let () = run { init; update; view; subscriptions }
Matrix: Low-Level Power
For direct terminal control, Matrix provides an immediate-mode API:
open Matrix
let () =
let app = Matrix.create () in
let count = ref 0 in
Matrix.run app
~on_render:(fun app ->
let grid = Matrix.grid app in
Grid.clear grid;
let cols, rows = Matrix.size app in
let text = Printf.sprintf "Count: %d" !count in
let hint = "Press + or - to change, q to quit" in
Grid.draw_text grid ~x:(cols / 2 - 5) ~y:(rows / 2) ~text;
Grid.draw_text grid ~x:(cols / 2 - 18) ~y:(rows / 2 + 1) ~text:hint)
~on_input:(fun app -> function
| Input.Key { key = Char c; _ } when Uchar.equal c (Uchar.of_char '+') ->
incr count
| Input.Key { key = Char c; _ } when Uchar.equal c (Uchar.of_char '-') ->
decr count
| Input.Key { key = Char c; _ } when Uchar.equal c (Uchar.of_char 'q') ->
Matrix.stop app
| Input.Key { key = Escape; _ } -> Matrix.stop app
| _ -> ())
Coming Soon: TUI for ML Training
We’re starting an Outreachy internship to build a TUI for monitoring model training with Raven, the scientific computing ecosystem for OCaml. It will provide a TensorBoard experience in the terminal, built entirely with Mosaic.
A good example of what we’re aiming to build is Wandb’s newly released TUI:
Mosaic vs Notty
Notty is the current go-to terminal UI library for OCaml, with a well-designed declarative image API. Mosaic sits a level above: it’s a TEA runtime with flexbox layout, rich components, focus/event bubbling, and diffed rendering via Matrix. In scope, Notty is closer to Matrix (the terminal infrastructure under Mosaic) than to Mosaic itself.
Matrix covers the low-level rendering, modern terminal protocols, and immediate-mode API that Notty doesn’t. For a detailed Matrix vs Notty comparison, see our comparison table.
Acknowledgements
Mosaic stands on the shoulders of great work:
- Bubble Tea - inspiration for the high-level TEA runtime and app structure.
- Notty - Matrix’s declarative Image API is directly copied from Notty’s to provide a familiar interface for OCaml users.
- OpenTUI - the biggest influence on Mosaic UI internals (render tree, text buffer, events, selection). Mosaic’s UI internals have been rewritten to mirror OpenTUI’s following its release, if you’re working in TypeScript, I can’t recommend it enough, it’s a fantastic piece of engineering.
- Rich and Textual - for ideas on rich text, diagnostics, and polished terminal UX.
Feedback Welcome
This is an early preview. APIs are stabilizing but may change. I’d love your feedback on:
- API ergonomics
- Missing components or features you need
- Performance on your terminal
- Bugs (please open issues!)
Give it a try and let me know what you think!
