---
title: Tab Group
description: A tab group component with generic tab keys and keyboard navigation.
---



```kotlin expandable title="TabGroupDemo.kt" githubUrl="https://github.com/composablehorizons/compose-unstyled/blob/main/demo/src/commonMain/kotlin/com/composeunstyled/demo/tabgroup/TabGroupDemo.kt"
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
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 com.composeunstyled.Text
import com.composeunstyled.UnstyledButton
import com.composeunstyled.UnstyledTab
import com.composeunstyled.UnstyledTabGroup
import com.composeunstyled.UnstyledTabList
import com.composeunstyled.UnstyledTabPanel
import com.composeunstyled.demo.borderToken
import com.composeunstyled.demo.colors
import com.composeunstyled.demo.contentToken
import com.composeunstyled.demo.mutedContentToken
import com.composeunstyled.demo.surfaceToken
import com.composeunstyled.theme.Theme

@Preview
@Composable
fun TabGroupDemo() {
  class Article(val title: String, val relativeTime: String, val comments: Int, val points: Int)

  val categories = mapOf(
    "Trending" to listOf(
      Article(
        title = "I hosted my startup's backend on a Tamagotchi – AMA",
        relativeTime = "11 hours ago",
        comments = 312,
        points = 1042,
      ),
      Article(
        title = "I fired myself to improve company culture — it worked",
        relativeTime = "9 hours ago",
        comments = 264,
        points = 928,
      ),
    ),
    "Latest" to listOf(
      Article(
        title = "The office microwave is now a Kubernetes node",
        relativeTime = "2 hours ago",
        comments = 87,
        points = 356,
      ),
      Article(
        title = "We replaced scrum with interpretive dancing",
        relativeTime = "1 hour ago",
        comments = 52,
        points = 198,
      ),
    ),
    "Popular" to listOf(
      Article(
        title = "Social network for ants is growing fast",
        relativeTime = "14 hours ago",
        comments = 412,
        points = 1376,
      ),
      Article(
        title = "Why I quit my $800K FAANG job to grow mushrooms",
        relativeTime = "16 hours ago",
        comments = 391,
        points = 1204,
      ),
    ),
  )

  var selectedTab by remember { mutableStateOf(categories.keys.first()) }

  Box(
    modifier = Modifier.fillMaxSize()
      .padding(16.dp)
      .padding(top = 90.dp),
    contentAlignment = Alignment.TopCenter,
  ) {
    UnstyledTabGroup(
      selectedTab = selectedTab,
      onSelectedTabChange = { selectedTab = it },
      tabs = categories.keys.toList(),
      modifier = Modifier.widthIn(max = 450.dp),
    ) {
      Column {
        UnstyledTabList(
          modifier = Modifier
            .fillMaxWidth()
            .height(48.dp)
            .clip(RectangleShape)
            .background(Theme[colors][surfaceToken])
            .border(1.dp, Theme[colors][borderToken], RectangleShape),
        ) {
          Row(Modifier.fillMaxSize()) {
            categories.forEach { (key, _) ->
              UnstyledTab(
                key = key,
                modifier = Modifier.weight(1f).fillMaxHeight(),
                indication = LocalIndication.current,
              ) {
                Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                  Text(
                    text = key,
                    fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal,
                    color = if (selected) {
                      Theme[colors][contentToken]
                    } else {
                      Theme[colors][mutedContentToken]
                    },
                  )
                  if (selected) {
                    Box(
                      modifier = Modifier
                        .background(
                          color = Theme[colors][contentToken],
                          shape = RectangleShape,
                        )
                        .fillMaxWidth()
                        .height(3.dp)
                        .align(Alignment.BottomCenter),
                    )
                  }
                }
              }
            }
          }
        }

        Spacer(modifier = Modifier.height(16.dp))
        categories.forEach { (key, items) ->
          UnstyledTabPanel(
            key = key,
            modifier = Modifier
              .fillMaxWidth()
              .background(
                color = Theme[colors][surfaceToken],
                shape = RectangleShape,
              )
              .border(1.dp, Theme[colors][borderToken], RectangleShape),
          ) {
            Column(Modifier.padding(16.dp)) {
              items.forEach { item ->
                UnstyledButton(
                  onClick = { /* TODO */ },
                  modifier = Modifier.clip(RectangleShape),
                  indication = LocalIndication.current,
                ) {
                  Column(Modifier.padding(12.dp)) {
                    Text(
                      item.title,
                      fontWeight = FontWeight.Medium,
                    )
                    Spacer(Modifier.height(4.dp))
                    Row(
                      horizontalArrangement = Arrangement.spacedBy(4.dp),
                      verticalAlignment = Alignment.CenterVertically,
                      modifier = Modifier.fillMaxWidth().alpha(0.6f),
                    ) {
                      Text(item.relativeTime)
                      Text("·")
                      Text("${item.comments} comments")
                      Text("·")
                      Text("${item.points} shares")
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```



## Features

- Generic tab keys
- Horizontal and vertical tab lists
- Automatic or manual activation
- Focus handoff to panels

## Installation

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

## Anatomy

```kotlin
val tabs = listOf("account", "billing")

UnstyledTabGroup(
  selectedTab = selectedTab,
  onSelectedTabChange = onSelectedTabChange,
  tabs = tabs,
) {
  TabList {
    tabs.forEach { tab ->
      Tab(tab) {
      }
    }
  }

  tabs.forEach { tab ->
    TabPanel(tab) {
    }
  }
}
```

## Concepts

- `UnstyledTabGroup` represents a set of tabs and panels grouped by key.
- `TabList` renders the group of tabs.
- `Tab` renders one tab inside `TabList`.
- `TabPanel` renders the panel for the selected tab key.

## Accessibility

Keep `tabs` in the same order as the visual tabs. `TabList` uses that order for arrow-key navigation, Home, and End.

## Code Examples

### Selecting tabs manually

Use the `selectedTab` parameter to control the active tab:

```kotlin expandable
val tabs = listOf("account", "billing")
var selectedTab by remember { mutableStateOf("account") }

UnstyledTabGroup(
  selectedTab = selectedTab,
  onSelectedTabChange = { selectedTab = it },
  tabs = tabs,
) {
  TabList {
    tabs.forEach { tab ->
      Tab(tab) {
        BasicText(tab)
      }
    }
  }

  TabPanel("account") {
    BasicText("Account")
  }

  TabPanel("billing") {
    BasicText("Billing")
  }
}
```

### Creating vertical tabs

Use the `orientation` parameter on `TabList` to change arrow-key navigation for vertical tabs:

```kotlin expandable
TabList(orientation = Orientation.Vertical) {
  tabs.forEach { tab ->
    Tab(tab) {
      BasicText(tab)
    }
  }
}
```

### Requiring click activation

Use the `activateOnFocus` parameter when arrow-key focus should not select tabs:

```kotlin expandable
Tab("billing", activateOnFocus = false) {
  BasicText("Billing")
}
```

### Disabling a tab

Use the `enabled` parameter to keep a tab visible but unavailable:

```kotlin expandable
Tab(
  key = "billing",
  enabled = false,
) {
  BasicText("Billing")
}
```

## API Reference

### UnstyledTabGroup

| Parameter | Type | Description |
|-----------|------|-------------|
| `selectedTab` | `T` | The initial selected tab for the tab group state. |
| `onSelectedTabChange` | `(T) -> Unit` |  |
| `tabs` | `List<T>` |  |
| `modifier` | `Modifier` | Modifier to be applied to the tab. |
| `content` | `TabGroupScope<T>.() -> Unit` | Composable function to define the content of the tab panel. |
### TabGroupScope.TabList

| Parameter | Type | Description |
|-----------|------|-------------|
| `modifier` | `Modifier` | Modifier to be applied to the tab. |
| `orientation` | `Orientation` | The orientation of the tab list (horizontal or vertical). |
| `content` | `TabListScope<T>.() -> Unit` | Composable function to define the content of the tab panel. |
### TabListScope.Tab

| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | `T` | The unique key for the tab panel. |
| `modifier` | `Modifier` | Modifier to be applied to the tab. |
| `enabled` | `Boolean` | Whether the tab is enabled. |
| `activateOnFocus` | `Boolean` | Whether to activate a tab when it receives focus. |
| `indication` | `Indication?` | Visual indication for interactions. |
| `interactionSource` | `MutableInteractionSource?` | Interaction source for the tab. |
| `contentAlignment` | `Alignment` |  |
| `content` | `TabScope.() -> Unit` | Composable function to define the content of the tab panel. |
### TabGroupScope.TabPanel

| Parameter | Type | Description |
|-----------|------|-------------|
| `key` | `T` | The unique key for the tab panel. |
| `modifier` | `Modifier` | Modifier to be applied to the tab. |
| `content` | `() -> Unit` | Composable function to define the content of the tab panel. |
