Accessibility & ARIA
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against GWT 2.13.1 (released 2026-06-19), gwt-maven-plugin 1.3.0 (net.ltgt.gwt.maven), JDK 21 running the compiler with -sourceLevel 17, and the Jakarta Servlet 6 server of the Bookshelf scenario, checked on 2026-10-08.
Accessibility in GWT is client knowledge: the ARIA library is a package of gwt-user (module com.google.gwt.aria.Aria, inherited by com.google.gwt.user.User) that the compiler turns into JavaScript with your code. Nothing is shared with the server and the build needs no extra dependency.
Every example was compiled with the 2.13.1 compiler and loaded in headless Chrome (driven by dispatching keydown events on the focused element); the attribute dumps, compiler messages and focus results quoted below are what the browser and javac printed.
The general theory (what accessibility is, WCAG 2.2, legal standards, validators) is not repeated here; it is in Web Accessibility, and this page is the GWT-specific part.
What the official guide says, and what changed
The Developer Guide page on accessibility is short and has one idea: a widget built from div and span elements is just text to a screen reader, so a widget author must (a) tell the browser what the element is (a role), (b) keep what it is doing up to date (states and properties), and (c) make sure keyboard users can reach it and that focus lands where the screen reader will announce it.
GWT gives you a typed Java API for the attributes (this page’s first half) and nothing magic for the other two: focus and keyboard behaviour are ordinary event handling, which the second half covers with runnable code.
| Release | Change |
|---|---|
1.0.21 |
The release notes announce an |
2.5.0 (RC1) |
"A new accessibility library for setting ARIA roles, states and properties on DOM elements" ( |
2.5.0 (final) |
"The accessibility library introduced in RC1 has been cleaned up for release." |
2.13.1 |
Same library; the |
Source: GWT — Release notes
The 2007 GWT in Action book predates ARIA and does not mention it; treat the book as a source for widget concepts, not for accessibility (it is listed in the references).
The ARIA library at a glance
The package com.google.gwt.aria.client mirrors the WAI-ARIA 1.0 vocabulary as Java types. Its pieces:
| Type | What it is |
|---|---|
|
A |
|
|
|
Static constants of type |
Value types |
Enums for the token attributes ( |
Source: GWT 2.13.1 — Roles.java
The design choice that matters most: the accessor methods live on the role, not on the element. A button supports aria-pressed and aria-expanded, so ButtonRole has setAriaPressedState and setAriaExpandedState; it does not have setAriaCheckedState, because the ARIA role model does not allow that state on a button. The compiler therefore rejects states that make no sense for the role you picked. The official guide recommends going through the role for exactly this reason.
(factory, one per concrete role)"] --> role["Role subtype
ButtonRole, TabRole, TreeitemRole ..."] role --> set["set(element) / remove(element)
writes the role attribute"] role --> state["setAriaXxxState(element, value)
dynamic: follows user actions"] role --> prop["setAriaXxxProperty(element, value)
rarely changes: names, levels, ranges"] role --> extra["setTabindexExtraAttribute(element, int)
the tabindex attribute"] state --> dom["DOM attribute
aria-pressed=true"] prop --> dom extra --> dom2["DOM attribute
tabindex=0"] dom --> at["Browser accessibility tree
screen reader announcement"] dom2 --> at
Role, state, property: three kinds of attribute
The guide’s distinction is worth keeping because it tells you when to write each attribute:
| Kind | Lifetime | When to write it |
|---|---|---|
Role |
Static: the widget’s type does not change. |
Once, in the constructor: |
State |
Dynamic: |
Initialise at construction and update in every handler that changes how the widget looks. The Javadoc of |
Property |
Changes rarely or never: labels, levels, ranges, relationships. |
Normally once. |
The 2.13.1 sources define 9 states in State and 25 properties in Property (the complete list is in the sources linked in the references). Their value types are, in short:
| Attribute | Java accessor on the role | Value type |
|---|---|---|
|
|
|
|
|
one or more |
|
|
one |
|
|
enum |
|
|
|
|
|
enum |
|
|
enum |
|
|
|
|
|
|
|
|
|
Source: GWT 2.13.1 — State.java
What the library does not cover
The library is the ARIA 1.0 vocabulary of 2011. It does not model anything that WAI-ARIA 1.1 and 1.2 added, so there is no SwitchRole, TableRole, CellRole, SearchboxRole or FeedRole, and no accessor for aria-modal, aria-current, aria-errormessage, aria-roledescription or aria-keyshortcuts. The 1.0 drag-and-drop attributes (aria-grabbed, aria-dropeffect) are in the library but are deprecated in current ARIA.
For anything missing, write the attribute directly: element.setAttribute("aria-modal", "true") on a com.google.gwt.dom.client.Element is plain DOM and works with the typed API on the same element (the dialog example below does it). Prefer native HTML elements where the platform has one (table, button, dialog-like patterns), see the advice at the end of this page.
Example 1: a button made from a div
The first Bookshelf widget is an icon button that marks a book as one the reader wants to borrow. It is a div (so that it can show a glyph and be styled), which means nothing is a button unless we say so. The widget does the five things the guide lists: pick the role, set it at construction, set the accessible name, initialise the state, and update the state in the handler.
public class PressableIcon extends FocusPanel {
private boolean pressed;
public PressableIcon(String accessibleName) {
Element el = getElement();
el.setInnerText("☆"); // a star glyph: not a readable name on its own
Roles.getButtonRole().set(el); // role, once
Roles.getButtonRole().setAriaLabelProperty(el, accessibleName); // accessible name
Roles.getButtonRole().setAriaPressedState(el, PressedValue.of(false)); // initial state
setTabIndex(0); // in the tab order
addClickHandler(e -> toggle());
addKeyDownHandler(e -> {
int k = e.getNativeKeyCode();
if (k == KeyCodes.KEY_ENTER || k == ' ') {
e.preventDefault(); // Space would otherwise scroll the page
toggle();
}
});
}
void toggle() {
pressed = !pressed;
Roles.getButtonRole().setAriaPressedState(getElement(), PressedValue.of(pressed)); // state follows the UI
getElement().setInnerText(pressed ? "★" : "☆");
}
}
What the browser held for the widget, and after pressing Enter and Space on it:
before: <div role="button" aria-label="Add to my loans" aria-pressed="false" tabindex="0">
Enter : <div aria-pressed="true">
Space : <div aria-pressed="false">
The div is now announced as "Add to my loans, toggle button, not pressed" instead of as an unnamed star glyph, can be reached with Tab, and answers to Enter and Space the way a button does. A real button element would have given all of that for free; the page keeps the div because that is the situation of every custom widget (the guide’s own advice is to use native controls whenever you can).
GWT’s own CustomButton does the same: its constructor calls Roles.getButtonRole().set(getElement()), a private setAriaPressed writes aria-pressed whenever the button face changes, and setEnabled(false) removes aria-pressed. (The guide’s text says the widget also initialises aria-disabled; the 2.13.1 source does not write that state, which is another reason to read the source.)
Source: GWT 2.13.1 — CustomButton.java
Example 2: a rating slider with a spoken value
A rating from one to five stars is a range, and ARIA has a role for ranges. The values are properties on RangeRole subtypes, so they only exist on the slider role.
public RatingSlider() {
Roles.getSliderRole().set(getElement());
Roles.getSliderRole().setAriaLabelProperty(getElement(), "Your rating");
Roles.getSliderRole().setAriaValueminProperty(getElement(), 1);
Roles.getSliderRole().setAriaValuemaxProperty(getElement(), 5);
setTabIndex(0);
render();
addKeyDownHandler(e -> {
switch (e.getNativeKeyCode()) {
case KeyCodes.KEY_RIGHT: case KeyCodes.KEY_UP: set(value + 1); break;
case KeyCodes.KEY_LEFT: case KeyCodes.KEY_DOWN: set(value - 1); break;
case KeyCodes.KEY_HOME: set(1); break;
case KeyCodes.KEY_END: set(5); break;
default: return;
}
e.preventDefault();
});
}
private void render() {
getElement().setInnerText("★".repeat(value) + "☆".repeat(5 - value));
Roles.getSliderRole().setAriaValuenowProperty(getElement(), value);
// The number alone says little: give the screen reader a word as well.
Roles.getSliderRole().setAriaValuetextProperty(getElement(), value + " of 5, " + WORDS[value]);
}
initial : <div role="slider" aria-valuemin="1" aria-valuemax="5" aria-valuenow="3" aria-valuetext="3 of 5, good" tabindex="0">
3x Right : <div aria-valuenow="5" aria-valuetext="5 of 5, excellent">
Home : <div aria-valuenow="1" aria-valuetext="1 of 5, poor">
The value stops at 5 because set clamps it. The text "3 of 5, good" is user-visible language: it has to come from your message bundle in a localised application (see Internationalization), not from a string literal as in this sketch. That is also the guide’s own objection to hand-written spoken text in hidden elements, below.
Names, descriptions and live regions
A control needs a name ("what is this?"), sometimes a description ("anything I should know?") and, when it changes without taking focus, an announcement. The ARIA properties for these are aria-label, aria-labelledby, aria-describedby and aria-live.
Labels: aria-labelledby and the Id type
A page often has a visible Label next to a widget, but the browser does not know they belong together. aria-labelledby makes the link explicit, and the target is given as an Id. An Id is created with Id.of(Element) (the element must already have an id attribute) or Id.of(String).
label.getElement().setId(Document.get().createUniqueId());
hint.getElement().setId(Document.get().createUniqueId());
status.getElement().setId(Document.get().createUniqueId());
Element in = title.getElement();
// Name, description and "required" are properties: set once.
Roles.getTextboxRole().setAriaLabelledbyProperty(in, Id.of(label.getElement()));
Roles.getTextboxRole().setAriaDescribedbyProperty(in, Id.of(hint.getElement()));
Roles.getTextboxRole().setAriaRequiredProperty(in, true);
// A live region: its role and politeness exist before the first message.
Roles.getStatusRole().set(status.getElement());
Roles.getStatusRole().setAriaLiveProperty(status.getElement(), LiveValue.POLITE);
public boolean validate() {
boolean ok = !title.getText().isEmpty() && title.getText().length() <= 40;
// Invalid is a state: it follows the validation result.
Roles.getTextboxRole().setAriaInvalidState(title.getElement(), ok ? InvalidValue.FALSE : InvalidValue.TRUE);
status.setText(ok ? "Review title accepted." : "Review title is required.");
return ok;
}
textbox : <input aria-labelledby="gwt-uid-1" aria-describedby="gwt-uid-2" aria-required="true">
status : <div role="status" aria-live="polite">
empty -> <input aria-invalid="true"> status="Review title is required."
valid -> <input aria-invalid="false"> status="Review title accepted."
Document.get().createUniqueId() returns gwt-uid-N strings, so the ids cannot clash with each other or with your own. Two details that the output shows: the message is written with setText (never setHTML with text that came from a user, see SafeHtml & XSS), and the live region exists with its role and politeness before the first message, because a screen reader announces changes to a region it already knows.
aria-label is the shortcut when there is no visible text to point at (an icon button, as in example 1). It is a property, takes a String, and must be localised like any other user-visible text.
Live regions for content that moves without focus
Some interfaces change what they show without moving keyboard focus: an auto-complete list, a "3 results" counter, a "saved" confirmation. A screen reader announces the element that has focus, so these changes are silent unless they are in a live region. The guide’s example puts the suggestions in a hidden div with the region role and aria-live set to assertive, and updates its text on every key press.
The LiveValue enum has OFF, POLITE and ASSERTIVE; use POLITE unless the message cannot wait, and use Roles.getStatusRole() or Roles.getAlertRole() when the role itself says how urgent the message is.
The guide also lists the weaknesses of hand-written spoken text, and they still hold: each developer picks different wording (an inconsistent experience between applications), and the words that are never shown on screen ("Suggestions") are easy to forget to translate. Whenever a role plus states say the same thing, prefer them to custom text, because translating the spoken terms is then the screen reader’s job.
Focus management
Screen readers speak what has focus, so for a widget author "accessible" mostly means "focus is where the user expects it". GWT has the pieces; the discipline is yours.
or select do the job?"} native -- "yes" --> use["Use it: focus, keys and
announcements come free"] native -- "no" --> single{"Single control?"} single -- "yes" --> tab0["role + tabindex 0 +
key handler (Enter, Space)"] single -- "no, a group of items" --> group{"Do the items
hold other focusable content?"} group -- "no" --> rove["Roving tabindex:
0 on the current item, -1 on the rest,
arrows move real focus"] group -- "yes, or too many to focus" --> desc["One focusable container +
aria-activedescendant -> item id"]
Tab order: tabindex
By default only links and form controls take focus. Setting tabindex to 0 puts any element in the natural tab order; -1 takes it out of the tab order but still allows focus() from code. Two ways to set it from GWT:
-
The
Focusableinterface (setTabIndex(int),getTabIndex(),setFocus(boolean),setAccessKey(char)), implemented byFocusWidget(buttons, text boxes, list boxes),FocusPaneland the cell widgets. -
The ARIA library, for an element that is not a widget:
Roles.getHeadingRole().setTabindexExtraAttribute(heading.getElement(), 0)(this is the guide’s example of making a clickable heading reachable).
Every FocusWidget is focusable by default. The guide’s caveat holds: extending FocusWidget does not make a widget focusable if the base element is not a natively focusable one. For arbitrary elements wrap them in a FocusPanel, which creates a focusable div, and test in the browsers you support.
Moving focus: FocusPanel.setFocus is deferred in WebKit permutations
Focusable.setFocus(true) is not the same call for every widget. FocusPanel and FocusWidget use two different FocusImpl instances, and the WebKit/Blink implementation (FocusImplSafari, used by the user.agent value safari, which Chrome belongs to) moves focus from a zero-delay timeout, because "attempts to focus elements from within an event callback will generally be unsuccessful". A Button.setFocus(true) is immediate; a FocusPanel.setFocus(true) is not.
setFocus(true) returned; active=BUTTON <- still the previous element (a Button)
60 ms later: active is the button=true <- the FocusPanel's div, after the timeout
So code that moves focus and then reads or relies on it (document.activeElement, a test, a second focus() call) must wait for the next turn of the event loop, for example with Scheduler.get().scheduleDeferred(...) or a Timer. Calling setFocus(true) on a FocusPanel and then immediately focusing something else leaves focus on the second element, which is not the order you wrote. GWT has no Document.getActiveElement() (the compiler says The method getActiveElement() is undefined for the type Document); read it with a one-line JSNI method, /*-{ return $doc.activeElement; }-*/.
Roving tabindex for toolbars, tab bars and menus
A toolbar with ten buttons should cost one Tab press, not ten. The standard pattern: exactly one item has tabindex="0" (the current one), the others -1; the arrow keys move the 0 and the real focus. GWT’s MenuBar does this by hand: its root menu is in the tab sequence and its sub-menus are reset to -1.
public RovingToolbar(String label, String... names) {
Roles.getToolbarRole().set(getElement());
Roles.getToolbarRole().setAriaLabelProperty(getElement(), label);
for (String n : names) {
FocusPanel b = new FocusPanel();
b.getElement().setInnerText(n);
Roles.getButtonRole().set(b.getElement());
b.setTabIndex(items.isEmpty() ? 0 : -1); // only the first item is a Tab stop
items.add(b);
add(b);
}
addDomHandler(e -> { // key events bubble up to the toolbar
int k = e.getNativeKeyCode(), next = current;
if (k == KeyCodes.KEY_RIGHT) next = (current + 1) % items.size();
else if (k == KeyCodes.KEY_LEFT) next = (current + items.size() - 1) % items.size();
else if (k == KeyCodes.KEY_HOME) next = 0;
else if (k == KeyCodes.KEY_END) next = items.size() - 1;
else return;
e.preventDefault();
items.get(current).setTabIndex(-1);
current = next;
items.get(current).setTabIndex(0);
items.get(current).setFocus(true);
}, KeyDownEvent.getType());
}
Right : active=Reserve tabindexes=Borrow=-1 Reserve=0 Review=-1
Right x2 more: active=Borrow tabindexes=Borrow=0 Reserve=-1 Review=-1
Home : active=Borrow
Because the FocusPanel focus call is deferred (previous section), the lab read the active element a few milliseconds after each key. A single listener on the container, instead of one per item, is also what the guide recommends for large trees: key events bubble, so the root can handle them for all items.
Many items or nested widgets: aria-activedescendant
When the items are too many to focus, or contain focusable content of their own (a tree item with a link in it), the guide’s second technique applies: one naturally focusable element holds focus, handles all the keys, and points at the current item with aria-activedescendant, whose value is the item’s id. GWT’s Tree does exactly that: it creates a hidden focusable element with role tree, gives every item a unique id and the role treeitem, and updates the property after each selection change.
Tree focusable : <div role="tree" tabindex="0">
after select : <div role="tree" aria-activedescendant="gwt-uid-5">
treeitem : <div class="gwt-TreeItem gwt-TreeItem-selected" role="treeitem" id="gwt-uid-5" aria-level="1" aria-setsize="1" aria-posinset="1" aria-expanded="true" aria-selected="true">
The guide documents the weakness of the technique: a screen reader may read only the text of the item that aria-activedescendant points at and ignore its role and states, which is enough for a list box but not for a tree (which level am I on?). Some screen readers interpret the item as if it had focus, some do not, so test with the ones your users have. Also note the generated markup: the second item of the lab’s tree (not yet selected) had a role and id but no aria-level yet, because Tree writes level, set size, position and expanded state when an item is selected.
Dialogs: put focus in, give it back
DialogBox and PopupPanel set no ARIA role. A modal-looking panel therefore needs three things from you: a role (dialog) with a name, focus moved inside when it opens, and focus returned to the element that opened it when it closes.
Element opener = active(); // remember who had focus (JSNI helper above)
DialogBox dlg = new DialogBox(false, true); // autoHide=false, modal=true
dlg.setText("Loan this book");
Roles.getDialogRole().set(dlg.getElement());
Roles.getDialogRole().setAriaLabelProperty(dlg.getElement(), "Loan this book");
dlg.getElement().setAttribute("aria-modal", "true"); // not in the 2.13.1 library: plain attribute
...
dlg.center();
who.setFocus(true); // first field; a TextBox is a FocusWidget: immediate
close.addClickHandler(e -> { dlg.hide(); opener.focus(); });
dialog open : active is textbox=true <div role="dialog" aria-label="Loan this book" aria-modal="true">
dialog closed: focus back on opener=true
Source: Javadoc — DialogBox
Keyboard navigation in cell widgets
The cell widgets (CellList, CellTable, DataGrid, CellTree, CellBrowser) implement keyboard navigation themselves: one element in the whole widget carries tabindex="0" (the keyboard-selected row or cell), and the arrow keys move that highlight and the real focus. Two enums configure it, on every AbstractHasData display (the list, table and grid) through HasKeyboardSelectionPolicy and HasKeyboardPagingPolicy:
| Setting | Values | Meaning |
|---|---|---|
|
|
|
|
|
|
|
|
The |
Which keys do what
The keys are handled by AbstractHasData.DefaultKeyboardSelectionHandler, a CellPreviewEvent.Handler installed by default; the table widgets use the subclass AbstractCellTable.CellTableKeyboardSelectionHandler, which adds the horizontal movement between cells.
| Key | Effect |
|---|---|
Down, Up |
Next and previous row. |
Page Down, Page Up |
Next and previous page ( |
Home, End |
First and last row of the data. |
Right, Left (tables only) |
Next and previous interactive column; past the last one it wraps to the next row, past the first to the previous row. The two keys swap in a right-to-left locale ( |
Space |
|
Enter |
Delivered to the cell: |
"Interactive" means a column whose cell consumes events: a TextColumn does not, a ButtonCell column does. In a table with no interactive column the horizontal keys have nothing to move between, so Right and Left just step through rows. The tabindex follows the keyboard-selected cell.
Measured in a CellTable
The lab table had two TextColumn`s (Title, Author) and a `ButtonCell column ("Borrow") over three Bookshelf rows. Each key was dispatched on the focused element; focus follows the keyboard selection a moment later, so each step reads the result in the next turn:
defaults : policy=ENABLED paging=CHANGE_PAGE getTabIndex()=0
Down : row=1 col=2 active=<div tabindex="0"> (the first interactive column gets the keyboard selection)
Right : row=2 col=2 (no further interactive column: wrapped to the next row)
Right : row=2 col=2 (end of the table: nothing moves)
End : row=2 col=2
Home : row=0 col=2
Down x3 more : row=2 page=0 (stops at the last of the 3 rows, same page)
DISABLED : the cell div has no tabindex any more
The same table showed these attributes: the SimplePager buttons are img elements with role="button", aria-label ("First page" and so on) and aria-disabled (written by the pager, Roles.getButtonRole().setAriaLabelProperty and setAriaDisabledState in SimplePager), while the table’s own table, header th and rows carry no ARIA attributes at all. CellTree is the cell widget that does: its root has role tree and every node role treeitem with aria-level, aria-setsize, aria-posinset and aria-expanded.
What you must add yourself
-
A name for the table. Give the widget’s element an
aria-labeloraria-labelledby, as in the review form above (CellTableextendsComposite, sotable.getElement()is thetableelement;Roles.getGridRole()is available if you want the grid semantics). -
Keyboard access to your own cells. A cell must declare the events it uses in its constructor (
super("click", "keydown")) and handle Enter inonEnterKeyDown. Aspanwith a click handler is not focusable; use a button element, ortabindexplus Enter handling asClickableTextCelldoes. Cell Widgets has the full cell-writing guide. -
Not trapping the user. Keyboard navigation inside a cell widget uses Tab only to enter and leave. If you set
KeyboardSelectionPolicy.DISABLEDto avoid a focus highlight, you remove the way into the widget for keyboard users unless your cells are focusable elements. -
Selection state. The selected style is only a CSS class on a
CellTablerow. Tell assistive technology about it yourself (aria-selectedon the row element in a customRowStylesor cell template) if the selection matters.
What the built-in widgets already expose
Before adding anything, see what the widget does. This table combines the GWT sources with the attributes read in the lab’s DOM.
| Widget | Roles, states and properties |
|---|---|
|
|
|
Focusable element |
|
Root |
|
|
|
Root |
|
Buttons with |
|
Day cells get |
|
|
|
Sets |
|
Nothing; they are native HTML elements and need nothing. Give them a name with a |
The guide’s statement that "all widgets that inherit from FocusWidget now have a tabindex by default" matches the code (FocusWidget.onAttach sets a tab index of 0 when it is -1), so a custom FocusWidget appears in the tab order without more work.
The deprecated Accessibility class
com.google.gwt.user.client.ui.Accessibility is the pre-2.5 API. It is a class of string constants (ROLE_TREE, ROLE_BUTTON, ROLE_TAB, …, STATE_EXPANDED, STATE_PRESSED, …) and four static methods, setRole, getRole, setState and removeState. It writes the attribute you name with the value you pass, so a typo is accepted silently. Its Javadoc marks it @Deprecated and tells you to use Roles instead:
Dep.java:3: warning: [deprecation] Accessibility in com.google.gwt.user.client.ui has been deprecated
public class Dep { void f(Element e) { Accessibility.setRole(e, Accessibility.ROLE_BUTTON); } }
It still works in 2.13.1 and compiles with that warning (the old and new API wrote the same attributes in the lab: <div role="tree" aria-expanded="true">, and Roles.getTreeRole().set(...) then replaced the role).
The mapping to the new API is mechanical:
| Old (deprecated) | New |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
the role’s |
Source: GWT 2.13.1 — Accessibility.java
The old API has only the eight roles and eight states of its constants; the new one covers the full 1.0 vocabulary with compile-time checking.
Bookshelf worked example: an accessible book card
The Bookshelf catalogue page, shown with Cell Widgets, is a DataGrid/CellTable of books. Putting this page together for one book detail panel gives the following inventory, which is what the lab above built and ran:
| Element | ARIA / focus | Why |
|---|---|---|
"Add to my loans" icon ( |
|
An icon has no text; a toggle must say whether it is on. |
Rating ( |
|
A star row is a range, with a spoken word as well as a number. |
Review form ( |
|
Name, hint and validation feedback without relying on colour or position. |
Actions toolbar ( |
|
One Tab stop for the group, arrows inside. |
Loan dialog ( |
|
|
Catalogue ( |
Default keyboard policy, an |
Cell widgets bring their keyboard model; the name and the action are yours. |
Testing accessibility
The guide gives three layers of checks, and they still apply:
-
The DOM. ARIA attributes are plain DOM attributes, so the browser developer tools show whether they are in the right place. This is the quickest check and the one used for every dump on this page.
-
The accessibility tree. Current browsers expose the computed tree (roles, names, states) in their developer tools; this is what a screen reader receives, and it is the modern replacement for the guide’s Windows-only event watcher.
-
A screen reader. The only real test of whether the result makes sense. The guide’s list of screen readers (JAWS, Window-Eyes, FireVox, ChromeVox) is dated: Window-Eyes, FireVox and the ChromeVox extension of that time are discontinued. Today test with NVDA, JAWS, VoiceOver and TalkBack.
Automated checkers and the WCAG tool list are in Web Accessibility; they cannot judge whether aria-valuetext is meaningful, which is why a manual pass remains necessary.
A GWT-specific caveat: assertions are on in Super Dev Mode, so assert failures inside the ARIA library (for example a null element) surface as errors during development and disappear from a production build (see Compiler & Compilation).
Common pitfalls
All messages below were produced by compiling or running the code in the lab, or are found in the 2.13.1 sources.
| Mistake | Symptom | Fix |
|---|---|---|
Copying the guide’s |
|
The type is |
Copying the guide’s |
|
The getter takes no argument; apply it with |
Setting a state the role does not support, such as |
|
Choose the role that supports the state ( |
Passing a |
|
Use |
The |
It does not exist in the library. |
ARIA has |
The guide’s |
The two methods set |
Set |
Two roles on one element |
|
One role per element. |
|
It is accepted ( |
Give the target an id first ( |
Calling |
|
Defer the read, or use a |
Looking for |
|
JSNI: |
Hiding with a style instead of |
|
|
|
No |
Restyle the focus classes ( |
Reading |
The attribute is absent on unselected items. |
Do not rely on those attributes in tests; they are written on selection. |
Recent changes and outdated material
-
The ARIA library is generated code built for ARIA 1.0 (every file carries the "auto-generated code" header), and nothing in the 2.13.1 sources extends it. If you need ARIA 1.1 or 1.2 attributes, set them as plain attributes.
-
The Developer Guide page still shows the pre-2.5 API names in two code samples (
IdReference,Roles.getRegionRole(element)) and names obsolete screen readers; the library source and the compiler are the authority. -
The GWT in Action book (2007) has no accessibility chapter and predates ARIA.
References
-
GWT Developer Guide — Accessibility: Indicating Selection Changes
-
GWT Developer Guide — Accessibility: Associating Meaningful Labels
-
GWT Developer Guide — Accessibility: Automatically Speaking Highlighted Content
-
Javadoc — HasKeyboardSelectionPolicy.KeyboardSelectionPolicy
-
Javadoc — AbstractCellTable.CellTableKeyboardSelectionHandler
-
W3C — ARIA Authoring Practices Guide: Developing a Keyboard Interface
-
Robert Hanson and Adam Tacy, GWT in Action (Manning, 2007), consulted only for outdated notes