A dialog is a window that overlays the main window or another dialog window. Windows under modal dialogs are usually inactive. That is, users cannot interact with content outside the active dialog window. These unavailable items are often visually blurred or darkened to weaken their visual presentation. In some scenarios, trying to interact with the unavailable content triggers the dialog box to close. — Wai-Aria Authoring Practices 1.2

Dialog boxes actually have modal and non-modal two conditions, is not the focus of this article, you can come down to check.

background

Recently in the research to do a C test using Low level popover components can be customized UI.

In the logic of popover itself, barrier-free access is the most complicated but easily neglected point in my opinion.

Here ARE some of the barriers to consider:

  1. Keyboard access
  2. Screen reader
  3. The mouse access
  4. Screen adaptation
  5. No CSS
  6. No JS

By removing these obstacles, we have achieved barrier-free access to the popover components.

First, keyboard access

Keyboard access is one of the most complex logic in popover, W3C official also has very detailed content to introduce you, here is probably according to their own understanding to introduce you.

The W3C document

W3C English text

Keyboard accessible element: any element with a tabIndex value of 0 or greater is “strongly discouraged”.

  • When a dialog opens, one of the elements in the dialog needs to get focus.
  • Tab:
    • Move the focus to the next accessible element in the dialog;
    • If the focus is on the last accessible element in the dialog box, move the focus to the first optional element in the dialog box;
  • Shift + Tab:
    • Moves the focus to the previous accessible element in the dialog box.
    • If the focus is on the first access element in the dialog box, move the focus to the last optional element in the dialog box.
  • Escape: Closes the dialog box

When the popover opens, the rule for the first element that needs focus is as follows:

  1. In all cases, after the popover opens, the focus can only be on the elements in the dialog;
  2. Unless specified, the focus is set by default to the first accessible element;
  3. If the contents of the container are large enough, the first accessible element that is down may cause the top area of the popover to move out of view. It is recommended that the first accessible element be placed in the top area of the popover, such as the title, or the first paragraph of the bodytabindex = -1
  4. If the dialog is at the last step in a process that cannot be easily reversed, such as data deletion or irreversible operations related to money. It is recommended to focus on the least disruptive action, which is the cancel or close button. Alert popovers are typically used to deal with this;
  5. If the dialog is limited to providing additional information or interactions that need to be continued, it is recommended to focus on common elements, such as the OK or Continue buttons.

When the popover is closed, the focus should default back to the relevant position before the popover display “such as the button that triggered the popover display”. However, there are several situations that require special consideration:

If there is no relevant element that triggers the popup, select the element that ensures the logical flow can continue.

Special consideration can be given if the following conditions exist in the workflow:

  • You really don’t want the user to call up this popover again;
  • After this popover operation is complete, the user needs to proceed to the next flow;

For example, in a grid there is a toolbar that contains an add row button associated with it. The Add Row button opens a dialog box asking for the number of rows to add. When the dialog closes, the focus should be on the first cell of the first new row, not back on the Add button.

It is highly recommended that all dialog boxes include an element with role=”button” visible in their TAB sequences, and that clicking on this element closes the popover. For example, the close icon or cancel button “Can’t use the keyboard to close the popover”.

implementation

Here’s a recommended component for keeping focus on an element:

  1. focus-trap
  2. focus-trap-react

This component will help you implement about 90% of the above focus-related logic. However, for the first element that needs to be focused when the page opens, and for the issue of refocusing after the popover closes, this has to be customized for the actual scene.

<div>Cannot be in focus</div>
<div tabindex="1">Focus can be obtained, but not accessed by the keyboard</div>
<div tabindex="0">It can get focus and be accessed by the keyboard</div>
Copy the code

Another thing to note here is that if an element tabIndex =”-1″ gets focus, it can’t be accessed by the keyboard. Therefore, when doing barrier-free access, label semantics is more important.

<dialog>
    <button typef="button">mask</button>
    <section tabindex="1">
       <! -- Popover content -->
       <button type="button">Shut down</button>
    </section>
</dialog>
Copy the code

So here’s what you think is a good way to arrange things.

  1. Our first accessible element is the mask, which ensures that without a specific focus, if the user just opens the popover by hand, the mask will be lighter by default, and then the user can directly press Enter to close the popover.
  2. In the middleThe body container is adoptedtabindex="-1", to ensure that we have at least one element that can get focus in the popover without masks and close buttons. But when the user is using the keyboardtabWhen the key switches focus, this element can be ignored.
  3. The last oneThe accessible element isShut downButton so that the user is using the keyboardtabThe user can close the popover if no action has been taken. In this way, it is better to form a cycle;
  4. Mask and close we use elements that are accessible by default, reducing code also increases accessibility;
<button type="button">Button 1</button>
<button type="button" style="visibility:hidden;">Button 2</button>
<button type="button" style="opacity: 0;">Button 3</button>
<button type="button" style="pointer-events: none;">Button 4</button>
Copy the code

Another problem is that only 1, 3, and 4 of these buttons are accessible by the keyboard.

This is especially important when doing popover animations, because most of the time when the popover animation ends, our element will be visibility:visible. It is invalid to bind focus listening events while the animation is still running.

Screen reading

W3C English text

Although this is classified under screen reading, it is actually the official rules of popover accessibility except keyboard access mentioned above. Including popover roles, states, properties…

  1. The container used as a popover should haverole="dialog"Properties;
  2. All the other elements that can manipulate this popover should be descendants of this popover;
  3. Popover containers should havearia-modal="true"attribute
  4. The popover container should also:
    • aria-labelledby="[id]"Property, the ID points to the title element visible in the popover;
    • througharia-labelAttribute add label;
  5. Optional properties,aria-describedby="[id]"The ID points to an element that indicates which or which elements in the dialog box contain elements that describe the main purpose of the dialog box or the content of the message. Specifying descriptive elements enables the screen reader to read the dialog when it opens, along with the dialog title and the initial focus element.
<dialog
    role="dialog"
    aria-modal="true"
    aria-describedby="alert-dialog-description" aria-labelledby="alert-dialog-title"
>
    <button typef="button" aria-label="Mask"><button>
    <section tabindex="1">
        <h2 id="alert-dialog-title">The title</h2>
        <p id="alert-dialog-description">The body of the</p>
        <button type="button" aria-label="Closed">&times;</button>
    </section>
</dialog>
Copy the code

The close button here we use an HTML character &tiems; Instead, if the button is not set to aria-label, the character is read directly. When we enter screen reading mode, open the popover for the first time, and then click the close button, we hear something like this:

Title => Web dialog => Body => Close => button

When I look at bootstrap and Ant Design popover designs, I find that they both add role=”document” to the section layer. After having this property tested by myself. Our previous reading sequence would look like this:

Close => button => End => Title => Web dialog => body

It can be seen that the order of reading has changed, the button information will be read first and then the popover information will be read. The second thing is here if our button is the last accessible element in the popover, it’s going to say the word “end” at the same time. Aria-describedby is used to describe the description of the describedby conditions.

Material – UI does not have this property, and similarly uses role=”presentation” and role=”none presentation”. But the result of reading this property is similar to the first method. No other features specifically read were found.

Because aria-Describedby is optional, so both conditions are possible, we can combine their actual projects to see.

Three, mouse access

When the user moves the mouse over the close button mentioned in the previous section.

Since it has been replaced with an icon, the user does not know what the button is for. Add title=” click to close the dialog “to the button, and when the user mouse over the button, they will see a bubble prompt with” click to close the dialog “.

Of course, our title will also be captured by the screen reader. The above two ways will hear the following reading:

  1. Title => Web dialog box => Body => Close => button => click close dialog box
  2. Close => button => End => Title => Web dialog => Click close dialog

Similarly, we can add the title attribute to the mask to clearly inform the user that the popover can be closed after the mask is clicked. “Some popover will not be closed when the mask is clicked”.

Four, no CSS

With no CSS this point usually means that our CSS resources may fail to load. The simplest and most crude way to do this is to inline HTML with CSS styles.

There is no CSS resource request and there is no resource load failure. It is the default of many build tools today. This is one way to optimize our web loading experience.

And of course there’s a little bit of trivia here. It is true that there is no CSS popover. Those are some of the default popovers in our browser’s built-in UI.

  • window.alert('hello world! ');
  • window.confirm('hello world! ');
  • window.prompt("Please enter your name","");
  • Window. CreatePopup ();IE only
  • HTML dialogobjectdomDialog.show()ordomDialog.showModal()

In real projects, we tend to replace this logic with custom styles. However, we can refer to these native syntax for our custom popover interface design.

Or for products where we don’t have UI requirements, using the default popover is actually a good choice (except that it’s not as visually satisfying as expected).

Five, JS

In addition to the logic that the CSS resource did not load successfully, there may be a bug in your JS that causes the popover event to not be initialized or the event that triggered the popover display to not be executed.

Often do barrier-free access to handle JS problems are to take advantage of CSS capabilities to do degradation. There are two ways I know how to display popovers with CSS.

1. Chekckbox switch

dialog{
  display:none;
}
.dialog-switch:checked ~ dialog{
  display:block;
}
Copy the code
<label for="dialogSwitch">According to the pop-up</label>
<input id="dialogSwitch" type="checkbox" class="dialog-switch" />
<dialog>
    <label for="dialogSwitch">Close Windows</label>I am a popup window<dialog>
Copy the code

Codepen online Demo

2. Target trigger

dialog{
  display:none;
}
dialog:target{
  display:block;
}
Copy the code
<a href="#dialog">According to the pop-up</a>
<dialog id="dialog">
    <a href="# #">Close Windows</a>I am a popup window<dialog>
Copy the code

Codepen online Demo

Both methods use pure CSS to trigger the popover’s display logic. But both are intrusive changes to HTML. So here is just to provide you with a reference idea.

Six, screen adaptation

There are thousands of user devices, and you can’t go around them.

Screen adaptation can be very passive once it starts to consider whether it is compatible with PC or M-station or user’s different devices.

My experience is that it’s more elegant to go straight to the resolution. That means that every user in this resolution range should look like this, and I don’t care what device you are.

Because it is adaptive resolution, so we can be split into two directions to adapt.

The transverse

<dialog open class="dialog-box">
    <section class="dialog">
       <! -- Popover content -->
    </section>
</dialog>
Copy the code
.dialog-box{ / *... * / }
.dialog{
  background-color:#ffffff;
  padding: 16px;
  box-sizing:border-box;
  margin-left: auto;
  margin-right:auto;
  max-width:480px; / * * /
  width:90%;
}
Copy the code

Codepen online Demo

Horizontal adaptation is relatively simple, which is to use max-width to make our popover present a horizontal flow state. So our popovers are centered at more than 480 pixels per 90% of the screen, and at less than 90% of the screen. It doesn’t matter what device it is.

The longitudinal

The same thing with landscape is that popovers should be centered when the browser window is higher than the popover. The difference is that when the browser height is smaller than the popover height, there is no place to expand, so we can only use the scroll bar. The scroll bar has two ways of inner scroll and outer scroll.

  • Inner scroll: Scroll in the title and bottom button area, it may appear that the browser height is smaller than the content outside the inner scroll area;
  • Outside scroll: scroll the whole popover area, the title and button area will be covered;

This is chosen based on the actual scenario, as shown here in the example of out-of-scope scrolling.

.dialog-box{
  /* And landscape the same code */
  overflow:auto;
  display:flex;
}
.dialog{
  /* And landscape the same code */
  margin:auto;
}
Copy the code

Codepen online Demo

If your product can use flex layout then congratulations, you only need this little bit of code to implement the above logic.

If your product can’t use Flex, you may need to consider a few gimmicks.

/* * * */
.dialog-box{
  text-align:center;
}
.dialog-box:after{
  content:' ';
  display:inline-block;
  height:100%;
  border-left:1px solid red; /* For display purposes, you don't need */
  vertical-align:middle;
  margin-left: -8px;
}
.dialog{
  text-align:left;
  display:inline-block;
  vertical-align:middle;
}
Copy the code

Codepen online Demo

With this you get a perfectly compatible popover that works at all resolutions.

conclusion

You can see that little popover covers almost all of the accessibility details that need to be considered in a web page. What I would say about accessibility is that it’s a feature that’s hard to see the benefits of quickly.

A website, it is not barrier-free to do, the basic can be used normally. But if one day your mouse suddenly died and you were anxious to use the site, you would joke about how bad the experience was.

Here I wrote a react Dialog component @_nu/ React-dialog that can customize UI based on the above theory. Since I just wrote it and haven’t tested it yet, there may be a bug. If you are interested, please send me an issue.

The technical solutions and documents of the project mentioned in the article are listed as follows:

  1. WAI – ARIA Authoring Practices of 1.2
  2. @davidtheclark/focus-trap
  3. @davidtheclark/focus-trap-react
  4. @davidtheclark/react-aria-modal
  5. bootstrap-modal
  6. ant design-modal
  7. material-ui dialog