-
Notifications
You must be signed in to change notification settings - Fork 957
IJPL-175712 Accessibility UI guidelines #1505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dmitrii-drobotov
wants to merge
3
commits into
JetBrains:main
Choose a base branch
from
dmitrii-drobotov:accessibility-ui-guidelines
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,342 @@ | ||
<!-- Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. --> | ||
|
||
# Accessibility | ||
|
||
<link-summary>Making the UI accessible to more people.</link-summary> | ||
|
||
Accessibility means building user interfaces that everyone can use, regardless of their abilities. | ||
It allows people with different needs, such as those who rely on assistive technologies or need keyboard-only navigation, to fully interact with the product. | ||
Follow these guidelines, based on [industry standards](https://www.w3.org/TR/WCAG22/), to ensure the UI is accessible for all. | ||
|
||
## Keyboard accessibility | ||
|
||
All functionality must be operable by using only the keyboard. | ||
People who have trouble using a mouse, including those who are blind, have low vision, or have motor disabilities, rely on keyboard navigation to effectively use the interface. | ||
|
||
### Focus basics | ||
|
||
The basic way to interact with the UI by using the keyboard is to switch focus between components with <shortcut>Tab</shortcut> and <shortcut>Shift+Tab</shortcut>. | ||
For some components, such as lists, drop-down menus, or tables, arrow keys can also be used to navigate between items. | ||
Once a component is focused, it can be activated by pressing <shortcut>Space</shortcut>. | ||
dmitrii-drobotov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Make all interactive elements focusable to achieve [full keyboard operability](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html). | ||
This includes buttons, checkboxes, text fields, lists, tables, dropdowns, and other controls that users can interact with. | ||
|
||
Non-interactive components, such as labels and panels, should not be focusable. | ||
However, in some cases, they can be focusable to let screen reader users access important information. | ||
Use [`ScreenReader.isActive()`](%gh-ic%/platform/util/ui/src/com/intellij/util/ui/accessibility/ScreenReader.java) to adjust the behavior only for screen reader users. | ||
|
||
### Keyboard traps | ||
|
||
Make the UI traversable in a cycle without trapping the keyboard focus. | ||
A [keyboard focus trap](https://www.w3.org/WAI/WCAG22/Understanding/no-keyboard-trap.html) occurs when a user cannot navigate away from a component by using the standard keyboard navigation. | ||
This prevents them from accessing other parts of the interface. | ||
|
||
Ensure that a user can always navigate away from any focusable component by using <shortcut>Tab</shortcut>, <shortcut>Shift+Tab</shortcut>, or <shortcut>Escape</shortcut>. | ||
dmitrii-drobotov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
For instance, continuously pressing <shortcut>Tab</shortcut> or <shortcut>Shift+Tab</shortcut> in a dialog should result in cycling through all components and returning to the starting point. | ||
To break out of such focus loops, use <shortcut>Escape</shortcut> to close dialogs and popups, or move focus back from a tool window to the editor. | ||
|
||
### Managing focus | ||
|
||
When new content appears, such as popups, modal dialogs, or dynamic content, ensure that: | ||
|
||
* Focus is moved to the new content, or there is a way to reach it by using only the keyboard. | ||
* Focus is not moved unexpectedly when users are interacting with components like lists or dropdowns to avoid a [change of context](https://www.w3.org/WAI/WCAG22/Understanding/on-focus.html#dfn-changes-of-context). | ||
|
||
## Assistive technology support | ||
|
||
Assistive technologies, such as screen readers and voice control, rely on accessibility metadata provided by UI components. | ||
For example, a checkbox might be announced by screen readers as "Don't ask again, checkbox, not checked, Alt+D". | ||
Voice control users can say "Press Don't ask again" to activate the checkbox. | ||
This integration is enabled by properly defined accessible metadata. | ||
|
||
Follow these guidelines to ensure that users of assistive technology can fully interact with the UI. | ||
|
||
### Accessible properties | ||
|
||
Each UI component has an associated [`javax.accessibility.AccessibleContext`](https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/javax/accessibility/AccessibleContext.html) object that defines properties for assistive technologies. | ||
The accessible context can also implement [`Accessible*`](https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/javax/accessibility/package-summary.html#class-summary) interfaces to provide additional metadata or ways for assistive technologies to interact with the component. | ||
|
||
> For [common UI components](Components.topic), basic accessibility support is often already implemented. | ||
> But when creating custom components or extending existing ones, you may need to extend the accessible context or modify its properties. | ||
> | ||
{style="note"} | ||
|
||
Example of customizing the accessible context and its properties: | ||
<tabs group="languages"> | ||
<tab title="Kotlin" group-key="kotlin"> | ||
|
||
```kotlin | ||
class ActionLink : JButton() { | ||
override fun getAccessibleContext(): AccessibleContext { | ||
if (accessibleContext == null) { | ||
accessibleContext = object : AccessibleAbstractButton() { | ||
override fun getAccessibleRole() = AccessibleRole.HYPERLINK | ||
override fun getAccessibleValue() = null | ||
} | ||
} | ||
return accessibleContext | ||
} | ||
} | ||
|
||
val link = ActionLink() | ||
link.accessibleContext.accessibleName = "Open in browser" | ||
``` | ||
|
||
</tab> | ||
<tab title="Java" group-key="java"> | ||
|
||
```java | ||
class ActionLink extends JButton { | ||
@Override | ||
public AccessibleContext getAccessibleContext() { | ||
if (accessibleContext == null) { | ||
accessibleContext = new AccessibleAbstractButton() { | ||
@Override | ||
public AccessibleRole getAccessibleRole() { | ||
return AccessibleRole.HYPERLINK; | ||
} | ||
|
||
@Override | ||
public AccessibleValue getAccessibleValue() { | ||
return null; | ||
} | ||
}; | ||
} | ||
return accessibleContext; | ||
} | ||
} | ||
|
||
ActionLink link = new ActionLink(); | ||
link.getAccessibleContext().setAccessibleName("Open in browser"); | ||
``` | ||
|
||
</tab> | ||
</tabs> | ||
|
||
#### Accessible name and description | ||
|
||
An [accessible name](https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/javax/accessibility/AccessibleContext.html#getAccessibleName()) is the label that defines the component's purpose for assistive technology users. | ||
Set a clear and descriptive accessible name for all focusable components. | ||
|
||
For example, a simple button with text should have that text as its accessible name. | ||
For a text field with a label in front of it, the accessible name should be the label's text. | ||
A list without a visible title should still have an accessible name that describes its content. | ||
|
||
In many cases, the accessible name is already taken implicitly from the following sources: | ||
|
||
* The text property of components such as labels or buttons. | ||
* Tooltip text. | ||
* The label of the component that was set by `JLabel.setLabelFor()`. | ||
|
||
If the name was not applied implicitly, set it by calling `AccessibleContext.setAccessibleName()` or by overriding `AccessibleContext.getAccessibleName()`: | ||
|
||
<tabs group="languages"> | ||
<tab title="Kotlin" group-key="kotlin"> | ||
|
||
```kotlin | ||
class AccessibleCheckBox : AccessibleJLabel() { | ||
override fun getAccessibleName() = myLabel.text | ||
} | ||
|
||
checkBox.accessibleContext.accessibleName = "Don't ask again" | ||
``` | ||
|
||
</tab> | ||
<tab title="Java" group-key="java"> | ||
|
||
```java | ||
class AccessibleCheckBox extends AccessibleJLabel { | ||
@Override | ||
public String getAccessibleName() { | ||
return myLabel.getText(); | ||
} | ||
} | ||
|
||
checkBox.getAccessibleContext().setAccessibleName("Don't ask again"); | ||
``` | ||
|
||
</tab> | ||
</tabs> | ||
|
||
Tips for choosing an accessible name: | ||
|
||
* Don't include the component's role in the accessible name. | ||
For example, for a password text field, set the name as "Password" instead of "Password text field." | ||
* For complex components, include all visible information in the accessible name. | ||
For example, for a panel that consists of a title, subtitle, and icon, combine all these parts in the accessible name. | ||
|
||
An accessible description provides additional context, such as instructions, keyboard shortcuts, placeholders, or explanatory text. | ||
Use it for supplementary information that helps users understand how to interact with the component. | ||
|
||
The accessible description is set similarly to the accessible name, either by calling `AccessibleContext.setAccessibleDescription()` or by overriding `AccessibleContext.getAccessibleDescription()`. | ||
|
||
#### Accessible role | ||
|
||
The accessible role tells assistive technologies what type of component they are interacting with. | ||
Use the [`AccessibleRole`](https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/javax/accessibility/AccessibleRole.html) value that correctly represents the component's function. | ||
|
||
The accessible role can be changed by overriding `AccessibleContext.getAccessibleRole()`: | ||
|
||
<tabs group="languages"> | ||
<tab title="Kotlin" group-key="kotlin"> | ||
|
||
```kotlin | ||
class AccessibleCheckBox : AccessibleJLabel() { | ||
override fun getAccessibleRole() = AccessibleRole.CHECK_BOX | ||
} | ||
``` | ||
|
||
</tab> | ||
<tab title="Java" group-key="java"> | ||
|
||
```java | ||
class AccessibleCheckBox extends AccessibleJLabel { | ||
@Override | ||
public AccessibleRole getAccessibleRole() { | ||
return AccessibleRole.CHECK_BOX; | ||
} | ||
} | ||
``` | ||
|
||
</tab> | ||
</tabs> | ||
|
||
Tips for customizing the role: | ||
|
||
* Use `AccessibleRole.LABEL` for plain text content and `AccessibleRole.TEXT` for text fields and text areas that are editable or support selection. | ||
* Set an appropriate button role: `AccessibleRole.PUSH_BUTTON`, `AccessibleRole.RADIO_BUTTON`, `AccessibleRole.TOGGLE_BUTTON`, or `AccessibleRole.HYPERLINK`. | ||
|
||
#### Accessible state | ||
|
||
The [accessible state](https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/javax/accessibility/AccessibleState.html) communicates the component's current condition to assistive technologies. | ||
For example, it can tell screen reader users that the component is selected, expanded, or editable. | ||
|
||
Adjust the component's state set by overriding `AccessibleContext.getAccessibleStateSet()`: | ||
|
||
<tabs group="languages"> | ||
<tab title="Kotlin" group-key="kotlin"> | ||
|
||
```kotlin | ||
class AccessibleCheckBox : AccessibleJLabel() { | ||
override fun getAccessibleStateSet(): AccessibleStateSet { | ||
val set = super.getAccessibleStateSet() | ||
if (myChecked) { | ||
set.add(AccessibleState.CHECKED) | ||
} | ||
return set | ||
} | ||
} | ||
``` | ||
|
||
</tab> | ||
<tab title="Java" group-key="java"> | ||
|
||
```java | ||
class AccessibleCheckBox extends AccessibleJLabel { | ||
@Override | ||
public AccessibleStateSet getAccessibleStateSet() { | ||
AccessibleStateSet set = super.getAccessibleStateSet(); | ||
if (myChecked) { | ||
set.add(AccessibleState.CHECKED); | ||
} | ||
return set; | ||
} | ||
} | ||
``` | ||
|
||
</tab> | ||
</tabs> | ||
|
||
|
||
Notify assistive technologies when the state changes: | ||
|
||
<tabs group="languages"> | ||
<tab title="Kotlin" group-key="kotlin"> | ||
|
||
```kotlin | ||
accessibleContext.firePropertyChange( | ||
AccessibleContext.ACCESSIBLE_STATE_PROPERTY, | ||
null, | ||
AccessibleState.CHECKED | ||
) | ||
``` | ||
|
||
</tab> | ||
<tab title="Java" group-key="java"> | ||
|
||
```java | ||
getAccessibleContext().firePropertyChange( | ||
AccessibleContext.ACCESSIBLE_STATE_PROPERTY, | ||
null, | ||
AccessibleState.CHECKED | ||
); | ||
``` | ||
|
||
</tab> | ||
</tabs> | ||
|
||
#### Accessible interfaces | ||
|
||
Advanced accessibility features are provided through specialized interfaces, such as `AccessibleAction`, `AccessibleText`, `AccessibleSelection`, and `AccessibleValue`. | ||
These are typically needed when implementing custom components from scratch. | ||
Refer to the [list](https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/javax/accessibility/package-summary.html) of available interfaces to determine whether any need to be implemented. | ||
|
||
> Look at similar Swing components to see which interfaces they implement. For example, when working on a new slider-like component, check the interfaces implemented by [`JSlider.AccessibleJSlider`](https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/javax/swing/JSlider.AccessibleJSlider.html). | ||
> | ||
{style="note"} | ||
|
||
### Announcing live changes | ||
|
||
Screen readers announce accessible property changes of the currently focused component automatically. | ||
For example, when a user checks a checkbox, the screen reader announces the new state. | ||
This happens through property change events fired by the component. | ||
|
||
However, in some cases, users need to be notified of changes outside the focused component. Or the event that you want to notify about doesn't fit into existing property change support. | ||
|
||
For these scenarios, use [`AccessibleAnnouncerUtil.announce()`](%gh-ic%/platform/util/ui/src/com/intellij/util/ui/accessibility/AccessibleAnnouncerUtil.java) to make screen readers announce a specific string. | ||
|
||
<tabs group="languages"> | ||
<tab title="Kotlin" group-key="kotlin"> | ||
|
||
```kotlin | ||
if (AccessibleAnnouncerUtil.isAnnouncingAvailable()) { | ||
AccessibleAnnouncerUtil.announce(mySearchPanel, "No results found", true) | ||
} | ||
``` | ||
|
||
</tab> | ||
<tab title="Java" group-key="java"> | ||
|
||
```java | ||
if (AccessibleAnnouncerUtil.isAnnouncingAvailable()) { | ||
AccessibleAnnouncerUtil.announce(mySearchPanel, "No results found", true); | ||
} | ||
``` | ||
|
||
</tab> | ||
</tabs> | ||
|
||
Common cases for using announcements: | ||
|
||
* Error messages or validation results. | ||
* Notifications or popups that are shown but don't receive keyboard focus. | ||
* Background task completion. | ||
* Search or filtering results. | ||
|
||
## Tools | ||
|
||
### Screen readers | ||
|
||
The IntelliJ Platform supports NVDA and JAWS screen readers on Windows, and VoiceOver on macOS. | ||
Test the UI with a screen reader to verify that all functionality is available and that the announced information is correct and complete. | ||
|
||
> Learn more about screen reader functionality and commands from their official user guides: | ||
> [NVDA](https://download.nvaccess.org/releases/2025.1.2/documentation/userGuide.html), | ||
> [JAWS](https://support.freedomscientific.com/products/blindness/jawsdocumentation), | ||
> and [VoiceOver](https://support.apple.com/guide/voiceover/welcome/mac). | ||
|
||
### UI Inspector | ||
|
||
Use the [UI Inspector](internal_ui_inspector.md) to examine accessible properties of UI components. | ||
You can also perform an [accessibility audit](internal_ui_inspector.md#accessibility-checks) to check for common issues and get information on how to resolve them. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.