import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
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.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import com.composeunstyled.CheckedIndicator
import com.composeunstyled.UnstyledCheckbox
import com.composeunstyled.UnstyledIcon
@Composable
fun CheckboxDemo() {
var checked by remember { mutableStateOf(true) }
val checkboxShape = RoundedCornerShape(4.dp)
UnstyledCheckbox(
checked = checked,
onCheckedChange = { checked = it },
modifier = Modifier.clip(checkboxShape),
accessibilityLabel = "Enable notifications",
indication = LocalIndication.current,
) {
CheckedIndicator(
modifier = Modifier
.size(24.dp)
.background(Color(0xFFF8FAFC), checkboxShape)
.border(1.dp, Color(0xFFCACACA), checkboxShape),
indication = LocalIndication.current,
) {
UnstyledIcon(Check)
}
}
}
private val Check: ImageVector
get() {
if (_Check != null) {
return _Check!!
}
_Check = ImageVector.Builder(
name = "Check",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(
fill = null,
fillAlpha = 1.0f,
stroke = SolidColor(Color(0xFF000000)),
strokeAlpha = 1.0f,
strokeLineWidth = 2f,
strokeLineCap = StrokeCap.Round,
strokeLineJoin = StrokeJoin.Round,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero,
) {
moveTo(20f, 6f)
lineTo(9f, 17f)
lineToRelative(-5f, -5f)
}
}.build()
return _Check!!
}
private var _Check: ImageVector? = nullFeatures
- Custom interaction bounds
- Custom checked indicator
- Animated checked content
- Accessibility label
Installation
implementation("com.composables:composeunstyled-checkbox")
Anatomy
UnstyledCheckbox(
checked = checked,
onCheckedChange = onCheckedChange,
) {
CheckedIndicator {
}
}
Concepts
UnstyledCheckboxrepresents the interactive bounds of the checkbox.CheckedIndicatorrepresents the visible checked state. It automatically shows and hides its content based on theUnstyledCheckboxstate.- Give
CheckedIndicatora fixed size when its content only exists while checked. Without a fixed size, the checkbox can change layout size when the indicator appears or disappears.
Accessibility
Screen readers will automatically read any text placed inside UnstyledCheckbox.
Use the accessibilityLabel parameter when the checkbox has no visible text label.
Code Examples
Making an entire row checkable
Use the UnstyledCheckbox component as the row container to make the full row toggleable. This is useful when the label should also toggle the checkbox:
UnstyledCheckbox(
checked = checked,
onCheckedChange = { checked = it },
) {
Row {
CheckedIndicator {
BasicText("✓")
}
BasicText("Accept all terms")
}
}
Creating larger checkbox interaction bounds
Use padding on the CheckedIndicator component to place the visible checkbox inside a larger interaction area. This is useful when the ripple or touch target should be larger than the visible checkbox:
Animating the checked indicator
Use the enter and exit parameters on CheckedIndicator to animate the checked content.
UnstyledCheckbox(
checked = checked,
onCheckedChange = { checked = it },
) {
CheckedIndicator(
enter = fadeIn(),
exit = fadeOut(),
) {
BasicText("Selected")
}
}
Creating a custom checked indicator animation
Use the checked state value to draw your own indicator instead of CheckedIndicator. This is useful when your design system needs a custom checkmark animation:
Labeling an icon-only checkbox
Use the accessibilityLabel parameter when the checkbox content has no text:
UnstyledCheckbox(
checked = checked,
onCheckedChange = { checked = it },
accessibilityLabel = "Enable notifications",
) {
CheckedIndicator {
BasicText("✓")
}
}
API Reference
UnstyledCheckbox
| Parameter | Type | Description |
|---|---|---|
checked |
Boolean |
Whether the checkbox is checked. |
onCheckedChange |
(Boolean) -> Unit |
Callback when the checked state changes. |
modifier |
Modifier |
Modifier to be applied to the checkbox. |
enabled |
Boolean |
|
interactionSource |
MutableInteractionSource? |
|
indication |
Indication? |
|
accessibilityLabel |
String? |
|
content |
CheckboxScope.() -> Unit |
CheckboxScope.CheckedIndicator
| Parameter | Type | Description |
|---|---|---|
modifier |
Modifier |
Modifier to be applied to the checkbox. |
indication |
Indication? |
|
enter |
EnterTransition |
|
exit |
ExitTransition |
|
content |
AnimatedVisibilityScope.() -> Unit |