Pantrix UI

Accordion

Sections whose content folds away under their headers, so that a long page can be read a part at a time.

import com.pantrixui.components.container.accordion.PantrixAccordion

Examples

Frequently asked questions: a line between them

The plainest form, questions and answers. The keys of the open sections are the screen's state and are kept with rememberSaveable; the accordion takes them as expanded and, when a header is pressed, reports the new set with onExpandedChange. Any number of sections stay open at once.

No variant is given: Plain, a line between the sections and nothing around them. A long title that does not fit on one line runs onto a second, and the header grows with it. The last section's enabled is false: it cannot be pressed, is written in the disabled text's colour and stays closed.

var open by rememberSaveable { mutableStateOf(setOf("delivery")) }
PantrixAccordion(
    sections = listOf(
        AccordionSection(key = "delivery", title = "When will my order arrive?") {
            PantrixText(text = "Within two working days. Orders placed before noon are delivered the next day.")
        },
        AccordionSection(key = "returns", title = "How do I return an item?") {
            PantrixText(text = "Within thirty days, in its box. Shipping is on us.")
        },
        AccordionSection(
            key = "gift",
            title = "Can I have my order gift-wrapped, and can I have a note put inside the parcel?"
        ) { PantrixText(text = "Yes. Tick the gift-wrap option at checkout.") },
        AccordionSection(key = "invoice", title = "Company invoice", subtitle = "Coming soon", enabled = false) {
            PantrixText(text = "Not available yet.")
        }
    ),
    expanded = open,
    onExpandedChange = { open = it },
    tagKey = "faq"
)

Inside one edge, with a count at a header's end

AccordionVariant.Bordered puts the sections inside one edge; there is still a line between them.

The icon before the title is drawn in the title's colour, and a screen reader passes over it. The count at the header's end is given with trailing: it stands between the title and the chevron, is a part of the header's button, and a screen reader reads it with the title. Here it is written in small type and is given no colour.

var open by rememberSaveable { mutableStateOf(setOf("open")) }
PantrixAccordion(
    sections = listOf(
        AccordionSection(
            key = "open",
            title = "Open orders",
            icon = PantrixIcons.Folder,
            trailing = { PantrixText(text = "2", style = PantrixTheme.typography.footnote) }
        ) {
            PantrixText(text = "SIP-2026-0914 · On its way")
            PantrixText(text = "SIP-2026-0921 · Preparing")
        },
        AccordionSection(
            key = "past",
            title = "Past orders",
            icon = PantrixIcons.Calendar,
            trailing = { PantrixText(text = "14", style = PantrixTheme.typography.footnote) }
        ) { PantrixText(text = "Last order: 12 September") }
    ),
    expanded = open,
    onExpandedChange = { open = it },
    variant = AccordionVariant.Bordered,
    tagKey = "orders"
)

Each section a card, only one open at a time

AccordionVariant.Separated makes each section a card of its own, with a gap between them. With singleExpanded only one section stays open at a time: opening one closes the one that was open. It is for pages where two open sections would push the rest off the screen.

subtitle is the second line, under the title, and gives the section's summary: flip the switches, and the subtitle of "Notifications" changes between "On" and "Off".

var open by rememberSaveable { mutableStateOf(setOf("notifications")) }
var email by rememberSaveable { mutableStateOf(true) }
var push by rememberSaveable { mutableStateOf(false) }
PantrixAccordion(
    sections = listOf(
        AccordionSection(
            key = "notifications",
            title = "Notifications",
            subtitle = if (email || push) "On" else "Off",
            icon = PantrixIcons.Star
        ) {
            PantrixSwitchButton(
                checked = email,
                onCheckedChange = { email = it },
                label = "Email",
                tagKey = "email"
            )
            PantrixSwitchButton(checked = push, onCheckedChange = { push = it }, label = "Push", tagKey = "push")
        },
        AccordionSection(
            key = "account",
            title = "Account",
            subtitle = "ada@pantrix.io",
            icon = PantrixIcons.Person
        ) { PantrixText(text = "Member since 2024") }
    ),
    expanded = open,
    onExpandedChange = { open = it },
    variant = AccordionVariant.Separated,
    singleExpanded = true,
    tagKey = "settings"
)

A section that closes keeps what was typed

A closed section's content is not composed at all; yet the section keeps, even while it is closed, everything that was saved to be restored. The field keeps its text with rememberTextFieldState(), and that state is saved.

Type something into the "Delivery note" section, close the section and open it again: when the section closes the field leaves the screen, and when it opens again what you typed is there.

var open by rememberSaveable { mutableStateOf(setOf("note")) }
PantrixAccordion(
    sections = listOf(
        AccordionSection(key = "note", title = "Delivery note") {
            PantrixTextField(
                state = rememberTextFieldState(),
                modifier = Modifier.fillMaxWidth(),
                label = "Note for the courier",
                placeholder = "The doorbell does not work, please knock",
                tagKey = "accordion_note"
            )
        },
        AccordionSection(key = "address", title = "Delivery address") {
            PantrixText(text = "Pantrix A.Ş., Maslak, İstanbul")
        }
    ),
    expanded = open,
    onExpandedChange = { open = it },
    variant = AccordionVariant.Bordered,
    tagKey = "remembers"
)

Opening and closing from outside

Since the open sections are the screen's state, they change from outside the accordion too. The two buttons above press no header; they only change the set: one puts every key into it, the other empties it. The accordion draws the new set.

var open by rememberSaveable { mutableStateOf(emptySet<String>()) }
Row(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixButton(
        text = "Open all",
        onClick = { open = setOf("monday", "tuesday", "wednesday") },
        variant = ButtonVariant.Outline,
        size = ButtonSize.Small,
        tagKey = "accordion_open_all"
    )
    PantrixButton(
        text = "Close all",
        onClick = { open = emptySet() },
        variant = ButtonVariant.Text,
        size = ButtonSize.Small,
        tagKey = "accordion_close_all"
    )
}
PantrixAccordion(
    sections = listOf(
        AccordionSection(key = "monday", title = "Monday") { PantrixText(text = "09.00 Weekly planning") },
        AccordionSection(key = "tuesday", title = "Tuesday") { PantrixText(text = "14.00 Design review") },
        AccordionSection(key = "wednesday", title = "Wednesday") { PantrixText(text = "No meetings") }
    ),
    expanded = open,
    onExpandedChange = { open = it },
    variant = AccordionVariant.Bordered,
    tagKey = "week"
)

All disabled

With enabled = false no header can be pressed and every section is held as it is: an open one stays open and a closed one closed. The headers are written in the disabled text's colour. A section's own enabled does the same for that section alone.

PantrixAccordion(
    sections = listOf(
        AccordionSection(key = "summary", title = "Order summary", subtitle = "Confirmed") {
            PantrixText(text = "The order is confirmed; it can no longer be changed.")
        },
        AccordionSection(key = "payment", title = "Payment") { PantrixText(text = "Paid by card") }
    ),
    expanded = setOf("summary"),
    onExpandedChange = {},
    variant = AccordionVariant.Bordered,
    enabled = false,
    tagKey = "locked"
)

In its own colours

colors gives the accordion the colours set there in place of the theme's: here, cards in a colour the theme does not have. The fill is given with container; the headers and the content take the content colour, and the line is given separately, with divider. A colour that is left out stays the theme's.

val own = ownColors()
var open by rememberSaveable { mutableStateOf(setOf("plan")) }
PantrixAccordion(
    sections = listOf(
        AccordionSection(key = "plan", title = "Plan", subtitle = "Team") {
            PantrixText(text = "Up to ten users, billed monthly.")
        },
        AccordionSection(key = "usage", title = "Usage") { PantrixText(text = "48,2 million requests this month") }
    ),
    expanded = open,
    onExpandedChange = { open = it },
    variant = AccordionVariant.Separated,
    colors = AccordionColors(container = own.container, content = own.onContainer, divider = own.color),
    tagKey = "own"
)

On a fill

The Plain and Bordered variants have no fill of their own: they stand on what is behind them. Here the accordion is inside a surface painted with a role's fill, and is given no colours: the titles, the subtitle and the chevron are written in the colour that fill gives what stands on it, and the lines are drawn in a share of that colour. The theme's line would not read on this fill.

var open by rememberSaveable { mutableStateOf(setOf("shipping")) }
PantrixSurface(modifier = Modifier.fillMaxWidth(), color = PantrixTheme.colors.primary.container) {
    PantrixAccordion(
        sections = listOf(
            AccordionSection(key = "shipping", title = "Shipping", subtitle = "Free", icon = PantrixIcons.Inbox) {
                PantrixText(text = "Ships tomorrow before noon.")
            },
            AccordionSection(key = "warranty", title = "Warranty") { PantrixText(text = "Two years") }
        ),
        expanded = open,
        onExpandedChange = { open = it },
        modifier = Modifier.padding(PantrixTheme.spacing.sm),
        tagKey = "on_a_fill"
    )
}

In its own style

style changes the accordion's geometry; only the fields that are given change, and the rest comes from the platform's style. Here the header's least height is set to 64dp and the gap between the cards to 16dp; the corners are not a step of the brand's shape scale but a fixed 20dp: ShapeSpec.Fixed.

var open by rememberSaveable { mutableStateOf(setOf("first")) }
PantrixAccordion(
    sections = listOf(
        AccordionSection(key = "first", title = "Step one") { PantrixText(text = "Create your account.") },
        AccordionSection(key = "second", title = "Step two") { PantrixText(text = "Verify your email.") }
    ),
    expanded = open,
    onExpandedChange = { open = it },
    variant = AccordionVariant.Separated,
    style = AccordionStyle(
        shape = ShapeSpec.Fixed(RoundedCornerShape(20.dp)),
        headerMinHeight = 64.dp,
        sectionGap = 16.dp
    ),
    tagKey = "own_style"
)

API

sections

Sections whose content folds away under their headers, so that a long page can be read a part at a time.

ParameterTypeDefaultWhat it is
sectionsList<AccordionSection>the sections, in the order they are shown. No two of them have one key.
expandedSet<String>the keys of the sections that are open.
onExpandedChange(Set<String>) -> Unitcalled with the keys that are to be open after a header was pressed. The accordion changes nothing by itself.
modifierModifierModifierthe accordion's modifier. The accordion is as wide as the room it is given and as tall as its headers and what the open ones show.
variantAccordionVariantAccordionVariant.Plainhow the sections are told apart: by a line between them, by an edge around the set, or each as a card of its own.
singleExpandedBooleanfalsewhether opening a section closes whichever was open: for a page where two open sections would push the rest off the screen.
enabledBooleantruewhether the headers can be pressed. False holds every section as it is, and a section says the same of itself alone.
styleAccordionStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform.
colorsAccordionColors?nullthis accordion's own colours, where they are not the theme's: the ones set here, with the theme's for the rest.
tagKeyString?nullthe accordion's automation key: the accordion is Accordion_<tagKey>, a header in it Accordion_<tagKey>.Header_<key> and what it opens Accordion_<tagKey>.Content_<key>. A press is reported as an Accordion event: expand and collapse, with the section's key.

AccordionStyle

PantrixAccordion's geometry: the room in a section's header and around what the header opens, and the edge.

ParameterTypeDefaultWhat it is
shapeShapeSpecShapeSpec.UnspecifiedThe corners of the edge around the set in AccordionVariant.Bordered, and around each section in AccordionVariant.Separated: a step of the brand's shape scale, or a fixed shape.
headerMinHeightDpDp.UnspecifiedThe least height of a header. A title on two lines, a subtitle or a larger type makes it taller, and the platform's touch target is its floor.
horizontalPaddingDpDp.UnspecifiedThe room between a header's sides and what it holds.
verticalPaddingDpDp.UnspecifiedThe room over and under a header's lines, where they need more than the least height.
contentPaddingDpDp.UnspecifiedThe room at the sides of what an open section shows, and under it.
iconGapDpDp.UnspecifiedBetween a header's icon, its title, what it has at its end and its chevron.
iconSizeDpDp.UnspecifiedThe width and height of a header's icon and of its chevron.
borderWidthDpDp.UnspecifiedThe width of the lines between sections, and of the edges. Zero draws none.
sectionGapDpDp.UnspecifiedBetween one section and the next in AccordionVariant.Separated.

AccordionColors

What a PantrixAccordion is painted with, for an accordion that does not take the theme's colours: a colour that is set is used, and one that is left out stays the theme's.

ParameterTypeDefaultWhat it is
containerColorColor.UnspecifiedThe fill behind the sections. None in AccordionVariant.Plain and AccordionVariant.Bordered, which stand on what is behind them, and the theme's surface under each section of AccordionVariant.Separated.
contentColorColor.UnspecifiedA header's title and its icon, and what a section shows where that gives no colour of its own. The theme's textPrimary, or on a fill that is not the accordion's, the colour that fill gives what stands on it. A subtitle and a chevron are the theme's textSecondary on the theme's own grounds, and this colour elsewhere and where it is given.
dividerColorColor.UnspecifiedThe lines between sections, and the edges. The theme's divider on the theme's own grounds. On a fill, where that line would not read, a share of content: the edge a subtle button has.

Accessibility