Labels and announcements
A screen reader speaks a control’s name, then its role and value: “Save, button” or “Volume, slider, 30”. Most widgets take their name from their text, so a button labelled “Save” is named “Save” with no extra work:
// A button with text needs no extra label: its text is its name.
archive := widget.NewButton(lang.L("Archive"), func() {})Name controls that have no text
An icon-only button has nothing to speak, so a screen reader can only say
“button”. Give it a name with
SetAccessibilityLabel:
del := widget.NewIconButton(theme.DeleteIcon(), func() {
status.SetText("Messages deleted")
bellui.AnnounceToScreenReader(lang.L("Messages deleted"), false)
})
del.SetAccessibilityLabel(lang.L("Delete messages"))SetAccessibilityLabel is a method of widget.BaseWidget, so every
standard widget has it. A non-empty label you set replaces the widget’s own
name. It only has an effect on widgets that describe themselves to screen
readers, which most standard widgets do. Translate the label like any other
text the user sees or hears.
Where you need a name:
- Icon-only buttons:
widget.NewIconButton, toolbar actions, floating action buttons without text. - Entries without a placeholder. An entry is named by its placeholder text.
- Entries in a form. The form shows a label next to each field, but it does not link the label to the field, so name the field yourself:
// A form's visible labels are not linked to its fields, and an entry is
// named by its placeholder. Name entries in a form explicitly.
email := widget.NewEntry()
email.SetAccessibilityLabel(lang.L("Email"))
form := widget.NewForm(widget.NewFormItem(lang.L("Email"), email))Bellui names the icon buttons inside its own widgets, such as the previous and next buttons of the date picker.
Announce changes
Some changes happen away from where the user is: a message is sent, a file finishes downloading, a search returns nothing. A sighted user sees it; a screen-reader user hears nothing unless you announce it:
bellui.AnnounceToScreenReader(lang.L("Messages deleted"), false)bellui.AnnounceToScreenReader
asks the screen reader to speak the text. The second argument is
assertive:
falsewaits until the screen reader finishes what it is saying. Use it for most status messages.trueinterrupts it. Keep it for errors and warnings that need attention now.
Call it on the UI goroutine. Without the accessibility build tag, or when
no screen reader is running, it does nothing.
Toasts and snackbars announce their own text when they are shown. Danger and warning toasts are announced assertively, the others politely.
The full program:
// Naming widgets that have no visible text, and announcing status changes
// to a screen reader.
package main
import (
"github.com/belluikit/bellui"
"github.com/belluikit/bellui/app"
"github.com/belluikit/bellui/container"
"github.com/belluikit/bellui/lang"
"github.com/belluikit/bellui/theme"
"github.com/belluikit/bellui/widget"
)
func main() {
a := app.New()
w := a.NewWindow("Labels")
status := widget.NewLabel("3 messages")
del := widget.NewIconButton(theme.DeleteIcon(), func() {
status.SetText("Messages deleted")
bellui.AnnounceToScreenReader(lang.L("Messages deleted"), false)
})
del.SetAccessibilityLabel(lang.L("Delete messages"))
// A button with text needs no extra label: its text is its name.
archive := widget.NewButton(lang.L("Archive"), func() {})
// A form's visible labels are not linked to its fields, and an entry is
// named by its placeholder. Name entries in a form explicitly.
email := widget.NewEntry()
email.SetAccessibilityLabel(lang.L("Email"))
form := widget.NewForm(widget.NewFormItem(lang.L("Email"), email))
w.SetContent(container.NewVBox(status, container.NewHBox(del, archive), form))
bellui.ShowAndRun(w)
}