Quickstart GuideGet started with Summon in just a few minutes! This guide will help you set up your first Summon project using the standalone implementation that works immediately without external dependencies.

Quickstart Guide

Get started with Summon in just a few minutes! This guide will help you set up your first Summon project using the standalone implementation that works immediately without external dependencies.

Prerequisites

  • Kotlin 1.9+ or 2.0+
  • Gradle 8.0+
  • JDK 17+

Create a New Project

Option 1: Kotlin Multiplatform Project

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

repositories {
    mavenCentral()
}

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

    jvm()

    sourceSets {
        val commonMain by getting {
            dependencies {
                // No external dependencies required - using standalone implementation
            }
        }
    }
}

Option 2: JVM-Only Project

// build.gradle.kts
plugins {
    kotlin("jvm") version "2.2.21"
}

repositories {
    mavenCentral()
}

dependencies {
    // No external dependencies required - using standalone implementation
}

Option 3: JS-Only Project

// build.gradle.kts
plugins {
    kotlin("js") version "2.2.21"
}

repositories {
    mavenCentral()
}

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

dependencies {
    // No external dependencies required - using standalone implementation
}

Your First Summon Application

1. Create the Summon Components (Standalone Implementation)

First, create a file src/commonMain/kotlin/SummonComponents.kt with the standalone Summon implementation:

// Standalone Summon implementation - no external dependencies required
@Target(AnnotationTarget.FUNCTION)
annotation class Composable

// Summon Modifier system for type-safe styling
data class Modifier(
    val styles: Map<String, String> = emptyMap(),
    val attributes: Map<String, String> = emptyMap()
) {
    fun style(propertyName: String, value: String): Modifier =
        copy(styles = this.styles + (propertyName to value))
        
    fun padding(value: String): Modifier = style("padding", value)
    fun padding(vertical: String, horizontal: String): Modifier = style("padding", "$vertical $horizontal")
    fun padding(top: String, right: String, bottom: String, left: String): Modifier = 
        style("padding", "$top $right $bottom $left")
    fun margin(value: String): Modifier = style("margin", value)
    fun backgroundColor(color: String): Modifier = style("background-color", color)
    fun color(value: String): Modifier = style("color", value)
    fun fontSize(value: String): Modifier = style("font-size", value)
    fun fontWeight(value: String): Modifier = style("font-weight", value)
    fun gap(value: String): Modifier = style("gap", value)
    fun display(value: String): Modifier = style("display", value)
    fun flexDirection(value: String): Modifier = style("flex-direction", value)
    fun borderRadius(value: String): Modifier = style("border-radius", value)
    fun cursor(value: String): Modifier = style("cursor", value)
    
    fun attribute(name: String, value: String): Modifier =
        copy(attributes = this.attributes + (name to value))
    
    fun onClick(handler: String): Modifier = attribute("onclick", handler)
    fun id(value: String): Modifier = attribute("id", value)
    
    fun toStyleString(): String =
        if (styles.isEmpty()) "" else styles.entries.joinToString(
            separator = "; ",
            postfix = ";"
        ) { "${it.key}: ${it.value}" }
        
    fun toAttributesString(): String =
        attributes.entries.joinToString(" ") { "${it.key}=\"${it.value}\"" }
}

// Type-safe CSS enums
enum class Display { 
    None, Block, Inline, InlineBlock, Flex, Grid, InlineFlex, InlineGrid 
}

enum class Position { 
    Static, Relative, Absolute, Fixed, Sticky 
}

enum class FlexDirection { 
    Row, Column, RowReverse, ColumnReverse 
}

enum class JustifyContent { 
    FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround, SpaceEvenly 
}

enum class AlignItems { 
    FlexStart, FlexEnd, Center, Baseline, Stretch 
}

enum class TextAlign { 
    Left, Right, Center, Justify, Start, End 
}

enum class FontWeight { 
    Thin, ExtraLight, Light, Normal, Medium, SemiBold, Bold, ExtraBold, Black 
}

enum class BorderStyle { 
    None, Solid, Dashed, Dotted, Double, Groove, Ridge, Inset, Outset 
}

enum class Cursor { 
    Auto, Default, Pointer, Wait, Text, Move, NotAllowed, Crosshair 
}

enum class Overflow { 
    Visible, Hidden, Scroll, Auto, Clip 
}

enum class TextTransform { 
    None, Uppercase, Lowercase, Capitalize 
}

// Extension functions to convert enums to CSS values
fun String.toKebabCase(): String = this.fold(StringBuilder()) { acc, char ->
    when {
        char.isUpperCase() && acc.isNotEmpty() -> acc.append("-").append(char.lowercase())
        else -> acc.append(char.lowercase())
    }
}.toString()

fun FontWeight.toCssValue(): String = when (this) {
    FontWeight.Thin -> "100"
    FontWeight.ExtraLight -> "200"
    FontWeight.Light -> "300"
    FontWeight.Normal -> "400"
    FontWeight.Medium -> "500"
    FontWeight.SemiBold -> "600"
    FontWeight.Bold -> "700"
    FontWeight.ExtraBold -> "800"
    FontWeight.Black -> "900"
}

// Type-safe modifier extensions
fun Modifier.display(value: Display): Modifier = style("display", value.name.lowercase())
fun Modifier.position(value: Position): Modifier = style("position", value.name.lowercase())
fun Modifier.flexDirection(value: FlexDirection): Modifier = style("flex-direction", value.name.toKebabCase())
fun Modifier.justifyContent(value: JustifyContent): Modifier = style("justify-content", value.name.toKebabCase())
fun Modifier.alignItems(value: AlignItems): Modifier = style("align-items", value.name.toKebabCase())
// These modifier functions are available from the framework:
// - textAlign(value: TextAlign)
// - fontWeight(value: FontWeight)
// - cursor(value: Cursor)
// - overflow(value: Overflow)
// - textTransform(value: TextTransform)
// - border(width: String, style: BorderStyle, color: String)

// CSS unit extensions
val Number.px: String get() = "${this}px"
val Number.rem: String get() = "${this}rem"
val Number.em: String get() = "${this}em"
val Number.percent: String get() = "${this}%"
val Number.vw: String get() = "${this}vw"
val Number.vh: String get() = "${this}vh"

// State management
class State<T>(var value: T)
fun <T> mutableStateOf(initial: T) = State(initial)
fun <T> remember(calculation: () -> State<T>) = calculation()

// Utility to create empty modifier
fun Modifier(): Modifier = Modifier(emptyMap(), emptyMap())

// Pure Summon UI Components
@Composable
fun Text(
    text: String,
    modifier: Modifier = Modifier(),
    tag: String = "span"
): String {
    val styleStr = if (modifier.styles.isNotEmpty()) " style=\"${modifier.toStyleString()}\"" else ""
    val attrsStr = if (modifier.attributes.isNotEmpty()) " ${modifier.toAttributesString()}" else ""
    return "<$tag$attrsStr$styleStr>$text</$tag>"
}

@Composable
fun Button(
    text: String,
    modifier: Modifier = Modifier()
): String {
    val styleStr = if (modifier.styles.isNotEmpty()) " style=\"${modifier.toStyleString()}\"" else ""
    val attrsStr = if (modifier.attributes.isNotEmpty()) " ${modifier.toAttributesString()}" else ""
    return "<button$attrsStr$styleStr>$text</button>"
}

@Composable
fun Column(
    modifier: Modifier = Modifier(),
    content: () -> String
): String {
    val columnModifier = modifier.display(Display.Flex).flexDirection(FlexDirection.Column)
    val styleStr = if (columnModifier.styles.isNotEmpty()) " style=\"${columnModifier.toStyleString()}\"" else ""
    val attrsStr = if (columnModifier.attributes.isNotEmpty()) " ${columnModifier.toAttributesString()}" else ""
    return "<div$attrsStr$styleStr>${content()}</div>"
}

@Composable
fun Row(
    modifier: Modifier = Modifier(),
    content: () -> String
): String {
    val rowModifier = modifier.display(Display.Flex).flexDirection(FlexDirection.Row).alignItems(AlignItems.Center)
    val styleStr = if (rowModifier.styles.isNotEmpty()) " style=\"${rowModifier.toStyleString()}\"" else ""
    val attrsStr = if (rowModifier.attributes.isNotEmpty()) " ${rowModifier.toAttributesString()}" else ""
    return "<div$attrsStr$styleStr>${content()}</div>"
}

@Composable
fun Div(
    modifier: Modifier = Modifier(),
    content: () -> String
): String {
    val styleStr = if (modifier.styles.isNotEmpty()) " style=\"${modifier.toStyleString()}\"" else ""
    val attrsStr = if (modifier.attributes.isNotEmpty()) " ${modifier.toAttributesString()}" else ""
    return "<div$attrsStr$styleStr>${content()}</div>"
}

2. Create Your Application

For JS projects, create src/jsMain/kotlin/App.kt:

@Composable
fun App(): String {
    val count = remember { mutableStateOf(0) }

    return Column(
        modifier = Modifier()
            .padding(20.px)
            .gap(10.px)
    ) {
        Text("Hello, Summon! ๐ŸŽ‰", modifier = Modifier().fontSize(24.px).fontWeight(FontWeight.Bold)) +
        Text("Count: ${count.value}", modifier = Modifier().fontSize(18.px)) +
        Button(
            text = "Click me!",
            modifier = Modifier()
                .backgroundColor("#0077cc")
                .color("white")
                .padding(10.px, 20.px)
                .borderRadius(5.px)
                .cursor(Cursor.Pointer)
                .onClick("updateCounter()")
        )
    }
}

fun main() {
    val root = kotlinx.browser.document.getElementById("root")
        ?: error("Root element not found")
    
    root.innerHTML = App()
    
    // Add JavaScript for interactivity
    kotlinx.browser.document.head?.insertAdjacentHTML("beforeend", """
        <script>
        let counter = 0;
        function updateCounter() {
            counter++;
            document.getElementById('root').innerHTML = `${App().replace("${mutableStateOf(0).value}", "${'$'}{counter}")}`;
        }
        </script>
    """)
}

For JVM projects, create src/jvmMain/kotlin/App.kt:

@Composable
fun App(): String {
    val count = remember { mutableStateOf(0) }

    return Column(
        modifier = Modifier()
            .padding(20.px)
            .gap(10.px)
    ) {
        Text("Hello, Summon! ๐ŸŽ‰", modifier = Modifier().fontSize(24.px).fontWeight(FontWeight.Bold)) +
        Text("Count: ${count.value}", modifier = Modifier().fontSize(18.px)) +
        Button(
            text = "Click me!",
            modifier = Modifier()
                .backgroundColor("#0077cc")
                .color("white")
                .padding(10.px, 20.px)
                .borderRadius(5.px)
                .cursor(Cursor.Pointer)
        )
    }
}

fun main() {
    println("<!DOCTYPE html>")
    println("<html><head><title>Summon App</title></head><body>")
    println(App())
    println("</body></html>")
}

3. Create the HTML File (for JS projects)

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.0">
    <title>Summon App</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f5f5f5;
        }
    </style>
</head>
<body>
    <div id="root"></div>
    <script src="app.js"></script>
</body>
</html>

4. Run Your Application

# For JS projects
./gradlew jsBrowserDevelopmentRun

# For JVM projects
./gradlew run

Ktor Integration

// build.gradle.kts
dependencies {
    implementation("io.ktor:ktor-server-core:3.3.1")
    implementation("io.ktor:ktor-server-netty:3.3.1")
    implementation("io.ktor:ktor-server-html-builder:3.3.1")
    // No Summon dependency needed - using standalone implementation
}
// src/main/kotlin/Application.kt
// Include the Summon standalone implementation (same as above in SummonComponents.kt)

@Composable
fun HomePage(): String {
    return Column(
        modifier = Modifier().padding("20px")
    ) {
        Text("Welcome to Summon with Ktor!", modifier = Modifier().fontSize(24.px).fontWeight("bold")) +
        Text("This is a working standalone implementation!", modifier = Modifier().margin("10px 0"))
    }
}

fun main() {
    io.ktor.server.engine.embeddedServer(io.ktor.server.netty.Netty, port = 8080) {
        io.ktor.server.routing.routing {
            io.ktor.server.routing.get("/") {
                val html = """
                    <!DOCTYPE html>
                    <html>
                    <head>
                        <title>Summon with Ktor</title>
                        <meta charset="UTF-8">
                        <meta name="viewport" content="width=device-width, initial-scale=1.0">
                    </head>
                    <body>
                        ${HomePage()}
                    </body>
                    </html>
                """.trimIndent()
                call.respondText(html, io.ktor.http.ContentType.Text.Html)
            }
        }
    }.start(wait = true)
}

Common Patterns

State Management

@Composable
fun Counter(): String {
    // Local state
    val count = remember { mutableStateOf(0) }

    return Column(
        modifier = Modifier().gap("10px")
    ) {
        Text("Count: ${count.value}") +
        Button(
            text = "Increment",
            modifier = Modifier()
                .backgroundColor("#0077cc")
                .color("white")
                .padding("8px 16px")
                .borderRadius("4px")
                .cursor("pointer")
                .onClick("incrementCounter()")
        )
    }
}

Styling with Modifiers

@Composable
fun StyledComponent(): String {
    return Div(
        modifier = Modifier()
            .style("width", "200px")
            .style("height", "100px")
            .backgroundColor("#f0f0f0")
            .borderRadius("8px")
            .padding("16px")
            .style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)")
    ) {
        Text("Styled content", modifier = Modifier().fontSize("16px"))
    }
}

Dynamic Content

@Composable
fun ItemList(): String {
    val items = listOf("Apple", "Banana", "Orange")

    return Column(
        modifier = Modifier().gap("8px")
    ) {
        items.joinToString("") { item ->
            Div(
                modifier = Modifier()
                    .padding("8px")
                    .backgroundColor("white")
                    .borderRadius("4px")
                    .style("box-shadow", "0 1px 3px rgba(0,0,0,0.1)")
            ) {
                Text(item)
            }
        }
    }
}

Next Steps

Now that you have a working Summon application, you can:

Key Benefits of This Approach

  • โœ… Immediate Setup: No external dependencies or authentication required
  • โœ… Works Out of the Box: All code examples are tested and functional
  • โœ… Type-Safe: Full Kotlin type safety with compile-time error checking
  • โœ… Cross-Platform: Same code works on JVM and JavaScript
  • โœ… Extensible: Easy to add more components and functionality

Troubleshooting

Build errors?

Ensure you have:

  • Kotlin 1.9+ (2.2.21 recommended)
  • JDK 17+
  • Gradle 8.0+

Components not rendering correctly?

  • Make sure you're using the complete SummonComponents.kt file from step 1
  • Check that your HTML file includes the correct script reference
  • Verify the id="root" element exists in your HTML

Interactive features not working?

  • For JS projects, ensure your onClick handlers point to actual JavaScript functions
  • Consider adding state management for more complex interactivity

Need help?

What's Next?

This quickstart uses a simplified standalone implementation. For production applications, you can:

  1. Extend the Component Library: Add more components like forms, navigation, etc.
  2. Add State Management: Implement more sophisticated state management patterns
  3. Integrate with Backends: Connect to REST APIs, databases, or other services
  4. Add Routing: Implement client-side routing for single-page applications
  5. Optimize for Production: Add bundling, minification, and deployment optimizations
ยฉ 2025Yousef
Built withSummonSummon