Skip to content
Labels and announcements

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:

  • false waits until the screen reader finishes what it is saying. Use it for most status messages.
  • true interrupts 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)
}