SkillAgentSearch skills...

Kotter

A declarative, Kotlin-idiomatic API for writing dynamic console applications.

Install / Use

/learn @varabyte/Kotter

README

version: 1.2.1 kotter tests kotter coverage <a href="https://varabyte.github.io/kotter"> kotter docs </a> <br> kotlin compatibility jvm kotlin compatibility k/n <br> <a href="https://discord.gg/5NZ2GKV5Cs"> <img alt="Varabyte Discord" src="https://img.shields.io/discord/886036660767305799.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2" /> </a> Bluesky

Kotter 🦦

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()) }
  }
}

Code sample in action

See also: the game of life, snake, sliding tiles, doom fire, and Wordle implemented in Kotter!


Kotter (a KOTlin TERminal library) aims to be a relatively thin, declarative, Kotlin-idiomatic API that provides useful functionality for writing delightful console applications. It strives to keep things simple, providing a solution a bit more opinionated than making raw println calls but way less featured than something like Java Curses.

Specifically, this library helps with:

  • Setting colors and text decorations (e.g. underline, bold)
  • Handling user input
  • Creating timers and animations
  • Seamlessly repainting terminal text when values change

Kotter is multiplatform, supporting JVM and native targets.

The next sections deal with setting Kotter up, but you may wish to jump straight to the usage section ▼ to immediately start learning about this library.

🐘 Gradle

🎯 Dependency

Kotter supports JVM and native targets.

[!TIP] If you're not sure what you want, start with a JVM project. That target is far easier to distribute. It also means your project will have access to a very broad ecosystem of Kotlin and Java libraries.

In case it affects your decision, you can read more about distributing Kotter applications ▼ later in this document.

JVM

// build.gradle.kts (kotlin script)
plugins {
    kotlin("jvm")
    application
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.varabyte.kotter:kotter-jvm:1.2.1")
    testImplementation("com.varabyte.kotterx:kotter-test-support-jvm:1.2.1")
}

application {
    applicationDefaultJvmArgs = listOf(
        // JDK24 started reporting warnings for libraries that use restricted native methods, at least one which Kotter
        // uses indirectly (via jline/jansi). It looks like this:
        //
        // WARNING: A restricted method in java.lang.System has been called
        // WARNING: java.lang.System::loadLibrary has been called by ...
        // WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
        // WARNING: Restricted methods will be blocked in a future release unless native access is enabled
        //
        // The best solution we have for now is to disable the warning by explicitly enabling access.
        // We also suggest the IgnoreUnrecognizedVMOptions flag here to allow kotter applications to be able to compile
        // with JDKs older than JDK24. You can remove it if you are intentionally using JDK24+.
        // See also: https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/doc-files/RestrictedMethods.html
        // And also: https://github.com/jline/jline3/issues/1067
        "-XX:+IgnoreUnrecognizedVMOptions",
        "--enable-native-access=ALL-UNNAMED",
    )
    // The following assumes a top-level `main.kt` file in your project; adjust as needed otherwise
    mainClass.set("MainKt")
}

Multiplatform

Multiplatform can be useful if you want to distribute binaries to users without requiring they have Java installed on their machine.

// build.gradle.kts (kotlin script)
plugins {
    kotlin("multiplatform")
}

repositories {
    mavenCentral()
}

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"
                }
            }
        }
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("com.varabyte.kotter:kotter:1.2.1")
            }
        }
        val commonTest by getting {
            dependencies {
                implementation("com.varabyte.kotterx:kotter-test-support:1.2.1")
            }
        }
    }
}

[!NOTE] Building native binaries is a little tricky, as you may need different host machines in order to build the various binaries. For example, here is Kotter's CI workflow which runs on both Linux and Mac targets to build platform-specific Kotter artifacts.

Testing snapshots

Most users won't ever need to run a Kotter snapshot, so feel free to skip over this section! However, occasionally, bug fixes and new features will be available for testing for a short period before they are released.

If you ever file a bug with Kotter and are asked to test a fix using a snapshot, you must add an entry for the sonatype snapshots repository to your repositories block in order to allow Gradle to find it:

// build.gradle.kts

repositories {
  mavenCentral()
+ maven("https://central.sonatype.com/repository/maven-snapshots/") {
+   mavenContent {
+     includeGroup("com.varabyte.kotter")
+     snapshotsOnly()
+   }
+ }
}

🚥 Running examples

If you've cloned this repository, examples are located under the examples folder.

JVM

Most of the examples (except examples/native) target the JVM. To try one of them, you can navigate into it on the command line and run it via Gradle.

$ cd examples/life
$ ../../gradlew run

However, because Gradle itself has taken over the terminal to do its own fancy command line magic, the example will actually open up and run inside a virtual terminal.

If you want to run the program directly inside your system terminal, which is hopefully the way most users will see your application, you should use the installDist task to accomplish this:

$ cd examples/life
$ ../../gradlew installDist
$ cd build/install/life/bin
$ ./life

[!WARNING] If your terminal does not support features needed by Kotter, which could happen on legacy machines for example, then this still may end up running inside a virtual terminal.

Multiplatform

Unlike the JVM target, native targets do not have a virtual terminal fallback. So be sure you do not use any of the Gradle run tasks (e.g. runDebugExecutabule...). This will also fail if you try to run your program through the IDE via the green "play" arrow.

Instead, you should link your executable and then run it directly.

For example, on Linux:

$ cd examples/native
$ ../../gradlew linkDebugExecutableLinuxX64
$ ./build/bin/linuxX64/debugExecutable/native.kexe

📖 Usage

👶 Basics

The following is equivalent to println("Hello, World"). In this simple case, it's definitely overkill!

session {
  section { textLine("Hello, World") }.run()
}

section { ... } defines a Section which, on its own, is inert. It needs to be run to output text to the console. Above, we use the run method to trigger this. The method blocks until the render (i.e. text printing to the console) is finished (which, in the above case, will be almost instant).

session { ... } sets the outer scope for your whole program. While we're just calling it with default arguments here, you can also pass in parameters that apply to the entire application.

While the above simple case is a bit verbose for what it's doing, Kotter starts to show its strength when doing background work (or other async tasks like waiting for user input) during which time the section block may render several times. We'll see many examples throughout this document later.

A Kotter session can contain one or more sections. Your own app may only ever contain a single section and that's fine! But if you have multiple sections, it will feel to the user like your app has a current, active area, following a history of text paragraphs from previous interactions that no longer change.

🎨 Text Effects

You can call color methods directly, which remain in effect until the next color method is called:

section {
  green(layer = BG)
  red() // defaults to FG layer if no layer specified
  textLine("Red on green")
  blue()
  textLine("Blue on green")
}.run()

![Code sample in action](https://github.com/varabyte/media/raw/main/kotter/images/kotter-text-ex-

View on GitHub
GitHub Stars679
CategoryContent
Updated15h ago
Forks22

Languages

Kotlin

Security Score

100/100

Audited on Apr 1, 2026

No findings