Right Sized Grouping

Status: ready

Intent

Group what belongs together. Don't bury controls the user needs to reach directly.

Problem

Too little grouping is Label Soup. Too much is Over Grouped Control. Both come from grouping by how the layout is nested in code instead of by task.

Solution

Verdict: static information about one object is one element. Frequent controls stay separate. Rare secondary actions can be accessibility actions. Editable content always stays separate.

Why: the user hears less noise and still reaches every control the task needs.

UI piece Group? Why
Decorative image + title Yes, hide the image The image isn't part of the task
Title + status + amount Yes One object summary
Favorite button on every card Maybe Custom action, or a button with the object name
Add to cart No Primary action
Text fields No Each one is edited separately
Payment method options No Selection needs its own role and state

Implementation

Summary + primary action:

VStack(alignment: .leading) {
    ProductSummary(tea)
        .accessibilityElement(children: .combine)

    Button("Add to cart") { add(tea) }
        .accessibilityLabel("Add \(tea.name) to cart")
        .accessibilityInputLabels(["Add to cart", "Add \(tea.name) to cart"])
}

Summary + secondary action, one stop:

AppointmentRow(appointment)
    .accessibilityElement(children: .ignore)
    .accessibilityLabel(appointment.title)
    .accessibilityValue(appointment.timeText)
    .accessibilityAction(named: "Cancel appointment") { confirmCancel(appointment) }

Form fields are never grouped:

// Bad: one element, nothing can be edited
VStack {
    TextField("Card number", text: $card)
    TextField("Expiration date", text: $date)
    TextField("Security code", text: $cvc)
}
.accessibilityElement(children: .combine)

// Good: three fields
TextField("Card number", text: $card)
TextField("Expiration date", text: $date)
TextField("Security code", text: $cvc)

Check

Profile:   VoiceOver
Task:      complete the row's task
Expected:  the summary is heard once
           the primary action is reachable
           every editable or selectable control is its own stop
Profile:   Switch Control
Task:      scan the list and activate one item
Expected:  fewer stops than before grouping
           required controls are still separate targets

Count stops before and after, and complete the task after. A lower count alone doesn't prove the grouping is right.

Pitfalls

  1. Combining editable fields.
  2. A destructive action inside a vague custom action.
  3. Grouping to make up for a confusing visual layout.
  4. A custom action as the only way to complete checkout.

WCAG mapping

  1. 1.3.1 Info and Relationships: related content is exposed as related.
  2. 2.4.3 Focus Order: focus order matches the task.
  3. 4.1.2 Name, Role, Value: controls keep their role and state.
  1. Label Soup
  2. Over Grouped Control
  3. Reading Order Drift
  1. Chapter 5
  2. Chapter 10
  3. Chapter 11

Sources

  1. Apple Developer: SwiftUI accessibilityElement(children:)
  2. Apple Human Interface Guidelines: Accessibility