---
title: Scrollbars
description: Scrollbar components for scroll state, lazy lists, and lazy grids.
---



```kotlin expandable title="ScrollbarsDemo.kt" githubUrl="https://github.com/composablehorizons/compose-unstyled/blob/main/demo/src/commonMain/kotlin/com/composeunstyled/demo/scrollbars/ScrollbarsDemo.kt"
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.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.RectangleShape
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.composeunstyled.Text
import com.composeunstyled.Thumb
import com.composeunstyled.ThumbVisibility
import com.composeunstyled.UnstyledHorizontalScrollbar
import com.composeunstyled.UnstyledVerticalScrollbar
import com.composeunstyled.demo.borderToken
import com.composeunstyled.demo.colors
import com.composeunstyled.demo.contentToken
import com.composeunstyled.demo.surfaceToken
import com.composeunstyled.rememberScrollbarState
import com.composeunstyled.theme.Theme
import kotlin.time.Duration.Companion.seconds

@Preview
@Composable
fun ScrollbarsDemo() {
  VerticalScrollbarsDemo()
}

@Preview
@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(Theme[colors][surfaceToken], RectangleShape)
        .border(1.dp, Theme[colors][borderToken], RectangleShape)
        .fillMaxSize(),
    ) {
      Column(
        Modifier.verticalScroll(state)
          .padding(start = 4.dp, end = 16.dp)
          .fillMaxWidth()
          .padding(8.dp),
      ) {
        Text(
          "Deserts",
          Modifier.padding(4.dp),
          fontSize = 20.sp,
          fontWeight = FontWeight.Bold,
        )
        Spacer(Modifier.height(12.dp))
        desserts.forEach { i ->
          Text(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(Theme[colors][contentToken].copy(0.33f), RoundedCornerShape(100)),
          thumbVisibility = ThumbVisibility.AlwaysVisible,
        )
      }
    }
  }
}

@Preview
@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(Theme[colors][surfaceToken], RectangleShape)
        .border(1.dp, Theme[colors][borderToken], RectangleShape)
        .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(Theme[colors][contentToken]))
        }
      }
      UnstyledHorizontalScrollbar(
        scrollbarState = scrollbarState,
        modifier = Modifier
          .align(Alignment.BottomCenter)
          .height(12.dp)
          .fillMaxWidth(),
      ) {
        Thumb(
          modifier = Modifier
            .fillMaxHeight()
            .padding(2.dp)
            .width(12.dp)
            .background(Theme[colors][contentToken].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

```kotlin
implementation("com.composables:composeunstyled-scrollbars:2.10.0")
```

## Anatomy

```kotlin
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:

```kotlin expandable
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:

```kotlin expandable
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:

```kotlin expandable
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:

```kotlin expandable
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:

```kotlin expandable
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` |  |
