The AI-native Compose layout for token-maxxers.
Describe the layout you want in plain English. Laygent writes the algorithm at runtime via an LLM, validates it against your prompt, and caches the result so your frames stay fast.
val config = LaygentConfig.defaultOpenAi(openAiApiKey = myOpenAiKey)
@Composable
fun Example() {
val state = rememberLaygentState(config) {
"Arrange items in a 3-column grid with 8 dp of spacing between them."
}
Laygent(state) {
repeat(12) {
Box(Modifier.size(50.dp).background(Color.Blue))
}
}
}
Why Laygent
A drop-in Layout for Compose that you program with prose.
Prompt → layout
Skip the math. Describe alignment, spacing, wrapping, conditional behavior — Laygent generates the measure/place algorithm to match.
Self-validating
An evaluator model checks the produced layout against your prompt and feeds errors back to the codegen model until it’s right.
Cached & fast
The validated JS algorithm is reused on every frame and survives configuration changes via rememberSaveable.
Multiplatform-ready
Built on Compose’s Layout + intrinsic measurement APIs. Works wherever Compose Desktop / Multiplatform runs.
Tag-aware
Attach prose-friendly labels via Modifier.laygentTag() and reference them in your prompt to drive per-item rules.
Bring your own model
Powered by Koog. Mix providers and models per stage — fast for codegen, strong for evaluation.
How it works
Laygent slots into Compose’s layout pipeline. The model writes JavaScript that drives the same measure/place primitives you already know.
You describe
Pass a prompt to rememberLaygentState. Read any state you like inside the prompt lambda — changes trigger a regen, not a recomposition.
The model generates
The codegen LLM emits a doLayout() JavaScript function with access to a Compose-shaped layout API: measurables, constraints, intrinsics, measure, place, setSize.
The runtime validates
QuickJS executes the code under real constraints. Errors round-trip back to the model. An evaluator LLM then judges whether the output matches your prompt.
Compose runs it
The validated algorithm becomes the MeasurePolicy for a normal Compose Layout. Subsequent frames just execute it — no LLM round-trip.
Quickstart
Add the dependency, hand Laygent an OpenAI key (or any Koog-supported model), and use it like any other Compose layout. Laygent is published to Maven Central.
repositories {
mavenCentral()
}
dependencies {
implementation("com.zachklipp:laygent:0.1.0")
implementation(compose.desktop.currentOs)
}
val config = LaygentConfig.defaultOpenAi(openAiApiKey = BuildConfig.OPENAI_API_KEY)
@Composable
fun Gallery(photos: List<Photo>) {
val state = rememberLaygentState(config) {
"Arrange items in a 3-column grid with 8 dp of spacing between them."
}
Laygent(state) {
photos.forEach { photo ->
AsyncImage(
model = photo.url,
contentDescription = null,
modifier = Modifier
.size(120.dp)
.laygentTag(photo.kind), // optional: prompt-addressable
)
}
}
}
Mixing providers per stage
Want a cheap model writing code and a strong model validating it? Compose two LlmConfigs.
val openAi = OpenAILLMClient(apiKey = openAiKey)
val config = LaygentConfig(
codeGenLlm = LlmConfig(client = openAi, model = OpenAIModels.Chat.GPT5_4Mini),
evalLlm = LlmConfig(client = openAi, model = OpenAIModels.Chat.GPT5_4),
)
Tracking spend
Every token used during generation and evaluation is reported through onTokensUsed.
var tokensUsed by remember { mutableLongStateOf(0L) }
val state = rememberLaygentState(
config = config,
layoutPrompt = { prompt },
onTokensUsed = { tokensUsed += it },
)
Public API
A small surface — most of the magic happens inside the runtime.
| Symbol | What it does |
|---|---|
Laygent(state, modifier, content) |
Composable that lays out content using the algorithm currently in state. Drop-in for any other Compose layout. |
rememberLaygentState { prompt } |
Holds the validated algorithm. State read inside the prompt lambda triggers regeneration, not recomposition. Survives recreation via rememberSaveable. |
LaygentConfig |
Bundles the codegen and evaluation LLM configs. Use LaygentConfig.defaultOpenAi(apiKey) for sane defaults. |
LlmConfig(client, model, params) |
Pairs a Koog LLMClient with the LLModel and LLMParams to invoke it under. |
LocalLaygentConfig |
Composition local for providing a default LaygentConfig to a subtree. |
LaygentStatus |
GeneratingLayout, ValidatingLayout, or Idle. Read state.status to drive a spinner / skeleton UI. |
state.forceUpdateLayout(prompt) |
Suspending. Re-runs codegen even if the prompt hasn’t changed (e.g. for a “regenerate” button). |
Modifier.laygentTag(tag) |
Attaches a string tag to a child. The model sees these tags and can reference them in its algorithm. |
Prompts that work
Pulled from the bundled demo. Anything you can describe with concrete rules — sizes, spacing, conditions, tag-based dispatch — Laygent can usually realize.
Try it in your browser
An interactive playground for editing prompts, swapping models, randomizing items, and watching token spend — running right here via Compose Multiplatform on wasmJs. Paste an OpenAI API key to drive the LLM.
Honest caveats
- The first frame of any new prompt costs at least one round-trip to your model provider. Cache hits after that.
- Layout code is JavaScript executed in QuickJS — fast, but more overhead than native Kotlin
MeasurePolicycode. - You are sending prompt text and tag strings to your LLM provider. Don’t paste secrets into your prompt.
- Models occasionally need a couple of correction rounds to satisfy weird prompts. Expect more tokens for ambitious instructions.
- This library is a 100% AI-generated proof-of-concept. Treat the API as experimental.