tui4s

kyo-ui in the terminal.

tui4s runs a kyo-ui value in the terminal, in Scala 3. kyo-ui describes the screen. tui4s lays it out in cells, follows its signals, sends the keys to it and paints it with jatatui.

The smallest app

This is the complete Counter example. The UI is plain kyo-ui, and `TuiApp` is its main class.
Counter.scala
import kyo.*
import kyo.UI.*
import tui4s.{Tui, TuiApp, initSig}

/** The Counter: a kyo-ui UI with one piece of state (a `SignalRef`), key
  * handlers and two buttons. `+` and `-` change the count, Tab moves between
  * the buttons, Enter and Space press them, `q` quits (and so does Ctrl-C).
  */
object Counter:

    /** The Counter UI; `quit` ends the run. */
    def ui(quit: Unit < Sync): UI < Sync =
      initSig(0).map(count => view(count, quit))

    /** The Counter view over a count that the caller owns. */
    def view(count: SignalRef[Int], quit: Unit < Sync): UI =
      div.onKeyDown {
        case KeyboardEvent(Keyboard.Char('+'), _, _) =>
          count.updateAndGet(_ + 1)
        case KeyboardEvent(Keyboard.Char('-'), _, _) =>
          count.updateAndGet(_ - 1)
        case KeyboardEvent(Keyboard.Char('q'), _, _) => quit
        case _                                       => ()
      }(
        h1("Counter"),
        span("Count: ", count.map(_.toString)).id("count"),
        div.style(_.row.gap(1.px))(
          button("+").onClick(count.updateAndGet(_ + 1)),
          button("-").onClick(count.updateAndGet(_ - 1)),
        ),
        p("+ / - change it, Tab moves, Enter presses, q quits").style(
          _.color(Style.Color.gray),
        ),
      )
end Counter

/** Runs the Counter in the real terminal. Start it with its start script, not
  * with `sbt run` (sbt also reads the keyboard):
  * {{{
  * sbt examples/launchers
  * examples/target/bin/counter
  * }}}
  */
object CounterApp extends TuiApp:
    def ui: UI < (Async & Scope) = Counter.ui(Tui.quit)

What tui4s gives

    kyo-ui is the DSL
    tui4s has no UI DSL of its own. A kyo-ui value describes the screen, and its signals drive the frames.
    Tests with no terminal
    Headless runs the app, sends keys and reads the screen in a plain unit test.
    Composables and widgets
    tui4s-use and tui4s-widgets are plain kyo-ui code for the JVM and Scala.js.
    The same app on the web
    The assistant example runs in the terminal and in a browser, from one shared code base.
    The terminal comes back
    Each way that a run ends restores the terminal: a quit, a failure, a signal or System.exit.
    Ready for AI agents
    Each page is also Markdown, with llms.txt and an MCP server. A test checks each Scala block.
Copyright © 2026