Skip to content
Kinetixui

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.kt

That 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:compose

Every 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#

  • Chart and Form aren't ported. The React Chart is a recharts theming shell with no rendering of its own — a Compose equivalent needs a charting-library decision (Vico / YCharts) or hand-rolled Canvas. The React Form is react-hook-form glue; KinetixField + KinetixFieldLabel / Description / Message are the Compose equivalent — Compose has no RHF-shaped context to wrap.
  • NavigationMenu isn't ported — a hover-triggered desktop mega-menu with no Android idiom.
  • NativeSelect, AvatarGroup, Tour, and KanbanBoard aren't ported either. NativeSelect wraps the browser's own <select>; KinetixSelect already wraps Compose's own DropdownMenu. AvatarGroup re-wraps its children, not an idiomatic Compose pattern — compose a Row of KinetixAvatars with spacedBy((-8).dp) instead. Tour targets an arbitrary already-rendered element by CSS selector, which Compose has no equivalent live-tree query for — compose a sequence of KinetixPopover steps instead. KanbanBoard is 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 + LazyColumn for the specific board.
  • Scoped ports: KinetixSheet is bottom-only. KinetixSidebar drops rail/icon-collapse mode, SidebarInset, the cookie and the shortcut. KinetixAppBar drops the mobile menu-toggle collapse — the nav stays visible and scrolls horizontally instead. KinetixDropdownMenu / ContextMenu / Menubar have no nested submenus. KinetixResizablePanels is two panels. KinetixCommandDialog doesn't fuzzy-filter (the caller filters). KinetixCodeBlock has no syntax highlighting. KinetixCalendar / DatePicker have no range select. KinetixImage / FileUpload / AudioPlayer are 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.