
Cross-target app template sharing UI and core logic with Compose integration, platform-specific entry points, and ready-made build and test tasks including Web/Wasm options.
https://res.cloudinary.com/dgsreulhd/video/upload/v1786301010/main_ui_demo_rcjhan.mp4
Build truly adaptive Kotlin Multiplatform applications using a single shared UI.
KitFlow helps you create responsive layouts across Android, iOS, Desktop and Web with a simple adaptive API.
KitFlow is built around a clean, reusable adaptive layer. The mark reflects that idea: a shared core with responsive motion and layout around it.
Add Maven Central:
repositories {
mavenCentral()
}Add the SDK:
dependencies {
implementation("io.github.vedangj72:kit-flow:1.0.4")
}Use the latest version you have published.
[!IMPORTANT]
Adaptive.layoutValueandAdaptiveFlowGridare source features on this branch. The latest published Maven Central version may not include them yet.
Use AdaptiveKitProvider once near the root of your Compose app.
@Composable
fun App() {
AdaptiveKitProvider {
MainScreen()
}
}Everything inside this provider can use KitFlow APIs.
KitFlow solves the problem of keeping adaptive UI logic readable, reusable, and shared across platforms.
| Feature | What it gives you |
|---|---|
Adaptive.value() |
Responsive values for layout, spacing, typography, and more |
Adaptive.onOrientationChange() |
Clear portrait and landscape branching |
| Adaptive Grid | Constraint-aware grid behavior for reusable components |
| Responsive Padding | Padding that scales with screen class |
| Responsive Typography | Text sizing that stays readable across devices |
| Adaptive Navigation | Layout decisions that fit the current surface |
| Shared Compose UI | One codebase for all supported platforms |
| Android | Native Android support |
| iOS | Native iOS support |
| Desktop | JVM desktop support |
| Web | JS and Wasm support |
https://res.cloudinary.com/dgsreulhd/video/upload/v1786300968/adptive_value_explination_qzdpky.mp4
Every UI value can adapt based on screen size.
Use it for:
val previewPadding = Adaptive.value(
sm = 12.dp,
md = 16.dp,
lg = 20.dp,
tab = 24.dp,
desktop = 28.dp
)
val previewButtonHeight = Adaptive.value(
sm = 40.dp,
md = 44.dp,
lg = 48.dp,
tab = 52.dp,
desktop = 56.dp
)
val previewCardWidth = Adaptive.value(
sm = 220.dp,
md = 260.dp,
lg = 300.dp,
tab = 360.dp,
desktop = 420.dp
)
val previewFontScale = Adaptive.value(
sm = 0.92f,
md = 0.98f,
lg = 1.00f,
tab = 1.06f,
desktop = 1.12f
)Adaptive.value() is not limited to dimensions.
It can adapt
DpSpFloatBooleanIntColorsShapesThis becomes the foundation for creating responsive Compose UIs.
[!NOTE] All adaptive APIs work together to produce a single responsive UI across every supported platform. Use them wisely instead of writing multiple screen implementations.
https://res.cloudinary.com/dgsreulhd/video/upload/v1786300958/orientation_android_olbjfh.mp4
https://res.cloudinary.com/dgsreulhd/video/upload/v1786300967/orientation_ios_qeoiih.mp4
Use Adaptive.onOrientationChange(...) when portrait and landscape should genuinely render different content.
Adaptive.onOrientationChange(
portrait = {
PortraitContent()
},
landscape = {
LandscapeContent()
}
)KitFlow automatically renders the correct UI based on orientation, so the layout decision stays declarative and easy to follow.
Important note
For Desktop and Desktop-class layouts, KitFlow treats the layout as Landscape by default.
Orientation based adaptation is primarily intended for mobile and tablet experiences.
Read current window state with rememberWindowInfo().
val info = rememberWindowInfo()
Text("widthDp: ${info.widthDp}")
Text("heightDp: ${info.heightDp}")
Text("screenClass: ${info.screenClass}")
Text("orientation: ${info.orientation}")Important difference:
screenClass = stable screen class based on shortest side
orientation = Portrait / Landscape / Square / Unknown
This prevents a phone from being treated like a tablet only because it rotated.
Use Adaptive.layoutValue(...) when a value should follow the current available width, including rotation, split screen, and window resizing.
val paneCount = Adaptive.layoutValue(
sm = 1,
md = 1,
lg = 2,
tab = 3,
desktop = 4
)Adaptive.value(...) remains shortest-side based for stable visual values; Adaptive.layoutValue(...) is width based for reflow decisions.
AdaptiveFlowGrid automatically fits equal-width columns inside its actual parent constraints. This makes reusable components respond to phones, rotation, split screen, and resizable windows without device-name checks.
import com.adaptive.kit_flow.layout.AdaptiveFlowGrid
AdaptiveFlowGrid(
minColumnWidth = 160.dp,
maxColumns = 3,
horizontalSpacing = 12.dp,
verticalSpacing = 12.dp
) {
repeat(6) { index ->
Card {
Text("Item $index", Modifier.padding(16.dp))
}
}
}Large system text increases the effective minimum column width by default, so the grid wraps earlier instead of squeezing text. Set fontScaleAware = false only when the content does not contain text.
The grid is eager and is intended for a small group of panels, cards, or controls. Continue using LazyVerticalGrid for long or unbounded collections.
Apply safe-area or window-inset padding outside the grid; the remaining usable width is then handled automatically. Children keep their natural height. If a parent imposes a smaller height, use scrolling, a lazy grid, or opt in to clipOverflow = true when clipped overflow is the intended behavior.
KitFlow helps keep UI stable when font scale increases.
val accessibility = LocalAdaptiveAccessibilityInfo.currentAvailable values:
accessibility.fontScale
accessibility.fontScaleClass
accessibility.minimumTouchTarget
accessibility.reducedMotion
accessibility.highContrast
accessibility.differentiateWithoutColorUse AdaptiveAccessibleLayout when a Row may break with large text.
AdaptiveAccessibleLayout(
normal = {
Row {
Title()
Actions()
}
},
largeText = {
Column {
Title()
Actions()
}
}
)Use adaptiveTouchTarget() for clickable UI.
Modifier
.adaptiveTouchTarget()
.clickable { onClick() }Use adaptiveSemantics() for clear labels and state.
Modifier.adaptiveSemantics(
label = "Download file",
role = Role.Button
)Use AdaptiveIconButton for icon-only actions.
AdaptiveIconButton(
icon = Icons.Default.Close,
contentDescription = "Close",
onClick = onClose
)Icon-only buttons must have meaningful labels.
There are certain things to keep in mind while making layouts or components in Jetpack Compose:
aspectRatio(...), weight(...), fillMaxWidth(), widthIn(...), heightIn(...), and constraints.maxLines, wrapping strategy, and TextOverflow.Ellipsis where needed.LazyColumn or LazyRow for lists.Example with official window size class style:
when (windowSizeClass) {
WindowWidthSizeClass.Compact -> {
Column {
LeftPanel()
RightPanel()
}
}
WindowWidthSizeClass.Medium,
WindowWidthSizeClass.Expanded -> {
Row {
LeftPanel(
modifier = Modifier.weight(1f)
)
RightPanel(
modifier = Modifier.weight(1f)
)
}
}
}With KitFlow, the same idea can be kept inside your SDK-driven layout flow:
val info = rememberWindowInfo()
AdaptiveFlowGrid(
minColumnWidth = 240.dp,
maxColumns = 2,
horizontalSpacing = 16.dp,
verticalSpacing = 16.dp
) {
Card {
LeftPanel(modifier = Modifier.padding(16.dp))
}
Card {
RightPanel(modifier = Modifier.padding(16.dp))
}
}KitFlow does not remove the need to understand Compose layout. It helps you arrange adaptive code in one clear flow.
Keep these official docs close while designing adaptive Compose UI:
KitFlow should resolve adaptive context and adaptive values, not own the UI.
Developers keep using normal Compose components. KitFlow helps those components adapt.
What we expect from contributors:
Contributions should push KitFlow toward a clearer shared SDK experience, not toward platform-specific duplication.
Adaptive.value() stays shortest-side based for stable visual values.Adaptive.layoutValue() follows current width for reflow decisions.AdaptiveFlowGrid is intended for small groups of panels, cards, or controls.https://res.cloudinary.com/dgsreulhd/video/upload/v1786301010/main_ui_demo_rcjhan.mp4
Build truly adaptive Kotlin Multiplatform applications using a single shared UI.
KitFlow helps you create responsive layouts across Android, iOS, Desktop and Web with a simple adaptive API.
KitFlow is built around a clean, reusable adaptive layer. The mark reflects that idea: a shared core with responsive motion and layout around it.
Add Maven Central:
repositories {
mavenCentral()
}Add the SDK:
dependencies {
implementation("io.github.vedangj72:kit-flow:1.0.4")
}Use the latest version you have published.
[!IMPORTANT]
Adaptive.layoutValueandAdaptiveFlowGridare source features on this branch. The latest published Maven Central version may not include them yet.
Use AdaptiveKitProvider once near the root of your Compose app.
@Composable
fun App() {
AdaptiveKitProvider {
MainScreen()
}
}Everything inside this provider can use KitFlow APIs.
KitFlow solves the problem of keeping adaptive UI logic readable, reusable, and shared across platforms.
| Feature | What it gives you |
|---|---|
Adaptive.value() |
Responsive values for layout, spacing, typography, and more |
Adaptive.onOrientationChange() |
Clear portrait and landscape branching |
| Adaptive Grid | Constraint-aware grid behavior for reusable components |
| Responsive Padding | Padding that scales with screen class |
| Responsive Typography | Text sizing that stays readable across devices |
| Adaptive Navigation | Layout decisions that fit the current surface |
| Shared Compose UI | One codebase for all supported platforms |
| Android | Native Android support |
| iOS | Native iOS support |
| Desktop | JVM desktop support |
| Web | JS and Wasm support |
https://res.cloudinary.com/dgsreulhd/video/upload/v1786300968/adptive_value_explination_qzdpky.mp4
Every UI value can adapt based on screen size.
Use it for:
val previewPadding = Adaptive.value(
sm = 12.dp,
md = 16.dp,
lg = 20.dp,
tab = 24.dp,
desktop = 28.dp
)
val previewButtonHeight = Adaptive.value(
sm = 40.dp,
md = 44.dp,
lg = 48.dp,
tab = 52.dp,
desktop = 56.dp
)
val previewCardWidth = Adaptive.value(
sm = 220.dp,
md = 260.dp,
lg = 300.dp,
tab = 360.dp,
desktop = 420.dp
)
val previewFontScale = Adaptive.value(
sm = 0.92f,
md = 0.98f,
lg = 1.00f,
tab = 1.06f,
desktop = 1.12f
)Adaptive.value() is not limited to dimensions.
It can adapt
DpSpFloatBooleanIntColorsShapesThis becomes the foundation for creating responsive Compose UIs.
[!NOTE] All adaptive APIs work together to produce a single responsive UI across every supported platform. Use them wisely instead of writing multiple screen implementations.
https://res.cloudinary.com/dgsreulhd/video/upload/v1786300958/orientation_android_olbjfh.mp4
https://res.cloudinary.com/dgsreulhd/video/upload/v1786300967/orientation_ios_qeoiih.mp4
Use Adaptive.onOrientationChange(...) when portrait and landscape should genuinely render different content.
Adaptive.onOrientationChange(
portrait = {
PortraitContent()
},
landscape = {
LandscapeContent()
}
)KitFlow automatically renders the correct UI based on orientation, so the layout decision stays declarative and easy to follow.
Important note
For Desktop and Desktop-class layouts, KitFlow treats the layout as Landscape by default.
Orientation based adaptation is primarily intended for mobile and tablet experiences.
Read current window state with rememberWindowInfo().
val info = rememberWindowInfo()
Text("widthDp: ${info.widthDp}")
Text("heightDp: ${info.heightDp}")
Text("screenClass: ${info.screenClass}")
Text("orientation: ${info.orientation}")Important difference:
screenClass = stable screen class based on shortest side
orientation = Portrait / Landscape / Square / Unknown
This prevents a phone from being treated like a tablet only because it rotated.
Use Adaptive.layoutValue(...) when a value should follow the current available width, including rotation, split screen, and window resizing.
val paneCount = Adaptive.layoutValue(
sm = 1,
md = 1,
lg = 2,
tab = 3,
desktop = 4
)Adaptive.value(...) remains shortest-side based for stable visual values; Adaptive.layoutValue(...) is width based for reflow decisions.
AdaptiveFlowGrid automatically fits equal-width columns inside its actual parent constraints. This makes reusable components respond to phones, rotation, split screen, and resizable windows without device-name checks.
import com.adaptive.kit_flow.layout.AdaptiveFlowGrid
AdaptiveFlowGrid(
minColumnWidth = 160.dp,
maxColumns = 3,
horizontalSpacing = 12.dp,
verticalSpacing = 12.dp
) {
repeat(6) { index ->
Card {
Text("Item $index", Modifier.padding(16.dp))
}
}
}Large system text increases the effective minimum column width by default, so the grid wraps earlier instead of squeezing text. Set fontScaleAware = false only when the content does not contain text.
The grid is eager and is intended for a small group of panels, cards, or controls. Continue using LazyVerticalGrid for long or unbounded collections.
Apply safe-area or window-inset padding outside the grid; the remaining usable width is then handled automatically. Children keep their natural height. If a parent imposes a smaller height, use scrolling, a lazy grid, or opt in to clipOverflow = true when clipped overflow is the intended behavior.
KitFlow helps keep UI stable when font scale increases.
val accessibility = LocalAdaptiveAccessibilityInfo.currentAvailable values:
accessibility.fontScale
accessibility.fontScaleClass
accessibility.minimumTouchTarget
accessibility.reducedMotion
accessibility.highContrast
accessibility.differentiateWithoutColorUse AdaptiveAccessibleLayout when a Row may break with large text.
AdaptiveAccessibleLayout(
normal = {
Row {
Title()
Actions()
}
},
largeText = {
Column {
Title()
Actions()
}
}
)Use adaptiveTouchTarget() for clickable UI.
Modifier
.adaptiveTouchTarget()
.clickable { onClick() }Use adaptiveSemantics() for clear labels and state.
Modifier.adaptiveSemantics(
label = "Download file",
role = Role.Button
)Use AdaptiveIconButton for icon-only actions.
AdaptiveIconButton(
icon = Icons.Default.Close,
contentDescription = "Close",
onClick = onClose
)Icon-only buttons must have meaningful labels.
There are certain things to keep in mind while making layouts or components in Jetpack Compose:
aspectRatio(...), weight(...), fillMaxWidth(), widthIn(...), heightIn(...), and constraints.maxLines, wrapping strategy, and TextOverflow.Ellipsis where needed.LazyColumn or LazyRow for lists.Example with official window size class style:
when (windowSizeClass) {
WindowWidthSizeClass.Compact -> {
Column {
LeftPanel()
RightPanel()
}
}
WindowWidthSizeClass.Medium,
WindowWidthSizeClass.Expanded -> {
Row {
LeftPanel(
modifier = Modifier.weight(1f)
)
RightPanel(
modifier = Modifier.weight(1f)
)
}
}
}With KitFlow, the same idea can be kept inside your SDK-driven layout flow:
val info = rememberWindowInfo()
AdaptiveFlowGrid(
minColumnWidth = 240.dp,
maxColumns = 2,
horizontalSpacing = 16.dp,
verticalSpacing = 16.dp
) {
Card {
LeftPanel(modifier = Modifier.padding(16.dp))
}
Card {
RightPanel(modifier = Modifier.padding(16.dp))
}
}KitFlow does not remove the need to understand Compose layout. It helps you arrange adaptive code in one clear flow.
Keep these official docs close while designing adaptive Compose UI:
KitFlow should resolve adaptive context and adaptive values, not own the UI.
Developers keep using normal Compose components. KitFlow helps those components adapt.
What we expect from contributors:
Contributions should push KitFlow toward a clearer shared SDK experience, not toward platform-specific duplication.
Adaptive.value() stays shortest-side based for stable visual values.Adaptive.layoutValue() follows current width for reflow decisions.AdaptiveFlowGrid is intended for small groups of panels, cards, or controls.