Lightweight JavaFX Framework for Kotlin
Kotlin Other

TornadoFX Logo


Lightweight JavaFX Framework for Kotlin

Travis CI Maven Central Apache License


  • Supports both MVC, MVP and their derivatives
  • Dependency injection
  • Type safe GUI builders
  • Type safe CSS builders
  • First class FXML support
  • Async task execution
  • EventBus with thread targeting
  • Hot reload of Views and Stylesheets
  • OSGi support
  • REST client with automatic JSON conversion
  • Zero config, no XML, no annotations

Getting started

Generate a quickstart application with Maven

mvn archetype:generate -DarchetypeGroupId=no.tornado \
  -DarchetypeArtifactId=tornadofx-quickstart-archetype \

Add TornadoFX to your project




compile 'no.tornado:tornadofx:1.5.9'

What does it look like? (Code snippets)

Create a View

class HelloWorld : View() {
    override val root = hbox {
        label("Hello world")

Load the root node from HelloWorld.fxml and inject controls by fx:id

class HelloWorld : View() {
    override val root: HBox by fxml()
    val myLabel: Label by fxid()

    init {
        myLabel.text = "Hello world"

Start your application and show the primary View and add a type safe stylesheet

class HelloWorldApp : App(HelloWorld::class, Styles::class)

class Styles : Stylesheet() {
    init {
        label {
            fontSize = 20.px
            fontWeight = FontWeight.BOLD
            backgroundColor += c("#cecece")

Start app and load a type safe stylesheet

Use Type Safe Builders to quickly create complex user interfaces

class MyView : View() {
    private val persons = FXCollections.observableArrayList(
            Person(1, "Samantha Stuart", LocalDate.of(1981,12,4)),
            Person(2, "Tom Marks", LocalDate.of(2001,1,23)),
            Person(3, "Stuart Gills", LocalDate.of(1989,5,23)),
            Person(3, "Nicole Williams", LocalDate.of(1998,8,11))

    override val root = tableview(persons) {
        column("ID", Person::id)
        column("Name", Person::name)
        column("Birthday", Person::birthday)
        column("Age", Person::age)
        columnResizePolicy = SmartResize.POLICY


Create a Customer model object that can be converted to and from JSON and exposes both a JavaFX Property and getter/setter pairs:

import tornadofx.getValue
import tornadofx.setValue

class Customer : JsonModel {
    val idProperty = SimpleIntegerProperty()
    var id by idProperty

    val nameProperty = SimpleStringProperty()
    var name by nameProperty

    override fun updateModel(json: JsonObject) {
        with(json) {
            id = int("id")
            name = string("name")

    override fun toJSON(json: JsonBuilder) {
        with(json) {
            add("id", id)
            add("name", name)

Create a controller which downloads a JSON list of customers with the REST api:

class HelloWorldController : Controller() {
    val api : Rest by inject()

    fun loadCustomers(): ObservableList<Customer> = 

Configure the REST API with a base URI and Basic Authentication:

with (api) {
    baseURI = ""
    setBasicAuth("user", "secret")

Load customers in the background and update a TableView on the UI thread:

runAsync {
} ui {
    customerTable.items = it

Load customers and apply to table declaratively:

customerTable.asyncItems { controller.loadCustomers() }

Define a type safe CSS stylesheet:

class Styles : Stylesheet() {
    companion object {
        // Define css classes
        val heading by cssclass()

        // Define colors
        val mainColor = c("#bdbd22")

    init {
        heading {
            textFill = mainColor
            fontSize = 20.px
            fontWeight = BOLD

        button {
            padding = box(10.px, 20.px)
            fontWeight = BOLD

        val flat = mixin {
            backgroundInsets += box(0.px)
            borderColor += box(Color.DARKGRAY)

        s(button, textInput) {

Create an HBox with a Label and a TextField with type safe builders:

hbox {
    label("Hello world") {

    textfield {
        promptText = "Enter your name"

Get and set per component configuration settings:

// set prefWidth from setting or default to 200.0
node.prefWidth(config.double("width", 200.0))

// set username and age, then save
with (config) {
    set("username", "john")
    set("age", 30)

Create a Fragment instead of a View. A Fragment is not a Singleton like View is, so you will create a new instance and you can reuse the Fragment in multiple ui locations simultaneously.

class MyFragment : Fragment() {
    override val root = hbox {

Open it in a Modal Window:


Lookup and embed a View inside another Pane in one go

root += MyFragment::class

Inject a View and embed inside another Pane

val myView: MyView by inject()

init {
    root += myFragment

Swap a View for another (change Scene root or embedded View)

button("Go to next page") {
    setOnAction {
        replaceWith(PageTwo::class, ViewTransition.Slide(0.3.seconds, Direction.LEFT)

Open a View in an internal window over the current scene graph

button("Open") {
    setOnAction {