Beyond the Binary - More Inclusive Gender Options with Compose

Reading time: about 6 minutes

Published

If your gender matches the one that was assigned to you at birth, you probably don't notice flaws in application design that enforce the gender binary. But if it doesn't, and especially if your gender is beyond the woman-man binary, you constantly notice user interfaces that exclude you.

For example, what should you select if you aren't a man or a woman, and those are the only options in a form? In some cases, forms might include the option "other". But... It underlines that you're something other and don't even have a category. As someone who has felt their entire life like an outsider and the other in many ways, I can tell you that it doesn't do good things to your mental health.

So, in this blog post, I'll discuss the concept of gender a bit and then demonstrate one way to build a more inclusive gender selection with Jetpack Compose. Finally, at the end, I'll list some resources related to this topic.

Gender Beyond the Binary

Let's start with the basics: Gender and sex are separate things. In some languages, like Finnish, the word for both is the same - "sukupuoli" - but, for example, English differentiates them. And neither of them is binary.

Gender is a social construct, and each culture has its own norms for genders. There are multiple genders, and a person might describe themself with one or more. Some examples include woman, man, non-binary, agender, genderqueer, demigender (e.g., demigirl or demiboy), or genderfluid.

If you want to learn more, I've listed some resources in the Resources-section.

Asking User's Gender

Gender is asked in many locations - it might be surveys, settings for an app, and many other places. I'll concentrate on app development as I write about app development, and the specific case of app settings because that's where I've seen this gender selection pop up the most frequently.

Before diving into the technical implementation of more inclusive gender options, I want to discuss whether there is a need for gender data and making the selection optional. Ruth Dillon-Mansfield discusses these topics, and more, in her article How to Ask About Gender in Forms Respectfully, which I recommend checking.

Do You Actually Need That Information?

The first question related to asking the user's gender is whether you really need it. Is the form asking it just because it's customary to do so, or because the company wants more data for marketing purposes, or is the data used for something the user benefits from?

When this information is asked, the reasons for requesting it should always be explained. Why is it asked? How is the data used? How it's stored? So remember to include that information easily available next to the form where this is asked.

Make It Optional

Not everyone is comfortable sharing their gender identity in all instances, even if you have good reasons for asking it. There might be a lot of reasons for that, and those reasons should be respected.

More Inclusive Gender Options

Let's look at one way to build a more inclusive gender selection component on Android.

The component we're going to build will have the following:

The component looks like this:

A form question with a title 'Which most accurately describe(s) you?' and options with checkboxes Woman, Man, Non-Binary, Let me type, Prefer not to disclose. Let me type is selected, and under it, there's a text field with label Type here and typed text Agender.

This list could include more genders, but as this is a demo, I decided to have it as short as possible while still allowing the user to input their gender.

We will need two UI components: A checkbox and a checkbox with a text field. Let's build the checkbox first, and then we can use it with the text field:

@Composable
fun CheckboxWithLabel(
    checked: Boolean,
    label: String,
    onCheckedChange: (Boolean) -> Unit
) {
    Row(
        modifier = Modifier.toggleable(
            value = checked,
            role = Role.Checkbox,
            onValueChange = onCheckedChange
        ),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Checkbox(
            modifier = Modifier.clearAndSetSemantics {},
            checked = checked,
            onCheckedChange = onCheckedChange
        )
        Text(label)
    }
}

This component takes in the value for the checkbox checked-state, a function to toggle the value, and a label for the checkbox. Inside the component, we're wrapping the label and the checkbox together with a Row, adding a toggleable-modifier to the parent Row, and then clearing semantics from the Checkbox.

We're doing this for increased accessibility so assistive technology users can use the checkbox more easily. It also extends the touch area to cover the label, too, not just the checkbox component. If you want to read more, I've written a blog post about improving Android accessibility with modifiers in Jetpack Compose, which explains these decisions more.

The second component, a checkbox with a text field, takes in the same parameters as the previous component and two additional parameters: Value and value setter for the text field. The code for the component looks like this:

@Composable
fun CheckboxWithTextField(
    checked: Boolean,
    label: String,
    textValue: String,
    onTextChange: (String) -> Unit,
    onCheckedChange: (Boolean) -> Unit
) {
    Column {
        CheckboxWithLabel(
            checked = checked, 
            label = label, 
            onCheckedChange = onCheckedChange
        )
        if (checked) {
            OutlinedTextField(
                modifier = Modifier
                    .fillMaxWidth()
                label = {
                      Text("Type here")
                },
                value = textValue,
                onValueChange = onTextChange
            )
        }
    }
}

We're using the CheckboxWithLabel component and passing in the required parameters. In addition, if the checkbox is checked, we show an OutlinedTextField, for which we pass the additional parameters.

Finally, we use both of these components to display the options. A list of options with the label and field type mapped together is used in this case. The field types are defined as an enum, which is then used in the list:

enum class OptionType {
    Boolean,
    String
}

val options =
    listOf(
        "Woman" to OptionType.Boolean,
        "Man" to OptionType.Boolean,
        "Non-Binary" to OptionType.Boolean,
        "Let me type" to OptionType.String,
        "Prefer Not to Disclose" to OptionType.Boolean,
    )

Oh, and it should go without saying: Trans-women are women, and trans-men are men. If you want to list them separately, the other options would be "Cis-woman" and "Cis-man". A cis-person is someone whose gender conforms to the one assigned at birth to them.

In the parent component, we're mapping through the options and then setting the correct component:

options.map { (label, type) ->
    when (type) {
        OptionType.Boolean ->
            CheckboxWithLabel(...)
        OptionType.String ->
            CheckboxWithTextField(...)
    }
}

In the example, I've used a component-level state to store the selected values and the text value. In an actual application, the values would be stored somewhere permanently (like a database), so I'm leaving that part out of this blog post.

The complete code I've used is in this Github gist.

Wrapping Up

In this blog post, we've discussed making gender options more inclusive beyond the binary. I hope that after reading this blog post, you'll remember at least these things:

Thank you for reading!

Resources

Other Links in the Blog Post