Hey everyone :wave: What's the expected behavior i...
# compose
s
Hey everyone 👋 What's the expected behavior in case
onClick
and
Modifier.clickable
are both set on a button. In addition, how does one set an accessibility description for a Button (if not use
Modifier.clickable
)? 🧵
Copy code
@Composable
fun ButtonTest() {
    val context = LocalContext.current
    Button(
        onClick = {
            Toast.makeText(context, "onclick", Toast.LENGTH_SHORT).show()
        },
        modifier = Modifier.clickable(
            onClick = {
                Toast.makeText(context, "clickable", Toast.LENGTH_SHORT).show()
            },
            onClickLabel = "login user"
        )
    ) {
        Text(text = "login")
    }
}
d
Most likely one just overrides the other. One of the best things about Compose is that you can see the implementation, which for button is
Copy code
...
    Surface(
        onClick = onClick,
        modifier = modifier.semantics { role = Role.Button },
        enabled = enabled,
        shape = shape,
        color = containerColor,
        contentColor = contentColor,
        tonalElevation = tonalElevation,
        shadowElevation = shadowElevation,
        border = border,
        interactionSource = interactionSource
    ) {
        CompositionLocalProvider(LocalContentColor provides contentColor) {
            ProvideTextStyle(value = MaterialTheme.typography.labelLarge) {
                Row(
                    Modifier
                        .defaultMinSize(
                            minWidth = ButtonDefaults.MinWidth,
                            minHeight = ButtonDefaults.MinHeight
                        )
                        .padding(contentPadding),
                    horizontalArrangement = Arrangement.Center,
                    verticalAlignment = Alignment.CenterVertically,
                    content = content
                )
            }
        }
    }
and Surface there is
Copy code
Box(
            modifier = modifier
                .minimumInteractiveComponentSize()
                .surface(
                    shape = shape,
                    backgroundColor = surfaceColorAtElevation(
                        color = color,
                        elevation = absoluteElevation
                    ),
                    border = border,
                    shadowElevation = shadowElevation
                )
                .clickable(
                    interactionSource = interactionSource,
                    indication = rememberRipple(),
                    enabled = enabled,
                    onClick = onClick
                ),
            propagateMinConstraints = true
        ) {
            content()
        }
One thing to remember is that order of modifiers matters (and imo that's one of the main reasons some composables take an onClick lambda separately to modifier)
s
I did 🙂 Noticed that the
onClick
value is used with
Modifier.clickable
and therefore setting
Modifier.clickable
on the button seems completely useless. I am conflicted if this should be the actual behavior
d
What behaviour would you expect? Maybe you have a niche use case but for the majority of users the Button component works as expected
âž• 1
s
Haha great question! What I observe from the implementation is that
onClick
when set via
Modifier.clickable
is ignored and only the one set via the
Button
onClick
is always used. However, if I wanna set an accessibility description on the Button, I am forced to provide
Modifier.clickable
with an empty
onClick
lambda. Seems like an api design issue rather than a behavior problem. I am also thinking about this in terms of designing an api for a design system.
d
You're looking for
Copy code
Modifier.semantics { }
and
Copy code
Modifier.clearAndSemantics { }
You can set contentDescription inside, however do consider if this is the right thing to do. Buttons are sort of a wrapper around other composables (text) so the contentDescription would depend on what's inside it.
> Haha great question! What I observe from the implementation is that
onClick
when set via
Modifier.clickable
is ignored and only the one set via the
Button
onClick
is always used. That's what I meant here > One thing to remember is that order of modifiers matter If you look clickable Surface implementation (the last snippet above),
Box
's Modifier is is your passed in Modifier (the one on your button where you set
.clickable
). It's much clearer if you expand the passed in modifier sort of:
Copy code
Box(
    modifier = Modifier.clickable( // your Modifier defined when you called Button
        onClick = {
            Toast.makeText(context, "clickable", Toast.LENGTH_SHORT).show()
        }
        .minimumInteractiveComponentSize()
        .surface(
            shape = shape,
            backgroundColor = surfaceColorAtElevation(
                color = color,
                elevation = absoluteElevation
            ),
            border = border,
            shadowElevation = shadowElevation
        )
        .clickable( // .clickable applied again so your modifier has no effect
            interactionSource = interactionSource,
            indication = rememberRipple(),
            enabled = enabled,
            onClick = onClick
        ),
    propagateMinConstraints = true
) {
    content()
}