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

Disclosure

An expandable content component with a dedicated trigger and content slot.

import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
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.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.composables.icons.lucide.ChevronDown
import com.composables.icons.lucide.Lucide
import com.composeunstyled.DisclosedContent
import com.composeunstyled.DisclosureButton
import com.composeunstyled.UnstyledDisclosure
import com.composeunstyled.UnstyledIcon

@Composable
fun DisclosureDemo() {
  var expanded by remember { mutableStateOf(false) }

  UnstyledDisclosure(
    expanded = expanded,
    onExpandedChange = { expanded = it },
  ) {
    Column(
      modifier = Modifier
        .widthIn(max = 560.dp)
        .clip(RoundedCornerShape(12.dp))
        .background(Color(0xFFF8FAFC))
        .border(1.dp, Color(0xFFCACACA), RoundedCornerShape(12.dp)),
    ) {
      DisclosureButton(
        modifier = Modifier.fillMaxWidth(),
        indication = LocalIndication.current,
      ) {
        Row(
          modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp, horizontal = 16.dp),
          verticalAlignment = Alignment.CenterVertically,
        ) {
          BasicText("What is Compose Unstyled", modifier = Modifier.weight(1f))

          val degrees by animateFloatAsState(if (expanded) -180f else 0f, tween())
          UnstyledIcon(
            imageVector = Lucide.ChevronDown,
            contentDescription = null,
            modifier = Modifier.rotate(degrees),
          )
        }
      }
      DisclosedContent(
        enter = expandVertically(
          spring(
            stiffness = Spring.StiffnessMediumLow,
            visibilityThreshold = IntSize.VisibilityThreshold,
          ),
        ),
        exit = shrinkVertically(),
      ) {
        BasicText(
          "Compose Unstyled is a collection of unstyled, accessible UI components for Compose " +
            "Multiplatform. It provides the building blocks for creating beautiful, consistent " +
            "user interfaces.",
          modifier = Modifier.padding(16.dp).alpha(0.66f),
        )
      }
    }
  }
}

Installation

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

Anatomy

UnstyledDisclosure(
  expanded = expanded,
  onExpandedChange = onExpandedChange,
) {
  DisclosureButton {
  }

  DisclosedContent {
  }
}

Concepts

  • UnstyledDisclosure represents an expandable region.
  • DisclosureButton renders the trigger that toggles the disclosure.
  • DisclosedContent renders content only while the disclosure is expanded.

Accessibility

Use DisclosureButton for the disclosure trigger so assistive technology receives expand and collapse actions.

Code Examples

Showing hidden content

Use the expanded parameter to control whether DisclosedContent is visible:

var expanded by remember { mutableStateOf(false) }

UnstyledDisclosure(
  expanded = expanded,
  onExpandedChange = { expanded = it },
) {
  DisclosureButton {
    BasicText(if (expanded) "Hide details" else "Show details")
  }

  DisclosedContent {
    BasicText("Details")
  }
}

Animating disclosed content

Use the enter and exit parameters on DisclosedContent to animate the disclosed content:

UnstyledDisclosure(
  expanded = expanded,
  onExpandedChange = { expanded = it },
) {
  DisclosureButton {
    BasicText("Details")
  }

  DisclosedContent(
    enter = expandVertically(),
    exit = shrinkVertically(),
  ) {
    BasicText("Hidden content")
  }
}

API Reference

UnstyledDisclosure

Parameter Type Description
expanded Boolean Controls whether the disclosure is expanded.
onExpandedChange (Boolean) -> Unit
modifier Modifier Modifier to be applied to the panel.
content DisclosureScope.() -> Unit A composable function that defines the content of the panel.

DisclosureScope.DisclosureButton

Parameter Type Description
modifier Modifier Modifier to be applied to the panel.
enabled Boolean Indicates if the heading is enabled.
contentPadding PaddingValues Padding values for the content.
indication Indication? The indication to be shown when the heading is interacted with.
interactionSource MutableInteractionSource? The interaction source for the heading.
contentAlignment Alignment
content () -> Unit A composable function that defines the content of the panel.

DisclosureScope.DisclosedContent

Parameter Type Description
modifier Modifier Modifier to be applied to the panel.
enter EnterTransition The enter transition for the panel.
exit ExitTransition The exit transition for the panel.
content () -> Unit A composable function that defines the content of the panel.