Jetpack Compose
packages/ui-compose is a native Jetpack Compose port of KinetixUI —
Kinetix* composables for 90 of the
98 React components, on the same token contract as the
web library: one for every React component (or a documented reason it isn't).
It's a standalone Gradle project inside this monorepo, not a
pnpm/npm workspace package — there's no package.json, so it's invisible
to pnpm install / Turborepo. It lives here for proximity to the token
source it's built from.
Not on Maven Central yet. Consume it by cloning the repo and copying the
packages/ui-compose/ui module into your project (or includeBuild-ing
it), the same "own the code" model as the web CLI. Remote publishing
needs a signing key and is deliberately not wired up.
Setup#
The ui module is com.android.library, namespace com.kinetixui.ui,
compileSdk 37 / minSdk 24, Compose enabled. It depends only on
compose-bom + androidx.compose.ui / material3.
Wrap your app (or a screen, or a @Preview) in KinetixTheme so every
Kinetix* composable resolves to the right light/dark token set:
import com.kinetixui.ui.*
KinetixTheme {
// KinetixTheme() defaults to isSystemInDarkTheme(),
// same as the web "system" mode. Pass darkTheme = … to force it.
Column(modifier = Modifier.padding(16.dp)) {
KinetixButton(onClick = { /* … */ }, variant = KinetixButtonVariant.Primary) {
Text("Get started")
}
KinetixField(invalid = emailError != null) {
KinetixFieldLabel(text = "Email")
KinetixInput(value = email, onValueChange = { email = it }, isError = emailError != null)
emailError?.let { KinetixFieldMessage(text = it) }
}
KinetixSwitch(checked = enabled, onCheckedChange = { enabled = it })
}
}KinetixColorScheme.current exposes the active KinetixColors set if you
need a token directly.
A custom theme#
KinetixTheme takes your own colour sets and still switches on the system
setting:
KinetixTheme(
light = AcmeTheme.light,
dark = AcmeTheme.dark,
) {
App()
}You can write that pair by hand, change one role with
LightKinetixColors.copy(action = …), or export one from a
Create design:
kinetixui preset compose KX1_… --name AcmeTheme --output AcmeTheme.ktThat writes a Kotlin object with a light and a dark KinetixColors,
built from the same resolved theme the workspace exports —
see the CLI reference.
Or skip the terminal: Create's Export panel has a Jetpack Compose target that generates the same file in the browser. It names the roles below that Compose has no field for, rather than leaving them to be discovered here.
Colours only. KinetixColors is the whole themeable surface here:
corner radius and elevation are generated constants that components
reference directly (KinetixRadius.md), with no runtime theme to override.
A Create design's radius and surface treatment therefore apply on the web
and not in Compose, and the generated file says so in its own header. Two
preset roles do not travel either: input and ring are colours a preset
can override by hand, and KinetixColors has no field for them.
Separately — and not a Create limitation — tertiary-foreground is in the
Kinetix token contract and in the SwiftUI theme, but KinetixColors has
never exposed it. Create does not model that token at all, so no preset can
set it.
A colour the design did not change is written as a reference to the shipped token rather than a copy of today's value, so it keeps following the library.
Tokens#
The Kotlin token objects and res/values/{colors,dimens}.xml under
ui/src/main/kotlin/com/kinetixui/tokens/ are generated and vendored —
copied from packages/tokens/dist/android/ by
scripts/vendor-compose-tokens.mjs. Don't hand-edit them. After any
token change:
pnpm build:tokens && pnpm vendor:composeEvery spacing / radius / type value in a component is read via
dimensionResource(R.dimen.*) — nothing is a hand-picked number, unless
a Figma value doesn't land on the shared scale (those are hardcoded with
a comment).
What's covered#
One per ported React component — Button, Input, Dialog, Popover, Sheet,
DropdownMenu, DataTable, Calendar, Carousel, Sidebar, and the rest. The
components gallery is the master list; each Compose file
mirrors its packages/ui/src/components/*.tsx counterpart 1:1, with a
doc comment stating exactly what wasn't carried over.
Reuse over re-derivation is the rule: KinetixSlider wraps Material3's
Slider, KinetixCalendar its DatePicker, KinetixToaster its
SnackbarHost, KinetixCarousel HorizontalPager, KinetixSidebar
ModalNavigationDrawer — each re-themed onto the token contract rather
than hand-built. KinetixSelect / KinetixDrawer are thin aliases over
KinetixDropdownMenu / KinetixSheet — the same primitive under a
different name.
Known gaps#
ChartandFormaren't ported. The ReactChartis arechartstheming shell with no rendering of its own — a Compose equivalent needs a charting-library decision (Vico / YCharts) or hand-rolledCanvas. The ReactFormisreact-hook-formglue;KinetixField+KinetixFieldLabel/Description/Messageare the Compose equivalent — Compose has no RHF-shaped context to wrap.NavigationMenuisn't ported — a hover-triggered desktop mega-menu with no Android idiom.NativeSelect,AvatarGroup,Tour, andKanbanBoardaren't ported either.NativeSelectwraps the browser's own<select>;KinetixSelectalready wraps Compose's ownDropdownMenu.AvatarGroupre-wraps its children, not an idiomatic Compose pattern — compose aRowofKinetixAvatars withspacedBy((-8).dp)instead.Tourtargets an arbitrary already-rendered element by CSS selector, which Compose has no equivalent live-tree query for — compose a sequence ofKinetixPopoversteps instead.KanbanBoardis built on@dnd-kit's accessible multi-container drag-and-drop, with no equivalent dependency in this repo's native packages — use Compose's own drag gestures +LazyColumnfor the specific board.- Scoped ports:
KinetixSheetis bottom-only.KinetixSidebardrops rail/icon-collapse mode,SidebarInset, the cookie and the shortcut.KinetixAppBardrops the mobile menu-toggle collapse — the nav stays visible and scrolls horizontally instead.KinetixDropdownMenu/ContextMenu/Menubarhave no nested submenus.KinetixResizablePanelsis two panels.KinetixCommandDialogdoesn't fuzzy-filter (the caller filters).KinetixCodeBlockhas no syntax highlighting.KinetixCalendar/DatePickerhave no range select.KinetixImage/FileUpload/AudioPlayerare presentational — no Coil, file picker, or media playback; you wire those in. - No remote publishing, no screenshot-test harness. CI compiles the
whole package (
assembleDebug+lintDebug) on every push that touches it or the token source.
The SwiftUI and Flutter ports carry the
same React surface, and all three native platforms skip the same
eight standing non-ports
(AvatarGroup, Combobox, DirectionProvider, Form, KanbanBoard, NativeSelect, NavigationMenu, and Tour). Components missing from only some platforms are
listed, with the reason, under Not on every platform.
Being on a platform doesn't mean carrying every React feature: a port that
drops something says so in its own doc comment — see
Contributing for the rule that governs this.