[ANN] Mint Tea (minttea 0.0.1) – a little TUI framework for OCaml

Hi folks :wave: just released minttea v0.0.1 on opam.

Mint Tea is a fun, functional, and stateful way to build terminal apps in OCaml heavily inspired by BubbleTea. Mint Tea is built on Riot and uses The Elm Architecture.

It includes plenty of examples of what you can build with it:

Toggling AltScreen


Multiple Views/Pages

Bouncing Ball / Spring-based physics

Alongside this release there’s also its sibling packages:

You can read the full changelog here.

If you’re interested in contributing to any of these, there’s a few good first issues open already, and don’t hesitate to reach out on Discord/X: @leostera :slight_smile:

Happy hacking! :tada:


Looking forward to peruse the source code soon, but why did you find it useful to implement mint tea with an actor model?


UIs have long been known to be a perfect fit for actor systems. Think about it–every part of the UI needs to be independent and responsive to events, while also being able to recover from errors. This is a nearly perfect use-case for actors. Elixir’s Phoenix LiveView system is a great proof of this but even way back when Joe Armstrong made an Erlang wrapper for the X Window System that showed the potential of this model for a perfect fit: https://erlang.org/workshop/2004/ex11.pdf


what @yawaramin said + I find it a lot easier to think in actors :slight_smile:

currently minttea is structured with 3 processes that can parallelly do output (rendering), input, and the program update cycle.

but larger applications can be decomposed so that subsections are processes on their own, and then a “compositor” process stitches them together.

using riot its also super easy to spin up other supervision trees of work that have their own failure mode, so they won’t take the UI down if they fail for whatever reason, while still being able to communicate with them via message passing (tho this is in a PR right now).


Mint Tea looks very cool! I’m pretty impressed by what I’ve seen in the examples.

Somewhat relatedly, there was a GUI toolkit for Standard ML called eXene back in 1991 which used Concurrent ML as its basis similarly to how this one uses actors. So building user interfaces as a “distributed system” is definitely not unheard of.


Great job on the nice examples.

I have a couple of questions related to the function Minttea.app

Why does it require init and initial_model separately, rather than a tuple model * Command.t (like in Elm)?

Also, why does Minttea.app require unit as a last argument ?

I’m similarly confused why initial_model requires a unit argument.

Curious to hear the answer :slight_smile:


Even though this function does not take any optional arguments now, it will be easier to add them in the future by requiring a positional ‘unit’ argument now. The change will be source-code compatible with existing code.


:100: this. Just making room for myself to add more things without breaking all the examples/apps. Generally speaking i find that using a unit gives me a little room play with the API.

Good feedback :slight_smile: I’m not much of an Elmer myself and this is how i was using the pattern in BubbleTea from Go. If this is more natural then we can definitely fix this in the next version. If you’re interested, PRs are welcome!

Some initial models can be expensive to create, so allowing delayed execution is imo a Good Thing :tm:

Thanks! :raised_hands:


Ah I see. I tend to associate functions requiring a unit to functions generating a side-effect so that’s why I got confused.

Cool :slight_smile: I’ll have to play with the lib for real to give better feedback.

In Elm, the init function has the same return type than the update function so that makes the API “symmetric” for lack of a better word. That allows you to reset your state by just recalling your init function within your update for instance. But that may not make sens in the absence of a customizable msg/event type


You got me thinking about this and I updated this API to be just one function:

val init : 'model -> Command.t

So that you can build the app, and when you call Minttea.start you can pass in the initial state:

Minttea.start app ~initial_model

This felt the cleanest since you don’t have to hardcode your initial state, and you can start your app in different states.

Considering I’d like to integrate MintTea into existing apps, this is useful in at least 2 ways:

  • You can parse CLI args before starting the app, and set the model to something specific based on that. For example, you could use cmdliner to do the initial argument parsing and have it build the initial state of your app before calling Minttea.start.

  • You can recreate specific test scenarios by just starting at the right state

This has been working alright in the new examples I’ve been building, but I very much welcome your opinions/critiques! :raised_hands:

Also a side-note if you’re gonna pin from the repo, you’ll also need to pin down the latest tty and colors. They should both land on opam soon tho.

1 Like

Oh forgot to say that since a Mint Tea app is just a Riot app, we can send Riot messages to it, and they’ll be sent into the main update loop as custom events:

open Riot
open Minttea

type Message.t + = Custom_event

let update e m = 
  match e with
  | Event.KeyDown Space -> 
      send (self ()) Custom_event;
      (m, Command.Noop)
  | Event.Custom Custom_event -> (* ... *)
  (* ... *)

We haven’t yet integrated this nicely with a command like Command.Message Custom_event, but it’s only a matter of time. I’m not a fan of leaking the Riot primitives like self () or send here.