Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Repository files navigation

ktsx

A lightweight Kotlin Multiplatform UI framework that lets you write declarative UI in .ktsx files — a JSX-inspired syntax that compiles to idiomatic Kotlin DSL code at build time.

<Column>
  <Text value="Hello, world!" />
  <Button if={isLoggedIn} onClick={handleLogout}>
    <Text value="Log out" />
  </Button>
  <Button else onClick={handleLogin}>
    <Text value="Log in" />
  </Button>
</Column>

↓ generated at build time ↓

fun MyScreen() = buildUi {
    Column {
        Text(value = "Hello, world!")
        if (isLoggedIn) {
            Button(onClick = handleLogout) {
                Text(value = "Log out")
            }
        } else {
            Button(onClick = handleLogin) {
                Text(value = "Log in")
            }
        }
    }
}

Modules

Artifact Description
io.github.ktsx:ktsx-core:0.1.0 Runtime — UIElement, UiBuilder, ComponentRegistry, Modifier, AnimationSpec, built-in components
io.github.ktsx:ktsx-processor:0.1.0 Build-time — .ktsx parser and Kotlin code generator
io.github.ktsx:ktsx-gradle-plugin:0.1.0 Gradle plugin — wires .ktsx.kt generation into your build

Installation

1. Apply the Gradle plugin

In your module's build.gradle.kts:

plugins {
    id("com.ktsx.ui") version "0.1.0"
}

The plugin automatically:

  • Discovers all .ktsx files in your source sets
  • Generates a .kt file for each one at build time
  • Adds the generated sources to your compile classpath

2. Add the runtime dependency

dependencies {
    implementation("io.github.ktsx:ktsx-core:0.1.0")
}

3. Configure (optional)

ktsx {
    // Override the default package derived from the file path
    defaultPackage.set("com.example.ui")

    // Strip the comment header from generated files (default: true)
    generateCommentHeader.set(false)
}

Repository

Artifacts are published to Maven Central. Until then, to use a local build:

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        mavenLocal()
        mavenCentral()
    }
}

Then publish locally:

./gradlew :core:publishToMavenLocal
./gradlew :processor:publishToMavenLocal
./gradlew :gradle-plugin:publishToMavenLocal

Writing .ktsx files

Place .ktsx files anywhere in your source set (alongside .kt files). Each file generates one top-level buildUi { } function named after the file.

src/main/ktsx/HomeScreen.ktsx → generates fun HomeScreen() = buildUi { … } in the same package.

Built-in components

Component Description
Column Vertical container
Row Horizontal container
Text Text node
Button Tappable button
Show Always-mounted visibility toggle
AnimatedVisibility Animated mount/unmount
AnimatedContent Cross-animate between states
Crossfade Opacity crossfade shorthand

Attribute values

<Text value="string literal" />
<Button onClick={kotlinExpression} enabled={isReady} />

String literals use "double quotes". Any Kotlin expression goes inside {curly braces} and is emitted verbatim into the generated code.


Directives

Directives are special attributes that control code generation. They are stripped from the node before it is rendered.

if / else-if / else

Consecutive siblings form an if/else if/else block:

<Button if={isLoggedIn} onClick={logout}>
  <Text value="Log out" />
</Button>
<Text else-if={isPending} value="Loading…" />
<Button else onClick={login}>
  <Text value="Log in" />
</Button>
if (isLoggedIn) {
    Button(onClick = logout) { Text(value = "Log out") }
} else if (isPending) {
    Text(value = "Loading…")
} else {
    Button(onClick = login) { Text(value = "Log in") }
}

show

Keeps the node in the tree; the platform renderer hides it without unmounting. Unlike if, there is no enter/exit animation — use AnimatedVisibility for that.

<Row show={isConnected}>
  <Text value="Online" />
</Row>
Show(visible = isConnected) {
    Row { Text(value = "Online") }
}

for

Repeats the node for every element in a collection. The value is any valid Kotlin for-expression:

<Card for={item in posts}>
  <Text value={item.title} />
</Card>

<Row for={(index, item) in tabs.withIndex()}>
  <Text value={item.label} />
</Row>
for (item in posts) {
    Card { Text(value = item.title) }
}

for ((index, item) in tabs.withIndex()) {
    Row { Text(value = item.label) }
}

Combining directives

Directives can be combined on the same tag. Evaluation order: for wraps if/else, which wraps show:

<Text for={item in notifications} if={item.unread} value={item.title} />
for (item in notifications) {
    if (item.unread) {
        Text(value = item.title)
    }
}

Modifier (styling)

All built-in components accept a modifier parameter for layout and visual styling. Modifiers are immutable and chain with .:

<Column modifier={Modifier.padding(16).background("#FAFAFA").fillMaxWidth()}>
  <Text value="Styled" modifier={Modifier.alpha(0.7f)} />
</Column>

Available entries: padding, margin, background, size, width, height, fillMaxWidth, fillMaxHeight, fillMaxSize, border, alpha, shadow, clipCircle, clipRounded, rotate, scale, custom.


Animations

AnimatedVisibility

<AnimatedVisibility
  visible={isVisible}
  enterSpec={AnimationSpec.Fade(durationMs=300)}
  exitSpec={AnimationSpec.Slide(direction=AnimationSpec.SlideDirection.BOTTOM_TO_TOP, durationMs=250)}
>
  <Card><Text value="Appeared!" /></Card>
</AnimatedVisibility>

AnimatedContent

Cross-animates between different states:

<AnimatedContent targetState={currentPage} transitionSpec={AnimationSpec.Tween(durationMs=250)}>
  <HomePage />
</AnimatedContent>

Crossfade

Shorthand for opacity crossfade:

<Crossfade targetState={isLoading} durationMs={300}>
  <Spinner if={isLoading} />
  <Content else />
</Crossfade>

AnimationSpec types

Spec Description
Fade(durationMs) Opacity only
Tween(durationMs, easing, delayMs) Duration-based with easing
Spring(stiffness, dampingRatio) Physics-based (no fixed duration)
Slide(direction, durationMs) Slide in/out
Scale(fromScale, toScale, durationMs) Scale in/out
Composite(vararg specs) Multiple specs simultaneously

Custom components

Register third-party or app-specific components with ComponentRegistry at startup:

ComponentRegistry.register("Avatar") { attrs, content ->
    // attrs["src"], attrs["size"], etc.
    add(UINode("Avatar", attrs, content?.let { buildUi(it) } ?: emptyList()))
}

Then use them in .ktsx files like any built-in component:

<Avatar src={user.avatarUrl} size={40} />

Building from source

git clone https://github.com/ktsx/ktsx.git
cd ktsx

# Run all tests
./gradlew test

# Publish to local Maven repository
./gradlew publishToMavenLocal

# Run the example
./gradlew :example:run

Requires JDK 17+, Kotlin 2.2.20, Android Gradle Plugin 8.13.0.


License

Apache 2.0

About

A lightweight Kotlin Multiplatform UI framework that lets you write declarative UI in .ktsx files — a JSX-inspired syntax that compiles to idiomatic Kotlin DSL code at build time.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages