Library / SDK
pushpalroy/JetLime avatar
pushpalroy/JetLime

JetLime draws timelines across Compose Multiplatform, with pagination built in

A Kotlin Multiplatform library to display a timeline view. 🍋

616 stars32 forksKotlinMIT

At a glance

What is it?
JetLime is an MIT licensed Kotlin Multiplatform library for Compose Multiplatform timelines, with vertical and horizontal variants, extended events, dashed and gradient lines, RTL support and native pagination. It suits a Compose Multiplatform app that needs an activity or history feed, and its item list is a plain list of anything you like rather than a fixed model.
Who is it for?
JetLime fits a team already on Compose Multiplatform that needs a timeline across Android, iOS, desktop and web, and that wants pagination and line styling handled by a library rather than written per screen. It does not fit an Android-only project, where the platform specific timeline options in Compose are usually enough, and it does not fit a design that needs a fixed data model, since the library deliberately takes a list of any type you supply.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 130 days ago.
What is it written in?
Mainly Kotlin, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A timeline view that is a list, not a data model

JetLime is a Kotlin Multiplatform library to display a timeline view, described as a simple yet highly customizable UI library for Compose Multiplatform. The homepage is jetlime.pushpalroy.com, and the library is published to Maven Central as io.github.pushpalroy:jetlime.

The design decision that matters is in the first usage example. The items list is created with remember { mutableListOf(Item1, Item2, Item3) }, with a comment noting it can be any type of items. There is no timeline item type in the library, no event model, no required fields. The library is a rendering of a vertical or horizontal line with points on it, and it hands you the index, the item and the position of that item in the sequence, and you draw whatever content you want at each point.

That makes it composable in the Compose sense rather than the data sense. A chat history, an order history, a build log and an onboarding checklist can all be the same component, differing only in the content lambda and the key function.

The cost of that flexibility is that everything about the shape of a point, from its size to the icon, the line style and the spacing, is something you configure, and the defaults are what the JetLimeDefaults object provides.

The supported platforms are the reason to reach for a Kotlin Multiplatform library at all: the highlights list Compose Multiplatform timelines on Android, iOS, desktop on the JVM, and the web on both JS and WASM, and the sample table in the documentation shows a screenshot for each.

JetLimeColumn and JetLimeRow are the same list in two orientations

The vertical and horizontal variants are the entry point, and they are near mirror images of each other.

kotlin
JetLimeColumn(
  modifier = Modifier.padding(16.dp),
  itemsList = ItemsList(items),
  key = { _, item -> item.id },
) { index, item, position ->
  JetLimeEvent(
    style = JetLimeEventDefaults.eventStyle(
      position = position
    ),
  ) {
    // Content here
  }
}

JetLimeRow takes the same parameters and the same content lambda shape. The position argument passed into the content lambda is what tells you where in the sequence the item sits, and the event style is derived from that position, which is how the library decides whether a point is at the start, in the middle or at the end of the line.

That position argument is the mechanism behind the point placement options. The highlights list flexible point placement as START, CENTER and END with continuous line joins, so a point can sit on the line, offset from it, or end it, and the line is drawn continuously between points rather than as a stack of separate segments.

The key parameter is the one to get right. The documentation says to pass the key to define a factory of stable and unique keys representing the item, that using the same key for multiple items in the list is not allowed, and that this key will be used by a LazyColumn or LazyRow internally. So the timeline is lazy underneath, and a duplicate key breaks the lazy list rather than the timeline drawing.

Extended events put content on the other side of the line

A vertical timeline that only has one content slot per point has a specific problem: in a chat or a status feed, some rows want a timestamp or a title on the left and the body on the right. JetLimeExtendedEvent exists for that.

kotlin
JetLimeColumn(
  modifier = Modifier.padding(16.dp),
  itemsList = ItemsList(items),
  key = { _, item -> item.id },
  style = JetLimeDefaults.columnStyle(contentDistance = 24.dp),
) { index, item, position ->
  JetLimeExtendedEvent(
    style = JetLimeEventDefaults.eventStyle(
      position = position
    ),
    additionalContent = {
      // Additional content here
    }
  ) {
    // Content here
  }
}

The additionalContent slot draws on the left side of the timeline, and the ordinary content draws on the right. The highlights also mention icons and animations for extended events, and the column style here sets a content distance of 24.dp, which is the gap between the line and the content.

The spacing model is worth understanding before you build a layout, because the line, the point and the content are separate pieces and the distances between them are what make a timeline look right or look wrong. The defaults exist so that the common case needs no configuration, and the style objects are where you change it when the common case is not yours.

Note that the extended event is documented for the vertical timeline. The horizontal variant is where the RTL support comes in, with the highlights noting that RTL layout support for JetLimeRow and JetLimeExtendedEvent mirrors the timeline and keeps content visible in right-to-left layouts.

Pagination is built in, and the page state stays with you

JetLimePaginatedColumn and JetLimePaginatedRow give the timeline infinite scroll, and the documentation is unusually explicit about who owns what.

The component watches the scroll position and calls onLoadMore when the user gets within loadMoreThreshold items of the end, as long as a page is not already loading and more items remain. The implementation is native and adds no extra dependency.

The state stays with the application. You own the itemsList, the isLoading flag and the hasMoreItems flag, and you append the next page inside onLoadMore and update the flags. While hasMoreItems is true, the timeline line stays continuous into the next page, and it terminates once you set hasMoreItems to false.

That last detail is the elegant part. A paginated list normally has a visual seam where one page ends and the next begins, and most implementations either draw a gap or accept the seam. Here the line continues, so the timeline reads as one continuous history even though the data arrives in chunks.

The setup snippet shows the three pieces of state.

kotlin
val items = remember { mutableStateListOf<Item>() }
var isLoading by remember { mutableStateOf(false) }
var hasMore by remember { mutableStateOf(true) }

The documentation also recommends mutableStateListOf when items are added dynamically from a data source, so the list can be observed as state. The rule the library sets for you is that the load must not fire while a page is already in flight, which is what the isLoading flag is for.

Line styling goes through Brush and PathEffect

The line itself is not a fixed style. The highlights list dashed, gradient and solid lines via Brush plus PathEffect, and that is the standard Compose way to draw them, which means the line can use anything the graphics layer can draw rather than a fixed set of options.

The practical implication is that a dashed timeline is not a separate component or a boolean flag on the event style, it is a different thing passed into the drawing code. That means the styling lives in your own composable configuration, and the library is not dictating a closed set of looks.

The other half of the visual system is the point placement, which the highlights describe as START, CENTER and END with continuous line joins. The event style is produced by JetLimeEventDefaults.eventStyle with a position argument, so the position in the list determines where the point and the line ends sit, and a custom style can override any of it.

The JetLimeDefaults object is the entry point for the rest of the configuration, and the documentation describes the API as small and focused with sensible defaults. That is a design claim you can check by reading the public API on the documentation site, which links each component, including the JetLimeColumn, JetLimeRow and JetLimeExtendedEvent pages.

Version tracking, the sample apps and the publishing setup

JetLime is published to Maven Central, and the dependency declaration in a shared module's build.gradle is a single line.

gradle
dependencies {
  implementation("io.github.pushpalroy:jetlime:4.3.0")
}

The version pins matter here because the library tracks both the Compose Multiplatform and the Kotlin versions. The badges name Compose Multiplatform 1.10.3 and Kotlin 2.3.20, and the Compose link in the badge goes to the JetBrains page on Compose compatibility and versioning, which is the reference to check before you upgrade either of them.

The releases listed are 4.3.0 on 2026-04-22, 4.2.0 on the same day, and 4.1.1 on 2025-11-24, and the last push was on 2026-06-15. The repository is not archived.

The repository layout is a Kotlin Multiplatform project with a jetlime/ module for the library and a sample/ directory containing composeApp/ and iosApp/, so the samples are a Compose app and an iOS app rather than a set of screenshots. There is a docs/ directory, a dokkaModule.md and a dokkaPackage.md for the API documentation generation, a spotless/ directory for code formatting, kotlin-js-store/ for the Kotlin JS and WASM targets, a public-key.asc for the publishing setup, and a PUBLISHING.md that documents the release process. There is also a CLAUDE.md and a .claude/ directory, which is a signal that the project is set up for coding agent contributions.

Editorial conclusion

JetLime fits a team already on Compose Multiplatform that needs a timeline across Android, iOS, desktop and web, and that wants pagination and line styling handled by a library rather than written per screen. It does not fit an Android-only project, where the platform specific timeline options in Compose are usually enough, and it does not fit a design that needs a fixed data model, since the library deliberately takes a list of any type you supply. Before adopting it, check the compatibility table for the Compose and Kotlin versions you are on, since the library tracks both, and read the pagination section carefully, because the page state stays with you and the library only reports when more items are near the end.

Frequently asked questions

How do I add JetLime to a Kotlin Multiplatform project?

Add the dependency to the shared module's build.gradle with implementation("io.github.pushpalroy:jetlime:4.3.0"). The library is published to Maven Central under the io.github.pushpalroy group.

Which platforms does JetLime support?

The highlights list Compose Multiplatform timelines on Android, iOS, desktop on the JVM, and the web on both JS and WASM, and the documentation shows a sample screenshot for each of those four.

How does infinite scroll work in JetLime?

JetLimePaginatedColumn and JetLimePaginatedRow watch the scroll position and call onLoadMore when the user comes within loadMoreThreshold items of the end, provided no page is loading and more items remain. You own the items list, the loading flag and the hasMoreItems flag, and pagination needs no extra dependency.

What item type does JetLime require?

None. The documentation shows the items list as a mutable list of any type, and the content lambda receives the index, the item and the position. You supply a key function that returns stable and unique keys, and using the same key for several items is not allowed.

Official sources

  1. License: MIT
  2. Project website
  3. pushpalroy/JetLime on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pushpalroy-jetlime.svg)](https://hysenlabs.com/projects/pushpalroy-jetlime)

Was this useful?