Getting Started

Set up a Kraft project and build your first component.

The Gradle setup is the hardest part. Once that compiles, everything else follows naturally.

Prerequisites

  • JDK 17+ — Kraft compiles Kotlin to JavaScript, but the build runs on the JVM
  • Gradle — the build system (wrapper included in most projects)

1. Set up your Gradle project

In your build.gradle.kts:

plugins {
    kotlin("multiplatform") version "2.3.10"
    kotlin("plugin.serialization") version "2.3.10"
}

kotlin {
    js {
        browser {
            testTask { }
        }
        binaries.executable()
    }

    sourceSets {
        jsMain {
            dependencies {
                // Kraft core
                implementation("io.peekandpoke.kraft:core:0.107.2")

                // SemanticUI integration (optional but recommended)
                implementation("io.peekandpoke.kraft:semanticui:0.107.2")
            }
        }
    }
}

2. Create your entry point

Create src/jsMain/resources/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>My Kraft App</title>
    <link rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.8/dist/semantic.min.css">
</head>
<body>
    <div id="app"></div>
    <script src="your-project-name.js"></script>
</body>
</html>

3. Write your first app

Create src/jsMain/kotlin/main.kt:

import io.peekandpoke.kraft.components.*
import io.peekandpoke.kraft.routing.Static
import io.peekandpoke.kraft.vdom.VDom
import io.peekandpoke.kraft.vdom.preact.PreactVDomEngine
import io.peekandpoke.ultra.semanticui.ui
import kotlinx.html.*

// Define the app and its routes
val app = kraftApp {
    semanticUI()
    routing {
        mount(Static("")) { HomePage() }
    }
}

// Entry point
fun main() {
    app.mount("#app", PreactVDomEngine()) {
        RouterComponent()
    }
}

4. Create your first component

Create src/jsMain/kotlin/HomePage.kt:

import io.peekandpoke.kraft.components.*
import io.peekandpoke.kraft.vdom.VDom
import io.peekandpoke.ultra.semanticui.ui
import kotlinx.html.*

// Factory function — this is how other components use yours
@Suppress("FunctionName")
fun Tag.HomePage() = comp {
    HomePage(it)
}

// The component class
class HomePage(ctx: NoProps) : PureComponent(ctx) {

    // Reactive state — changing this redraws the component
    private var count by value(0)

    override fun VDom.render() {
        ui.container {
            ui.segment {
                ui.header H1 { +"Hello from Kraft!" }

                p { +"You clicked the button $count times." }

                ui.blue.button {
                    onClick { count++ }
                    +"Click me"
                }
            }
        }
    }
}

5. Run it

./gradlew -t --parallel jsRun

The -t flag enables continuous build — save a file and the browser refreshes.

What just happened?

Let's break down the patterns you just used:

The factory function pattern

@Suppress("FunctionName")
fun Tag.HomePage() = comp {
    HomePage(it)
}

Every component has a factory function — an extension on Tag that creates the component via comp { }. This is how you compose components in the HTML DSL:

override fun VDom.render() {
    div {
        HomePage()           // Just call the factory function
        Counter(start = 5)   // With props
    }
}

Reactive state with value()

private var count by value(0)

value() creates a delegated property that automatically triggers a redraw when changed. No setState(), no actions, no dispatchers — just assign a new value.

The HTML DSL

ui.container {
    ui.segment {
        ui.header H1 { +"Hello" }
    }
}

This is kotlinx.html with the SemanticUI DSL layered on top. Standard HTML tags (div, p, button) work alongside SemanticUI helpers (ui.segment, ui.button).

App configuration

The kraftApp builder is where you configure routing, UI frameworks, and app-level services. Here's a complete setup:

val app = kraftApp {
    // Registers modals, toasts, popups with SemanticUI styling
    semanticUI {
        // Optional: customize toast defaults
        defaultDuration = 5.seconds
    }

    // Set up routing
    routing {
        usePathStrategy()  // clean URLs: /users/123
        // or useHashStrategy() for: /#/users/123

        mount(Nav.home) { HomePage() }
        mount(Nav.users) { route -> UsersPage(route["id"]) }

        catchAll { NotFoundPage() }
    }
}

fun main() {
    app.mount("#spa", PreactVDomEngine()) {
        App()  // your root component
    }
}

The semanticUI() call registers modals, toasts, and popups with FomanticUI styling. If you want the core overlay systems without SemanticUI, register them individually with modals(), toasts(), and popups().