This guide provides practical examples and best practices for making your Summon applications accessible to everyone, including people with disabilities.
The AccessibleElement component serves as a wrapper for adding accessibility attributes to UI elements:
// Basic usage
AccessibleElement(
role = AccessibilityUtils.NodeRole.BUTTON,
label = "Add to cart",
) {
Box(modifier = Modifier.padding("10px").backgroundColor("#0066cc")) {
Text("Add to cart")
}
}
// With custom role
AccessibleElement(
customRole = "banner",
label = "Site header",
) {
// Header content
}
// With relationships to other elements
AccessibleElement(
role = AccessibilityUtils.NodeRole.TEXTBOX,
label = "Email address",
relations = mapOf(
"describedby" to "email-hint",
"errormessage" to "email-error"
)
) {
// Email input field
}
Use these components to create a properly structured document that conveys meaning to assistive technologies:
Header {
Heading(level = 1) {
Text("My Application")
}
Nav {
// Navigation links
}
}
Main {
Section {
Heading(level = 2) {
Text("Features")
}
Text("Features content")
}
Article {
Heading(level = 2) {
Text("Latest News")
}
Text("News content")
}
}
Aside {
Heading(level = 2) {
Text("Related Information")
}
// Sidebar content
}
Footer {
Text("© 2023 My Company")
}
ARIA (Accessible Rich Internet Applications) attributes provide additional semantics to make web content more accessible:
// Button with accessible label
Button(
onClick = { /* handle click */ },
modifier = Modifier
.ariaLabel("Close dialog")
.ariaControls("main-dialog")
)
// Checkbox with state
Checkbox(
checked = isChecked,
onCheckedChange = { isChecked = it },
modifier = Modifier
.ariaChecked(isChecked)
.ariaDescribedBy("checkbox-hint")
)
// Custom tab implementation
Box(
modifier = Modifier
.role("tab")
.ariaSelected(isSelected)
.tabIndex(if (isSelected) 0 else -1)
)
Proper keyboard focus management is essential for accessibility:
// Make an element focusable but not in tab order
Box(
modifier = Modifier
.focusable()
.onClick { /* handle click */ }
)
// Make an element keyboard tabbable
Box(
modifier = Modifier
.tabbable()
.onClick { /* handle click */ }
)
// Disable an element
Button(
onClick = { /* handle click */ },
enabled = false,
modifier = Modifier.disabled()
)
// Auto-focus an element when it appears
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.autoFocus()
)
The AccessibilityUtils object provides functions for working with accessibility attributes:
// Create a Modifier with a specific role
val buttonModifier = AccessibilityUtils.createRoleModifier(AccessibilityUtils.NodeRole.BUTTON)
// Create a Modifier with an accessible label
val labelModifier = AccessibilityUtils.createLabelModifier("Close dialog")
// Create a Modifier with relationship to another element
val relationModifier = AccessibilityUtils.createRelationshipModifier("describedby", "description-id")
// Inspect accessibility attributes on a Modifier
val modifier = Modifier()
.role("button")
.ariaLabel("Close")
val accessibilityAttrs = modifier.inspectAccessibility()
// Result: {"role": "button", "aria-label": "Close"}
// Visible focus indicator
Button(
onClick = { /* handle click */ },
modifier = Modifier
.tabbable()
.backgroundColor("#ffffff")
.focusStyle(
Modifier.border("2px", "solid", "#0066cc")
)
)
// Accessible image with alternative text
Image(
src = "/assets/logo.png",
alt = "Company Logo",
modifier = Modifier.ariaHidden(false)
)
// Hidden decorative image
Image(
src = "decoration.png",
alt = "",
modifier = Modifier.ariaHidden(true)
)
// Error state with both color and icon
if (hasError) {
Box(
modifier = Modifier
.color("#d32f2f") // Ensure this has proper contrast
.ariaDescribedBy("error-message")
) {
Icon("error")
Text("This field is required", id = "error-message")
}
}
// Accessible form field with label and validation
FormField {
Label(for = "email-input") {
Text("Email address")
if (isRequired) {
Text("*", modifier = Modifier.ariaHidden(true))
Text(" (required)", modifier = Modifier.className("visually-hidden"))
}
}
TextField(
value = email,
onValueChange = { email = it },
modifier = Modifier
.id("email-input")
.ariaRequired(isRequired)
.ariaInvalid(hasError)
.ariaDescribedBy(if (hasError) "email-error" else "email-hint")
)
if (hasError) {
Text(
"Please enter a valid email address",
id = "email-error",
modifier = Modifier
.color("#d32f2f")
.ariaLive("assertive")
)
} else {
Text(
"Enter your email address in format: name@example.com",
id = "email-hint",
modifier = Modifier.color("#666666")
)
}
}
Keyboard Testing:
Screen Reader Testing:
Automated Testing:
Manual Testing: