Blog

Basic Tips for Improving Accessibility

By  
Joacim Päivärinne
Joacim Päivärinne
·
On Sep 30, 2021 1:00:57 PM
·

Accessibility Featured ImageUser Experience is a core value of the Vaadin team. This post is part of our series on UX in the Enterprise and highlights the importance of accessibility in building modern enterprise web applications.

In this article we're going to go over some basic tips and tricks for improving the accessibility of a web application. But before we do that, let's briefly define what accessibility is and why it matters.

What is accessibility?

MDN defines accessibility as "the practice of making your websites usable by as many people as possible.

There's a common misconception that accessibility benefits only people with disabilities, but it can actually be helpful for a lot of different user groups, such as older generations (who might be seeing a decline in their abilities due to age), people with temporary disabilities or limitations, people using touch devices, etc.

Accessibility as a whole covers a wide range of topics, but essentially it boils down to making sure that as many people as possible can understand and interact with your application. That is the inherent value of accessibility.

If you prefer, you can also watch a video version of this article:

Why accessibility matters

There's no good reason ever to exclude anyone from being able to use your products. By making your application accessible, you're extending your reach and potential user base. Accessibility can only ever have a positive impact on your brand and public image, and it's absolutely essential in order to create a high-quality and professional web app. Nowadays, it's also required by law in many countries , especially in the public sector. 

Read more about Accessibility in Modern Enterprise Apps.

Types of disabilities

There are many types of disabilities that impact how people interact with applications,  such as visual, hearing, mobility, and cognitive impairments.

In this article, we will focus mainly on the visually impaired, and specifically on people who suffer from poor to no vision, and who therefore have to rely on screen readers to interpret and read the screen content aloud to them.

The three most popular screen readers are JAWS (commercial product), NVDA (free, Windows) and VoiceOver (free, Mac). We highly recommend trying out one of these options just to get first-hand knowledge of what it’s like for users with poor vision to use your software. This can be quite an eye-opening experience. It will give you a different perspective, allowing you to evaluate more objectively exactly how accessible and user-friendly your application really is.

WebAIM’s article “Testing with Screen Readers” is an excellent read if you’d like to know more.

Current accessibility standards - WCAG

We can't talk about accessibility on the web without mentioning WCAG, or Web Content Accessibility Guidelines. It's the de facto standard which most countries use in some way or another in their accessibility policies. The most commonly used versions are 2.0 and 2.1. 

Version 2.0 became an ISO standard in 2012. It is referenced in the United States' Section 508, which requires federal agencies to ensure that their information is accessible to people with disabilities. (The US also has the Americans with Disabilities Act, which applies to both the private and public sector, but it doesn't directly reference any WCAG version.)

Version 2.1 became a W3C recommendation in 2018. It is referenced (indirectly) in the European Union's Web Accessibility Directive (via EN 301 549) and applies to most of the public sector in the EU. This Directive complements the European Accessibility Act, which applies to a lot of different services in the private sector.

Conformance levels

Both 2.0 and 2.1 have three levels of conformance: A, double A and triple A. Each level becomes progressively more difficult to meet. For example, for version 2.1, there are 30 criteria that you have to satisfy for level A. To reach the second level, there are an additional 20 criteria that need to be met. And for triple A, there are 28 more criteria, for a total of 78.

Example

To give you a simplified example, in order to reach level A, you can’t rely on color alone to convey information. For example, color-blind people can have difficulty seeing links within text if these don't have an underline or a different font weight.

Image showing how links show up for colorblind users

For level double A, there's a criterion that the contrast ratio between the text and the background must be at least 4.5:1 to ensure that the text is legible. For triple A, that contrast needs to be 7:1.

Visual example of contrast ratio between text and background

Tips and tricks to get you started

I’ve gathered together some of what we consider the best Tips and Tricks to ensure that your application follows WCAG guidelines. 

Application layout

The first thing to consider is the layout of your application. A typical Vaadin application looks something like this:

A typical Vaadin application layout

It's commonly built using components such as AppLayout, HorizontalLayouts, and VerticalLayouts. While it's OK to use these components to achieve the overall structure that you want, you should make sure to also use HTML landmarks for the most important sections of your app.

Respective HTML landmarks for app sections

By using landmarks, you drastically improve the user experience for people using screen readers. Screen readers are able to identify and quickly navigate between the different landmarks of your application, so users can, for example, skip to the main content of the page. An application without landmarks can be cumbersome and frustrating to use, similar to asking a sighted user to only use their keyboard. It severely limits a screen reader user’s ability to move around.

We won't go through all the different landmarks in this article, but we'll briefly touch on some of the more important ones.

  • The nav landmark (“nav” is short for “navigation”) should be used for major navigation blocks in your application, such as the main menu, which should consist of a list of links.
  • The header landmark can be used for the application's main header, which commonly houses a logo, title, search bar, etc.
DrawerToggle menuButton = new DrawerToggle();
menuButton.getElement().setAttribute("aria-label", "Menu toggle");
H1 pageTitle = new H1("Page title");
Header header = new Header();
header.add(menuButton, pageTitle);
  • Lastly, we have the main element, which represents the main content of the page. The important thing to note here is that each page should have exactly one main landmark.
@PageTitle("Home | MyApp")
@Route(value = "", layout = MainLayout.class)
public class HomeView extends Main { ... }

For more information on HTML landmarks, you can check out W3C’s landmarks example page.

Page titles

One of the simplest WCAG criteria to satisfy is to make sure that each page has a title. It's the first thing read aloud by screen readers and it helps with identifying what the page is all about. It's recommended to put the most important information first, such as the name of the page, with the company or organization name coming next. This way, users are able to quickly tell which pages they have open in the tab bar.

In Flow, views are given a title with the @PageTitle annotation.

@PageTitle("Home | MyApp")
@Route(value = "", layout = MainLayout.class)
public class HomeView extends Main { ... }

In Fusion, you use the <title> element, placed within the <head> block.

<html>
  <head>
    ...
    <title>Home | MyApp</title>
    ...

Headings

There are six different headings – h1, h2, all the way down to h6 – and they communicate the hierarchy and structure of the page content.

Screen readers are able to create an outline of the page based on the headings, allowing users to quickly navigate and scan the content. This is why it's recommended to use headings in order to avoid causing confusion.

h1 is the most important heading and, like the page title, it should describe the purpose of the current page. It's recommended to have only one h1 per page. However, it is OK to have multiple h2, h3, etc. elements, as long as they're used in sequential and descending order.

H1 myProfile = new H1("My profile");
H2 personalDetails = new H2("Personal details");
TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
DatePicker birthdate = new DatePicker("Birthdate");
H2 contactInformation = new H2("Contact information");
EmailField emailAddress = new EmailField("Email address");
TextField phoneNumber = new TextField(“Phone number”);

form@2xHeadings should not be used based on their visual appearance. If a heading appears too large in a specific context, you can use CSS to change its font size. For comparison, here’s what start.vaadin.com looks like with and without changing the font size for h1 (application header, “Clients”) and h2 (navigation sidebar, “My App”).

font-size@2x

Navigation

When it comes to navigation, you should always use RouterLinks (or Anchors) to navigate to and between views. A common mistake is to use whatever components look the part, add a listener to them, and handle the navigation on the server.

// Don’t use buttons for navigation purposes
Button button = new Button(“...”);
button.addClickListener(event -> UI.getCurrent().navigate(...));

// Don't use divs as links
Div dashboard = new Div(
  VaadinIcon.DASHBOARD.create(),
  new Span("Dashboard")
);
dashboard.addClickListener(event -> UI.getCurrent().navigate(...));

This approach should be avoided, since, in the worst case scenario, screen reader users won't know how to navigate the site. For example, while buttons are picked up by screen readers, users won't expect them to be (nor are they intended to be) used for navigation, except in the case of a submit button in a form.

Navigation hierarchy

When there are multiple levels of navigation, give each level an accessible name. It doesn't have to be anything advanced; for example, "primary" and "secondary" is often sufficient. You can use the aria-label attribute to give nav landmarks a name. This helps screen reader users to identify the purpose of each navigation section.

Application with multiple levels of navigation.

// Primary navigation
Nav primaryNav = new Nav(
  new UnorderedList(
    new ListItem(new RouterLink("Home", HomeView.class)),
    new ListItem(new RouterLink("Dashboard", DashboardView.class))
  )
);
primaryNav.getElement().setAttribute("aria-label", “Primary");

// Secondary navigation
Nav secondaryNav = new Nav(
  new UnorderedList(
    new ListItem(new RouterLink("Pending", PendingView.class)),
    new ListItem(new RouterLink("Submitted", SubmittedView.class))
  )
);
secondaryNav.getElement().setAttribute("aria-label", “Secondary");

Input field labels

In general, input fields should always have a label. If a label isn't set, screen reader users won't be able to tell what the input field is for. For example, a TextField without a label will be announced as "edit text". Given a label, however, screen readers will first announce the label and then the type of input you're expected to enter.

// Don’t! Announced as “edit text”
TextField firstName = new TextField();

// Do! Announced as “First name, edit text”
TextField firstName = new TextField(“First name”);

Placeholders aren't a substitute for labels, because they won't necessarily be picked up by screen readers. However, if there is enough context, there are situations where you can omit the label for sighted users, for example in search fields and grid column filters. In those cases, you must set an aria-label for the input field, so that screen readers can interpret them correctly.

// Use placeholders with a label or aria-label
TextField search = new TextField();
search.setPlaceholder(“Search”);

textfield.getElement().executeJs(
  "this.inputElement.setAttribute(
    'aria-label', ‘Search'
)");

textfield.getElement().executeJs(
  "this.inputElement.removeAttribute(
    'aria-labelledby'
)");

Input field helpers

Business applications are often specialized and require deep domain knowledge. It can be easy, especially if you know the domain very well, to assume that everyone else is also an expert, and knows how to fill in the various forms and input fields. These kinds of assumptions should be avoided, because, although many users might indeed be experts, it’s important to have enough guidance in the application so that newcomers, too, can figure out what needs to be done. It shouldn’t be necessary to read a manual in order to use a business application, so provide helpers and additional information whenever possible.

PasswordField password = new PasswordField("Password");
password.setHelperComponent(getPasswordHelper());
password.setValueChangeMode(ValueChangeMode.EAGER);
password.addValueChangeListener(e -> updateHelper(e.getValue()));

...

private Component getPasswordHelper() {
  strengthValue = new Span("weak");
  strengthValue.addClassNames("font-semibold", "text-error");

  Paragraph strength = new Paragraph(
    new Text("Password strength: "),
    strengthValue
  );
  strength.addClassNames("mb-s", "mt-0");

  Paragraph hint = new Paragraph("A password must be at least 8 
   characters long, containing at minimum one letter and one digit.");
   hint.addClassNames("bg-primary-10", "m-0", "p-s", "text-primary");

   return new Div(strength, hint);
}

When users are required to enter information, if you don’t make it clear what the expected values and formats are, they will most likely run into errors, which can cause frustration. This is especially important when there are specific requirements that have to be met, or input formats that need to be followed.

Tooltips aren't a substitute for helpers, because they might not be available on mobile devices or when screen readers are used.

In Flow, you can use the setHelperComponent() method (as in the preceding code example) if you want to use components in your helper, or setHelperText() if you only need plain text.

PasswordField password = new PasswordField("Password");
password.setHelperText("A password must be at least 8 characters long,
containing at minimum one letter and one digit.");

In Fusion, you can use the helper slot to set the helper for an input field. Plain text, HTML, and other components are all supported.

<vaadin-password-field label="Password">
  <div slot="helper">
    <p class="mb-s mt-0">
      Password strength: <span class="font-semibold text-error">weak</span>
    </p>
    <p class="m-0 p-s bg-primary-10 text-primary">
      A password must be at least 8 characters long,containing at minimum one letter and one digit.
    </p>
  </div>
</vaadin-password-field>

Required fields

Another WCAG requirement that is really easy to satisfy is to mark required fields. In Vaadin's default theme, required fields are marked with a bullet character. You can customize how required fields are marked but, whichever approach you use, it's always a good idea to inform users upfront about how they are indicated.

H1 personalInformation = new H1("Personal information");
Paragraph requiredFields = new Paragraph("All required fields are marked with a “•”");

TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
EmailField emailAddress = new EmailField("Email address");

// Mark as required using Binder
Binder<User> binder = new Binder<>(User.class);
binder.forField(emailAddress)
     .asRequired("Email address is required")
     .bind(User::getEmailAddress, User::setEmailAddress);

...

// Dummy class
private class User {
 String firstName;
 String lastName;
 String emailAddress;

 User(String firstName, String lastName, String emailAddress) {
   this.firstName = firstName;
   this.lastName = lastName;
   this.emailAddress = emailAddress;
  }

public String getEmailAddress() {
    return emailAddress;

  }

  public void setEmailAddress(String emailAddress) {
    this.emailAddress = emailAddress;
  }
}

Alternatively, you can also add "required" as a suffix to the label of the input field.

Some developers go a different route by marking the optional fields instead. This approach is commonly used when most of the fields are required.

H1 personalInformation = new H1("Personal information");
Paragraph requiredFields = new Paragraph("All fields are required unless otherwise indicated");

TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
EmailField emailAddress = new EmailField("Email address (optional)");

// Mark as required using Binder
Binder<User> binder = new Binder<>(User.class);
binder.forField(firstName)
      .asRequired("First name is required")
      .bind(User::getFirstName, User::setFirstName);
binder.forField(lastName)
      .asRequired("Last name is required")
      .bind(User::getLastName, User::setLastName);

Required vs. optional fields in a form

Whichever way you go, the most important thing is to be consistent with your approach regarding forms in your application.

Error messages

It's recommended to validate user input "on blur", meaning when the field loses focus. Avoid running validation during entry, as it can be distracting for the user, especially if they're using a screen reader, which will announce the errors while they're typing.

It's important to note, though, that screen readers will not be able to pick up an error message when using "on blur" validation, for technical reasons. The error message will be announced only if the field is refocused.

So, it's recommended to use validation summaries in addition to "on blur" validation to ensure that the errors are read aloud. Validation summaries are usually placed at the top or bottom of a form. They should have the aria-live attribute set, so any new errors are announced automatically.

// Validation summary
H2 title = new H2("Incomplete form");
Paragraph description = new Paragraph("Please fix the following errors:");

Error firstNameError = new Error("firstname");
Error lastNameError = new Error("lastname");

UnorderedList errors = new UnorderedList();

validationSummary = new Div(title, description, errors);
validationSummary.getElement().setAttribute("role", "alert");
validationSummary.setVisible(false); // Only show when there’s errors

// Form
TextField firstName = new TextField("First name");
firstName.setId("firstname");
firstName.addValueChangeListener(e -> {
  if (e.getValue().isEmpty()) {
    firstNameError.setErrorMessage("Please enter a first name");
    errors.add(firstNameError);
  } else {
    errors.remove(firstNameError);
  }
  validationSummary.setVisible(errors.getChildren().count() > 0);
});

...

// Convenience class
private class Error extends ListItem {
  private Anchor anchor;

  Error(String id) {
    anchor = new Anchor("#" + id, "");
    add(anchor);
  }

  public void setErrorMessage(String message) {
    anchor.setText(message);
  }
}

Error messages should always be displayed when a field is invalid, so that the user knows that something is amiss. A good error message is informative, and provides helpful instructions on how to resolve the problem. Additional information can be also provided when needed, for example, by linking to a separate page or opening a popup with more detailed instructions.

Tooltips should be avoided for displaying error messages, as they aren't very discoverable. Alert dialogs should similarly be avoided, due to their potentially disruptive effect on the user's workflow.

Buttons

Just like input fields, buttons should have a label whenever possible. Icon-only buttons are often ambiguous, which results in users having to do guesswork; that is not something that you want.

However, there are situations that call for icon-only buttons, such as toolbars and repeated actions in tables and lists. Ideally, these buttons should use simple, recognizable icons, such as a magnifying glass for search, and a printer for printing.

Icon-only buttons must have an aria-label set for screen reader users, and a tooltip for sighted users. Aria-labels can also be used to provide additional context for labeled buttons. This is especially helpful when there are multiple buttons with the same label.

List of employees with icon-only buttons for managing the dataset.

Button button = new Button(VaadinIcons.PENCIL.create());
button.getElement().setAttribute(“aria-label”, “Edit Aria Bailey”);
button.getElement().setAttribute(“title”, “Edit Aria Bailey”);

Finally…..

Accessibility is vital for anyone developing or designing web applications. The goal is to enable access for as many people as possible, making your application user-friendly regardless of any potential disabilities that they might have.

Creating a perfectly accessible application is not trivial, but it's important to remember that a little goes a long way. Following the best practices laid out in this article will improve the user experience of your screen reader users. You don't need to do everything at once; you can improve your application's accessibility step by step, making life a little bit easier each time for your end users.

Vaadin is heavily focused on improving the accessibility of its web components. The upcoming versions (V21-23) will be getting a major (internal) overhaul that will address accessibility issues that we, and you, have identified. Stay tuned to the blog and our newsletter for updates.

In the meanwhile, Vaadin Start is a good starting point for creating an accessible application. We are constantly rolling out updates that improve the accessibility of our templates. (If you encounter any issues, regardless of whether or not it’s related to accessibility, you can report them at Start's GitHub repository and we will make sure to address them.)

Additional Resources

If you are interested in learning more about accessibility guidelines and standards, we recommend these well known sources. Here are a few links to get you started.

Additional Vaadin Resources

Any questions?

That’s it for now. Thanks for reading! If you have any questions regarding accessibility and Vaadin, feel free to stop by our Discord or ask in Stack Overflow, and we'll do the best we can to help out.