# Kotter turns a Kotlin console app into a declarative session

> Kotter is a Kotlin library for console applications that sits between raw println calls and something like Java Curses, and its central idea is a session block where text and input are declared rather than printed. The example in the README is a question, a prompt, a live variable and a callback, and it is short enough to be the whole tutorial.

**varabyte/kotter** — A declarative, Kotlin-idiomatic API for writing dynamic console applications.

- Repository: https://github.com/varabyte/kotter
- Stars: 698 · Forks: 22
- Language: Kotlin
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/varabyte-kotter

## The README example is the whole library in one block

Kotter, which the README expands as a KOTlin TERminal library, is a declarative, Kotlin-idiomatic API for writing dynamic console applications.

The example at the top of the README is a question with a colour and a live variable:

```kotlin
session {
  var wantsToLearn by liveVarOf(false)
  section {
    text("Would you like to learn "); cyan { text("Kotter") }; textLine("? (Y/n)")
    text("> "); input(Completions("yes", "no"))

    if (wantsToLearn) {
      yellow(isBright = true) { p { textLine("""\(^o^)/""") } }
    }
  }.runUntilInputEntered {
    onInputEntered { wantsToLearn = "yes".startsWith(input.lowercase()) }
  }
}
```

Four ideas are in there. The `session` block is the scope a program runs in. `liveVarOf` creates a variable whose change repaints the screen, and it is a delegated property, so `wantsToLearn` reads like a plain boolean. The `section` groups output, and `text` and `textLine` write to it, with `cyan` and `yellow` applying colour as a scoped block rather than as a prefix and a suffix. And `runUntilInputEntered` with an `onInputEntered` callback is how the program waits for the user.

The Completions call with yes and no on the input is a tab completion list, and the comparison is deliberately lenient, checking that the typed input starts with the expected value case-insensitively.

What the library is, in the project's own framing, is a solution a bit more opinionated than making raw println calls but way less featured than something like Java Curses. That positioning is the design brief, and it is why the API looks like this and not like a widget toolkit.

## Four things the library does for you

The README lists what the library helps with, and the list is short.

Setting colors and text decorations, with underline and bold named as examples. Handling user input. Creating timers and animations. And repainting terminal text when values change.

That last one is the part that is genuinely hard to do well and the part that makes the library worth using. A conventional terminal program prints lines, and if a value changes you print a new line or use cursor movement escapes and hope the terminal keeps up. A live variable repaints the affected region in place, so a countdown updates the same spot rather than scrolling the screen.

The scope-based colour blocks are the other ergonomic decision. `cyan { text("Kotter") }` opens a colour scope and closes it, so you never have to pair a start and an end sequence and never leak colour into the rest of the output. And `yellow(isBright = true)` shows that the colour functions are ordinary functions taking options, not a fixed set of named styles.

The library is multiplatform, supporting JVM and native targets, and the README names four specific problems it addresses: colours and decorations, input, timers and animations, and repainting.

The example projects in the repository are the evidence that this covers more than text prompts. The examples directory holds the game of life, snake, sliding tiles, doom fire and Wordle, and also anim, blink, border, clock, compiler, extend, grid, hiragana, input, keys, life, list, mandelbrot, mosaic, native and picker. A mandelbrot example and a doom fire example in a console library is a stronger statement of capability than any feature list.

## JVM and native are different products to distribute

Kotter is multiplatform, and the README is candid that the two targets suit different situations.

The advice is to start with a JVM project if you are not sure, because that target is far easier to distribute and it means your project has access to a very broad ecosystem of Kotlin and Java libraries. Multiplatform is described as useful if you want to distribute binaries to users without requiring them to have Java installed on their machine.

The JVM dependency is one line:

```kotlin
dependencies {
    implementation("com.varabyte.kotter:kotter:1.4.0")
    testImplementation("com.varabyte.kotterx:kotter-test-support:1.4.0")
}
```

Note that the test support library is a separate artifact, com.varabyte.kotterx:kotter-test-support, which is how a console UI library can offer snapshot testing of rendered frames.

The JVM build file also carries a long list of runtime flags, and the comment explains why at length. Starting in JDK 24, the Java runtime is getting a bit more strict about what code is allowed to make native calls, which jline, a major dependency used by kotter, does. This is related to JPMS, the Java module system, and you are supposed to whitelist modules that you grant heightened privileges for. Unfortunately, JPMS does not play too well with Kotlin, and it is not straightforward or even that beneficial to modularize your own application. For now, the easiest recommendation is to just disable the whole system.

So the application default JVM args are `-XX:+IgnoreUnrecognizedVMOptions` and `--enable-native-access=ALL-UNNAMED`, and without them you see warnings about a restricted method in java.lang.System having been called and about java.lang.System::loadLibrary, with a note that restricted methods will be blocked in a future release unless native access is enabled.

That is a real deployment detail rather than a footnote, and it is the kind of thing that would otherwise cost an afternoon on JDK 24.

## Native targets need a build host per platform

The multiplatform build lists the targets explicitly, and the comment on the list is the practical warning. You need the right machine to build each one, otherwise the target is disabled automatically.

```kotlin
kotlin {
    // Choose the targets you care about.
    // Note: You will need the right machine to build each one; otherwise, the target is disabled automatically
    listOf(
        linuxX64(), // Linux
        mingwX64(), // Windows
        macosArm64(), // Mac M1
        macosX64(), // Mac Legacy
    ).forEach { nativeTarget ->
        nativeTarget.apply {
            binaries {
                executable {
                    entryPoint = "main"
                }
            }
        }
    }
```

Four targets, and you cannot cross-compile between them, so a release that ships for Linux, Windows and both Mac architectures needs more than one build machine. The README makes the same point about the project's own CI, pointing at the Kotter publish workflow, which runs on both Linux and Mac targets to build platform-specific Kotter artifacts.

The dependency block for multiplatform puts the library in commonMain and the test support in commonTest, which is the correct shape for a library that behaves the same everywhere.

The snapshots mechanism is the other piece of the build story, and it is for contributors and bug reporters rather than users. If you file a bug and are asked to test a fix using a snapshot, you must add an entry for the sonatype snapshots repository to your repositories block, and the entry is restricted to the com.varabyte.kotter and com.varabyte.kotterx groups with snapshotsOnly, so a normal build cannot accidentally resolve a snapshot.

## The repository shape and the version line

The tree is that of a Gradle project with a two-module library, and the naming is worth decoding because the artifact coordinates are unusual.

There is a kotter/ directory for the library itself and a kotterx/ directory for the extras, which is where kotter-test-support lives. The com.varabyte.kotter and com.varabyte.kotterx group identifiers reflect that split, so kotterx is a separate publication with its own versioning inside the same repository.

There is an examples/ directory with its own Gradle build, holding the sample applications, and a docs/ directory for the API documentation, which the badge points at as varabyte.github.io/kotter. The root has build.gradle.kts, settings.gradle.kts, gradle.properties, a gradle/ directory with the wrapper, and both gradlew and gradlew.bat.

Two files at the root are worth noting. CONTRIBUTING.md and MIGRATING.md, the second of which tells you what changed between versions, which matters for a library that is pre-1.0 and moving.

The version history shows a project that has been at 1.x for a while and releases steadily. The recent versions are v1.4.0 from 2026-07-29, v1.3.0 from 2026-05-06 and v1.2.1 from 2024-12-13, and the badge at the top of the README says version 1.4.0. The last push was on 2026-08-09.

The compatibility badges give the language requirements, with Kotlin 1.8 or newer for the JVM target and 1.9 or newer for native, which is the sort of floor you want to know about before you add the dependency to a project pinned to an older Kotlin.

The project also has a Discord server and a Bluesky account, and the topics list includes hacktoberfest, which is a hint that contributions arrive seasonally.

## Conclusion

Adopt Kotter if you are writing a console application in Kotlin and want colours, input handling, timers and live-updating output without pulling in a full curses binding. Skip it if you are on a JVM language other than Kotlin, since the API is built on Kotlin idioms like delegated properties and the session DSL, or if you need a widget toolkit, since this is deliberately a thin layer. Verify first which target you want, because the JVM artifact and the native artifacts distribute very differently, and that you read the JVM-specific JVM flags the build file sets, since the JDK 24 module system otherwise blocks the native calls the library makes.

## FAQ

### What is Kotter used for?

It is a relatively thin, declarative, Kotlin-idiomatic API for writing console applications, providing colours and text decorations, user input handling, timers and animations, and clean repainting of terminal text when values change, sitting between raw println calls and something like Java Curses.

### How do I add Kotter to a Gradle project?

For a JVM project, add `implementation("com.varabyte.kotter:kotter:1.4.0")` and `testImplementation("com.varabyte.kotterx:kotter-test-support:1.4.0")`. The JVM application also needs the `-XX:+IgnoreUnrecognizedVMOptions` and `--enable-native-access=ALL-UNNAMED` runtime flags, because JDK 24 blocks the native calls the library makes.

### Can Kotter build native binaries?

Yes, it supports linuxX64, mingwX64, macosArm64 and macosX64 targets. You need the right machine to build each one, and the target is disabled automatically otherwise, which is why the project's own CI runs on both Linux and Mac to build platform-specific artifacts.

## Sources

- [Issues](https://github.com/varabyte/kotter/issues)
- [License: Apache-2.0](https://github.com/varabyte/kotter/blob/main/LICENSE)
- [README](https://github.com/varabyte/kotter/blob/main/README.md)
- [Releases](https://github.com/varabyte/kotter/releases)
- [varabyte/kotter on GitHub](https://github.com/varabyte/kotter)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/varabyte-kotter
