Compose Unstyled 2.0 is out! Check the official announcement blog ->

Scrollbars

Scrollbar components for scroll state, lazy lists, and lazy grids.

import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.composeunstyled.Thumb
import com.composeunstyled.ThumbVisibility
import com.composeunstyled.UnstyledHorizontalScrollbar
import com.composeunstyled.UnstyledVerticalScrollbar
import com.composeunstyled.rememberScrollbarState
import kotlin.time.Duration.Companion.seconds

@Composable
fun ScrollbarsDemo() {
  VerticalScrollbarsDemo()
}

@Composable
fun VerticalScrollbarsDemo() {
  Box(
    modifier = Modifier.fillMaxSize()
      .padding(vertical = 40.dp)
      .padding(horizontal = 16.dp),
    contentAlignment = Alignment.TopCenter,
  ) {
    val desserts = listOf(
      "Cupcake",
      "Donut",
      "Eclair",
      "Froyo",
      "Gingerbread",
      "Honeycomb",
      "Ice Cream Sandwich",
      "Jelly Bean",
      "KitKat",
      "Lollipop",
      "Marshmallow",
      "Nougat",
      "Oreo",
      "Pie",
      "Quince",
      "Red Velvet Cake",
      "Snow Cone",
      "Tiramisu",
      "Upside-down Cake",
      "Vanilla Custard",
      "Waffle",
      "Xmas Pudding",
      "Yogurt Parfait",
      "Zabaglione",
    )

    val state = rememberScrollState()
    val scrollbarState = rememberScrollbarState(state)

    Box(
      modifier = Modifier
        .widthIn(max = 400.dp)
        .background(Color(0xFFF8FAFC), RoundedCornerShape(8.dp))
        .border(1.dp, Color(0xFFCACACA), RoundedCornerShape(8.dp))
        .fillMaxSize(),
    ) {
      Column(
        Modifier.verticalScroll(state)
          .padding(start = 4.dp, end = 16.dp)
          .fillMaxWidth()
          .padding(8.dp),
      ) {
        BasicText(
          "Deserts",
          Modifier.padding(4.dp),
          style = TextStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold),
        )
        Spacer(Modifier.height(12.dp))
        desserts.forEach { i ->
          BasicText(i, Modifier.padding(4.dp).fillMaxWidth())
          Spacer(Modifier.height(12.dp))
        }
      }
      UnstyledVerticalScrollbar(
        scrollbarState = scrollbarState,
        modifier = Modifier
          .align(Alignment.TopEnd)
          .width(12.dp)
          .fillMaxHeight(),
      ) {
        Thumb(
          modifier = Modifier
            .fillMaxWidth()
            .padding(2.dp)
            .height(12.dp)
            .background(Color.Black.copy(0.33f), RoundedCornerShape(100)),
          thumbVisibility = ThumbVisibility.AlwaysVisible,
        )
      }
    }
  }
}

@Composable
fun HorizontalScrollbarsDemo() {
  Box(
    modifier = Modifier.fillMaxSize()
      .padding(vertical = 40.dp),
    contentAlignment = Alignment.TopCenter,
  ) {
    val state = rememberScrollState()
    val scrollbarState = rememberScrollbarState(state)

    Box(
      modifier = Modifier
        .widthIn(max = 400.dp)
        .background(Color(0xFFF8FAFC), RoundedCornerShape(8.dp))
        .border(1.dp, Color(0xFFCACACA), RoundedCornerShape(8.dp))
        .wrapContentHeight(),
    ) {
      Row(
        Modifier.horizontalScroll(state)
          .systemBarsPadding()
          .navigationBarsPadding()
          .padding(start = 4.dp, end = 16.dp)
          .fillMaxWidth()
          .padding(8.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
      ) {
        (1..100).forEach { i ->
          Box(Modifier.size(90.dp).clip(CircleShape).background(Color.Black))
        }
      }
      UnstyledHorizontalScrollbar(
        scrollbarState = scrollbarState,
        modifier = Modifier
          .align(Alignment.BottomCenter)
          .height(12.dp)
          .fillMaxWidth(),
      ) {
        Thumb(
          modifier = Modifier
            .fillMaxHeight()
            .padding(2.dp)
            .width(12.dp)
            .background(Color.Black.copy(0.33f), RoundedCornerShape(100)),
          thumbVisibility = ThumbVisibility.HideWhileIdle(
            enter = fadeIn(),
            exit = fadeOut(),
            hideDelay = 1.seconds,
          ),
        )
      }
    }
  }
}

Features

  • ScrollState support
  • LazyListState support
  • LazyGridState support
  • Draggable scrollbar thumbs

Installation

implementation("com.composables:composeunstyled-scrollbars")

Anatomy

val scrollbarState = rememberScrollbarState(scrollState)

UnstyledVerticalScrollbar(scrollbarState) {
  Thumb()
}

Concepts

  • ScrollbarState represents the scroll position used by a scrollbar.
  • UnstyledVerticalScrollbar renders a vertical scrollbar.
  • UnstyledHorizontalScrollbar renders a horizontal scrollbar.
  • Thumb renders the draggable thumb inside a scrollbar.

Code Examples

Adding scrollbars to LazyColumn

Use the rememberScrollbarState(LazyListState) function to connect a scrollbar to lazy list scroll position:

val listState = rememberLazyListState()
val scrollbarState = rememberScrollbarState(listState)

LazyColumn(state = listState) {
  items(100) { index ->
    BasicText("Item $index")
  }
}

UnstyledVerticalScrollbar(scrollbarState) {
  Thumb()
}

Adding scrollbars to LazyVerticalGrid

Use the rememberScrollbarState(LazyGridState) function to connect a scrollbar to lazy grid scroll position:

val gridState = rememberLazyGridState()
val scrollbarState = rememberScrollbarState(gridState)

LazyVerticalGrid(
  columns = GridCells.Fixed(2),
  state = gridState,
) {
  items(100) { index ->
    BasicText("Item $index")
  }
}

UnstyledVerticalScrollbar(scrollbarState) {
  Thumb()
}

Adding scrollbars to scrollable content

Use the rememberScrollbarState(ScrollState) function for content that uses a regular scroll state:

val scrollState = rememberScrollState()
val scrollbarState = rememberScrollbarState(scrollState)

Column(Modifier.verticalScroll(scrollState)) {
  repeat(100) { index ->
    BasicText("Item $index")
  }
}

UnstyledVerticalScrollbar(scrollbarState) {
  Thumb()
}

Hiding the scrollbar while idle

Use the thumbVisibility parameter to hide the thumb when the user is not interacting with the scrollable content:

UnstyledVerticalScrollbar(scrollbarState) {
  Thumb(
    thumbVisibility = ThumbVisibility.HideWhileIdle(
      enter = fadeIn(),
      exit = fadeOut(),
      hideDelay = 500.milliseconds,
    ),
  )
}

Supporting reverse layout

Use the reverseLayout parameter when the scrollable content uses reverse layout:

UnstyledVerticalScrollbar(
  scrollbarState = scrollbarState,
  reverseLayout = true,
) {
  Thumb()
}

API Reference

rememberScrollbarState

Parameter Type Description
scrollState ScrollState
lazyListState LazyListState
lazyGridState LazyGridState

UnstyledVerticalScrollbar

Parameter Type Description
scrollbarState ScrollbarState
modifier Modifier
enabled Boolean
interactionSource MutableInteractionSource?
reverseLayout Boolean
thumb (ScrollbarScope.() -> Unit)

UnstyledHorizontalScrollbar

Parameter Type Description
scrollbarState ScrollbarState
modifier Modifier
enabled Boolean
interactionSource MutableInteractionSource?
reverseLayout Boolean
thumb (ScrollbarScope.() -> Unit)

ScrollbarScope.Thumb

Parameter Type Description
modifier Modifier
thumbVisibility ThumbVisibility
enabled Boolean