Accessibility for Webflow

Client-first is dedicated to make accessibility upgrades to our Style System and to Webflow as a platform. Over the next few months, we will release accessibility add-ons specific to Webflow.

Anywhere you see [Finsweet solution coming soon] - it indicates that there is javascript required and Finsweet will release a solution for this (with a no-code implementation!)

This is a small look into how we are approaching accessibility at Finsweet.

What is Accessibility?

The term accessibility (commonly referred as a11y) is not only restricted to web development, but applies to many different aspects of life.

If we take the definition of Wikipedia:

“Accessibility refers to the design of products, devices, services, or environments for people who experience disabilities.”

Applying this to our web development field, we understand it as:

“Web a11y means that anyone at any moment can use your website.”

Why is Web Accessibility so important?

Don’t fall under the widespread assumption that a11y is only needed for people with severe disabilities like blindness or reduced mobility. There are actually more types of disabilities that usually people don’t think of:

  1. Permanently disabled users: The user has a severe disability like blindness or deafness.
  2. Temporary disabled users: The user has a physical or mental disability which hinders his discharging of responsibilities for a short period of time.
  3. Conditional or Situational disabled users: The user is not able to do things due to the current situation he is in. Example: slow internet connection, browsing while eating...

If you’ve ever asked yourself “Why should I spend so much time making my site accessible if there’s so few people that actually will need it?”

The percentage of disabled people that navigate through your site is way higher than you might expect. Companies are realizing this and now requiring website development companies to follow best accessibility practices.

About this guide

This guide won’t get too deep into the basics of a11y, as there is already a lot of quality content out there that you can check such as the A11y Project Checklist or Webflow’s Accessibility Checklist.

Instead, it will focus on specific technical challenges that we, as Webflow developers, need to face on a regular basis.

Keyboard navigation

Due to physical restrictions, many users rely on the keyboard to navigate through your site. This section explains how you can make sure your whole site is keyboard navigation friendly.

Basic controls

Tab Key

The Tab key is used to move around the page by focusing the available elements. Every time the user hits the Tab key, the focus will move to the next focusable element. Holding Shift reverses the direction.

  • It is important to make sure that all elements a regular mouse user would click on the page are also focusable with the Tab key (see Using tabindex).
  • This also implies that all focusable items should have their focus state styled, otherwise the users won’t be able to distinguish what element they are currently focusing on.

Enter Key

When focusing an element, the Enter key should:

  • [Finsweet solution coming soon] Activate links or buttons. Non-standard elements like divs using a role attribute need JS implemented for this.
  • Send forms.

Space Key

When focusing an element, the Space key should:

  • [Finsweet solution coming soon] Activate buttons.
  • Activate toggle states like checkboxes or radios. This also includes buttons that toggle some functionality like an accordion.

Arrow Keys

When focusing an element, the Arrow keys should:

  • [Finsweet solution coming soon] Navigate through grouped children of a component (like Tab Links, Radio buttons, Accordion Toggles, etc).
  • Change the value of it (like range sliders or number inputs).

Esc Key

The Esc key should allow the user from exiting different states like:

  • [Finsweet solution coming soon] Closing a modal.

Focusability

All elements that are clickable on the page should be focusable too. Standard HTML elements have this functionality built-in by default, but sometimes we need to tell the browsers to do so.

In these cases it can be achieved by adding the tabindex attribute.

The value set to it will depend on the wanted behavior:

  • tabindex=”0”: Makes the element focusable with keyboard navigation, following the natural order of the page.
  • tabindex=”X”: where X is any desired number greater than 0. When explicitly setting this number, you’re stating the order when this element should be focused. Example: the element with tabindex=”26” will be focused after the element with tabindex=”25”.
  • tabindex=”-1”: Disables the focusing of an element. This comes handy in some situations where you have a native focusable element that doesn’t provide any interactivity.

Programmatic focus

[Finsweet solution coming soon]

To improve the UX for keyboard navigation and screen readers, sometimes it’s a good idea to programmatically focus an element when a certain condition is met.

Some examples are focusing the close button right after opening a modal, or moving the focus to a certain element that appeared on the page.

However, always think of the UX! There are some cases where it’s not a good idea to automatically focus an element that appears on the page, like when switching tabs in a Tabs component.

HTML Semantics

Using <button> tags in Webflow

The main purpose of this tag is to tell the user that an element is clickable and will trigger an action on the page. This includes actions like expanding/collapsing elements (example: accordions), showing/hiding elements (example: hamburger menu or dropdown list) or custom app-like functionalities like a TO-DO item to a list.

Do not confuse <button> tags with the Button component in Webflow!

It's important to understand the difference between <button> tags and <a> tags:

  • <button> activate actions on the page.
  • <a> navigate through the page / website

Behind the curtains, the Webflow Button component is just a regular html anchor link (<a> tag) with some styles sprinkled on top of it:

Button, text link, link block html screenshot


Unfortunately, using the <button> tag in Webflow is not possible to do as of the time of writing this guide (unless using an Embed component, which is not ideal).

When the use of a <button> tag is needed, we must use a <div> instead with the following conditions:

  • It has a role=”button” attribute.
  • It’s keyboard focusable with the tabindex attribute.
  • [Finsweet solution coming soon] It fires a click event when the Enter or Space key is pressed.
  • [Finsweet solution coming soon] If the button has pressed/not pressed states (like a toggle), it must have an aria-pressed attribute reflecting the current state.
  • [Finsweet solution coming soon] If the button controls the expansion/collapse of another element (like an accordion), it must have an aria-expanded attribute reflecting the current state.

Creating HTML Tables in Webflow

The <table> element is not available to be used natively in Webflow.

You can still build accessible tables using regular Divs (meaning that a screen reader will be able to read the content in the proper order, following rows and columns) using the ARIA roles defined for this purpose. This is shown in this guide in the ARIA Roles: role=”table” section.

Hiding elements on the page

Aside from the most famous display: none style, that hides the element from the structure of your page, there are some times where you want an element to only be visible to a part of the user base.

Hiding elements only from screen readers

While parsing the content of your page, a screen reader will read out loud as much information as it can to describe it to the user.

There are some elements though that do not have any information attached to them, such as visual elements like styled divs or svg elements.

In those cases we want the screen readers to just skip them so they don’t break the reading flow.

This can be achieved by using the aria-hidden attribute. More on this in the ARIA section below.

Hiding elements only for regular users

On the other hand, there are some times where you are using a visual representation of some content (like a fancy svg as the title of your page), but still want to provide context of it to screen readers (and crawling bots!).

You can achieve this by using CSS to visually hiding the content in a way that it will still be picked by screen readers and crawling bots:

.fs-a11y_visually-hidden {
   position: absolute;
   clip: rect(1px, 1px, 1px, 1px);
   clip-path: inset(0px 0px 99.9% 99.9%);
   overflow: hidden;
   height: 1px;
   width: 1px;
   padding: 0;
   border: 0;
}

This technique is used by many big sites like Apple!

Apple.com website example of visually hidden class
Zoomed in Apple.com example of visually hidden class

ARIA Basics

The MDN Web Docs describe ARIA as:

“Accessible Rich Internet Applications (ARIA) is a set of attributes that define ways to make web content and web applications (especially those developed with JavaScript) more accessible to people with disabilities.”

When building static sites with no interactivity, usually sticking to just using correct HTML semantics is almost enough.

But when adding custom functionalities using Javascript (Webflow Interactions count as it!), we need to provide more context to the users with disabilities so they can correctly read and use our site.

Note: Webflow’s native components like Navbar, Slider, Tabs, etc already use the correct aria attributes with the required Javascript for the dynamic ones.

ARIA Roles

Elements that provide a specific functionality to the page (thus they perform a role) must be defined so screen readers can understand the context of the actions / situations.

When using basic HTML semantic elements (like <button>, <nav>, <a>, <input>, etc…), these role attributes are not needed, as both the browser and the screen reader devices already understand their purpose. 

We won’t list all of the available roles. Below are the most commonly used ones. You can check the full list on MDN Docs.

role=”button”

Used to define an element that doesn’t have a <button> tag that is acting like it.

role=”listbox”

Used to define an element that contains a list of options to choose from.

role=”option”

Used to define an element that represents an option in a list of options. Usually combined with the aria-selected attribute.

role=”table”, role=”rowgroup”, role=”rowheader”, role=”row”, role=”columnheader”

Used to define an HTML Table structure, where:

  • role=”table” corresponds to the <table> element.
  • role=”rowgroup” corresponds to the <tbody> element.
  • role=”rowheader” corresponds to the <thead> element.
  • role=”row” corresponds to the <tr> element.
  • role=”columnheader” corresponds to the <th> element.

ARIA Attributes

aria-label 

How an element is announced by a screen reader is usually determined by its content

For example, a text-link like this:

<a href="https://www.webflow.com">Go to Webflow!</a>

Will be read out loud as “Go to Webflow!”. But there are some times where the content doesn’t explain the action that will be performed, or maybe there isn’t any content at all.

In those cases, using aria-label will let you give more context to the user.

Example:

aira-label expand dropdown example in Webflow

aria-labelledby

aria-labelledby, as the name already says, lets you give more context to an element by pointing to another element that explains it.

The attribute is used like:

aria-labelledby button-id example in Webflow

Where the element with an ID of “button-id” has some content in it that provides context to the user.

aria-describedby

In addition to an element's label, screen readers can announce a referenced element as its description, so the user can understand the context more deeply.

aria-describedby description paragraph example in Webflow

Where “description-paragraph” is the ID of the element that holds the description of the context.

aria-controls

This attribute creates a cause and effect relationship. It identifies the element(s) that are controlled by the current element.

Example: a button that opens a modal when clicking on it should have:

aria-controls modal example in Webflow

Where “modal” is the ID of the modal element.

aria-expanded

[Finsweet solution coming soon]

There are many situations where a user needs to be able to toggle an element’s visibility, such as when opening a hamburger menu or a modal dialog.

Usually, when an element becomes visible on the page, the user spots it right away. This is not the case for blind people, who need their screen reader devices to notify them about these changes.

This is where aria-expanded comes into place, allowing us to define the status of the element that is being controlled.

This attribute should be paired with the aria-controls one to provide the full context of the action to the user.

The value is set to true/false using Javascript depending on the element’s state:

aria-expanded=”true”

aria-haspopup

Complimentary to the aria-expanded attribute, we can further define what has been expanded with this attribute. It accepts the following values:

  • aria-haspopup=”menu”, indicates the popup is a menu.
  • aria-haspopup=”listbox”, indicates the popup is a listbox.
  • aria-haspopup=”tree”, indicates the popup is a tree.
  • aria-haspopup=”grid”, indicates the popup is a grid.
  • aria-haspopup=”dialog”, indicates the popup is a dialog.

aria-pressed

[Finsweet solution coming soon]

Some elements require a visual confirmation that have been pressed, like a custom Switch being toggled on/off. Screen readers can be notified about this newly achieved state with the aria-pressed attribute.

The value is set to true/false using Javascript depending on the element’s state:

aria-expanded=”true”

aria-current

[Finsweet solution coming soon]

Used to identify the “current” item in a set of items. This has different applications:

  • Page: used as aria-current=”page”, defines an element that has the current URL where the user is located. Usually set to the links that point to the current page.
    Important: Webflow’s Current state in the Designer only adds a w--current CSS class to the element, but not the aria-current attribute.
  • Location: used as aria-current=”location”, defines an element that has the current page description where the user is located. A good example where to use this attribute is in a Breadcrumbs component, where the element describing the current page would get it.
  • Date: used as aria-current=”date”, defines an element that has the current date. Commonly used in calendars and date pickers.
  • Step: used as aria-current=”step”, defines an element that is indicating the current step in a multi-step process (like a multi-step form).

aria-selected

[Finsweet solution coming soon]

When building custom selection interfaces (like a custom dropdown of options or a combo box), we need to notify to screen readers what element is the one selected at any moment.

This attribute must be set dynamically, pointing to the element in the list that is currently selected:

aria-selected option-2 example in Webflow

Where “option-2” is the ID of the selected element.

aria-hidden

[Finsweet solution coming soon]

As seen in hiding elements only from screen readers, sometimes a visual element provides no context to the user when reading it with a screen reader.

Setting the attribute aria-hidden=”true” will make them skip this element from being read out loud.

Commonly used components

Building accordions in Webflow:

Trigger:

  • [Finsweet solution coming soon] USWDS uses <button> tags, we will have to rely on divs with the role=”button” and use JS to trigger a click event on Enter or Space key.
  • If using a div , it needs to have tabindex to make it keyboard navigable. It should also have its focus state styled.
  • Use aria-controls for defining the collapsible content.
  • [Finsweet solution coming soon] Use aria-expanded for defining the state of the collapsible content.

Content:

  • Should not be hidden by default. Rely on JS to hide it when the page loads (this can be done with ix2) to make sure people with disabled JS can see it.
  • .Use aria-labelledby to define the trigger of it.

Other cool stuff we’ve found:

Bad practices for accessibility

Not using HTML semantic elements when available

There are some cases (like the <button> elements) where we are forced to find a workaround due to Webflow not providing them natively.

But there are other cases where using a role approach doesn’t make sense.

For example: links

Doing this:

<a href="https://www.google.com">Go to Google</a>

Is obviously preferred over this:

<div role="link" onclick= "window.location.replace ('https://www.google.com')">Go to Google</div>

Although the regular users will notice no difference at all, disabled users will not be able to navigate to the element and activate it with the keyboard, as well as other issues like SEO indexing of the link.

It might seem obvious with this example, but there are other use cases where something like this could happen.

Adding redundant WAI-ARIA attributes

Semantic elements (a, form, nav, etc…) will already be read by the screen readers. Adding to them an additional role attribute will cause the screen reader to read it twice, which can get very annoying.

For example: forms

The following element will be read as “Form, Form”:

<form role="form"></form>

Thanks! That's all for now!

We are actively working on our Attributes platform that will give you the power to implement everything in this guide.

Stay updated with Finsweet content to stay updated with our a11y updates!


fs-tags-icon

Tags and structure

fs-play-icon
2min30s
Close video
Our structure follows accessibility guidelines. Use HTML tags to make your content more clear.

Classes in our core page structure

page-wrapper
nav_component
main-wrapper
section-[section-identifer]
section-[section-identifer]
section-[section-identifer]
section-[section-identifer]
section-[section-identifer]
footer_component

Add HTML tags to divs on the page

Further classify your page content here in Div Block Settings. We have a lot to classify in our core page structure.
This is what our core structure looks like with <nav> <main> <section> and <footer> html tags.
<div .page-wrapper>
<nav .nav_component>
<main .main-wrapper>
<section .section-[section-identifer]>
<section .section-[section-identifer]>
<section .section-[section-identifer]>
<section .section-[section-identifer]>
<section .section-[section-identifer]>
<footer .footer_component>

Tag explanation

Ignore the dots before the < character

<nav> tag

Defines a set of navigation links, such as those in your main site nav.

Place this on your nav/menu component.

If you are using the Webflow Navbar component, the <nav> tag will be added automatically for you.

If you are using a div and custom building your nav, add the <nav> tag to the outer div that holds the nav elements.

Why it's important:

  • People can X

<main> tag

Specifies the main content of a document.

Use a <main> tag to wrap all or most of your website content sections. This content is what this page is about.

The nav and footer should not be inside this <main> tag.

Why it's important:

  1. People can X

<section> tag

Defines a section of a document.

Place this tag on the section elements on page. We use .section-[section-dentifier] as our class name to identify sections inside Designer. Apply the <section> tag as well so all people and devices also know it's a section.

Why it's important:

  1. People can X

<footer> tag

Defines a footer for a document or section.

Place the <footer> tag on the page's footer.

Why it's important:

  1. People can X

<header> tag

Defines a header for a document or section.

Place the <header> tag on the page's header.

Why it's important:

  1. People can X

<article> tag

Defines an article.

Place the <article> tag on

Why it's important:

  1. People can X

<aside> tag

Defines content aside from the page content, such as a sidebar.

Place the <aside> tag on

Why it's important:

  1. People can X

<address> tag

Defines contact information for a person or people, or for an organization.

Place the <address> tag on the

Why it's important:

  1. People can X

<figure> tag

Specifies self-contained content, like illustrations, diagrams, photos, code listings, etc.

Place the <figure> tag on the

Why it's important:

  1. People can X
There are many HTML tags available to us as website developers.

Mozilla has a great resource for a full list of HTML tags:

https://developer.mozilla.org/en-US/docs/Web/HTML/Element