feat(curriculum): add interactive examples to common aria states lesson (#63887)

This commit is contained in:
Sagar Kumar
2025-11-18 03:02:57 +05:30
committed by GitHub
parent 3f226362f2
commit 93ceb61b7c

View File

@@ -5,7 +5,7 @@ challengeType: 19
dashedName: what-are-some-common-aria-states-used-on-custom-control-elements
---
# --description--
# --interactive--
Semantic form control elements like `input`, `select`, `textarea`, `button`, and `fieldset` have built-in states that are conveyed to assistive technologies.
@@ -19,14 +19,96 @@ The first ARIA state we will discuss is `aria-selected`. This state is used to i
Here is an example of how you can use `aria-selected` on a custom tab control:
:::interactive_editor
```html
<link rel="stylesheet" href="styles.css">
<div role="tablist">
<button role="tab" aria-selected="true">Tab 1</button>
<button role="tab" aria-selected="false">Tab 2</button>
<button role="tab" aria-selected="false">Tab 3</button>
</div>
<script src="index.js"></script>
```
```css
[role="tablist"] {
display: flex;
border-bottom: 2px solid #ddd;
gap: 0.25rem;
font-family: system-ui, sans-serif;
}
[role="tab"] {
appearance: none;
border: none;
background: none;
padding: 0.5rem 1rem;
cursor: pointer;
font-size: 1rem;
color: #444;
border-radius: 4px 4px 0 0;
transition: background-color 0.2s, color 0.2s;
}
[role="tab"]:hover {
background-color: #f3f3f3;
}
[role="tab"][aria-selected="true"] {
background-color: #fff;
color: #0078d4;
border: 2px solid #0078d4;
border-bottom: 2px solid #fff;
font-weight: 600;
position: relative;
z-index: 1;
}
[role="tab"]:focus {
outline: 2px solid #0078d4;
outline-offset: 2px;
}
```
```js
document.addEventListener("click", (event) => {
const clickedTab = event.target.closest('[role="tab"]');
if (!clickedTab) return;
const tablist = clickedTab.closest('[role="tablist"]');
const tabs = tablist.querySelectorAll('[role="tab"]');
tabs.forEach((tab) => {
const isSelected = tab === clickedTab;
tab.setAttribute("aria-selected", isSelected);
tab.tabIndex = isSelected ? 0 : -1;
});
});
document.addEventListener("keydown", (event) => {
const activeTab = document.activeElement;
if (activeTab.getAttribute("role") !== "tab") return;
const tablist = activeTab.closest('[role="tablist"]');
const tabs = Array.from(tablist.querySelectorAll('[role="tab"]'));
const index = tabs.indexOf(activeTab);
let newIndex = index;
if (event.key === "ArrowRight") newIndex = (index + 1) % tabs.length;
if (event.key === "ArrowLeft") newIndex = (index - 1 + tabs.length) % tabs.length;
if (newIndex !== index) {
tabs[newIndex].focus();
tabs[newIndex].click();
}
});
```
:::
Tabs are used to display multiple panels of content in a limited space. The `aria-selected` state is used to indicate which tab is currently selected.
When the user selects a tab, the `aria-selected` state of the selected tab is set to `true`, and the `aria-selected` state of the other tabs is set to `false`.
@@ -35,19 +117,64 @@ Another common ARIA state is `aria-disabled`. This state is used to indicate tha
Here is an example of how you can use `aria-disabled` on a custom edit button:
:::interactive_editor
```html
<link rel="stylesheet" href="styles.css">
<div role="button" tabindex="-1" aria-disabled="true">Edit</div>
```
```css
[role="button"] {
display: inline-block;
background-color: #0078d4;
color: #fff;
padding: 0.5rem 1rem;
border-radius: 6px;
font-family: system-ui, sans-serif;
font-size: 1rem;
text-align: center;
cursor: pointer;
user-select: none;
transition: background-color 0.2s, transform 0.1s, opacity 0.2s;
}
[role="button"]:not([aria-disabled="true"]):hover {
background-color: #005fa3;
}
[role="button"]:not([aria-disabled="true"]):focus {
outline: 2px solid #005fa3;
outline-offset: 2px;
}
[role="button"]:not([aria-disabled="true"]):active {
transform: scale(0.97);
}
[role="button"][aria-disabled="true"] {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
background-color: #b0b0b0;
color: #f2f2f2;
}
```
:::
The `aria-disabled` attribute is used to tell screen reader users that the edit button is disabled and cannot be interacted with. Again, it does not actually disable the button. When using `aria-disabled`,you will need to apply styling and JavaScript to make the control look and behave like a disabled button.
In most cases, you will probably use the native button element, but there are cases where you might need to use a custom control. So, it is essential to know how to convey the state of the control to assistive technologies.
The next ARIA state we will discuss is `aria-haspopup`. This state is used to indicate that an interactive element will trigger a popup element when activated. You can only use the `aria-haspopup` attribute when the popup has one of the following roles: `menu`, `listbox`, `tree`, `grid`, or `dialog`. The value of `aria-haspopup` must be either one of these roles or `true`, which defaults to the `menu` role.
Here is an example of a file editor menu that uses `aria-haspopup`:
Here is an example of a file editor menu that uses `aria-haspopup`:
:::interactive_editor
```html
<link rel="stylesheet" href="styles.css">
<button id="menubutton" aria-haspopup="menu" aria-controls="filemenu" aria-expanded="false">File</button>
<ul id="filemenu" role="menu" aria-labelledby="menubutton" hidden>
<li role="menuitem" tabindex="-1">Open</li>
@@ -57,18 +184,127 @@ Here is an example of a file editor menu that uses `aria-haspopup`:
</ul>
```
```css
#menubutton {
background-color: #0078d4;
color: #fff;
border: none;
padding: 8px 14px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
position: relative;
}
#menubutton:hover,
#menubutton:focus {
background-color: #005ea2;
outline: none;
}
#filemenu {
list-style: none;
padding: 4px 0;
margin: 4px 0 0;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
width: 160px;
position: absolute;
z-index: 1000;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
#filemenu[hidden] {
display: none;
}
#filemenu [role="menuitem"] {
display: block;
padding: 8px 12px;
font-size: 14px;
color: #333;
cursor: pointer;
}
#filemenu [role="menuitem"]:hover,
#filemenu [role="menuitem"]:focus {
background-color: #e5f1fb;
outline: none;
}
#filemenu [role="menuitem"]:focus-visible {
box-shadow: inset 0 0 0 2px #0078d4;
}
```
:::
The `aria-haspopup` state is used to indicate that the `File` menu button will open a popup menu when activated. Screen reader users may hear this additional information when they navigate to the button.
You will need to use JavaScript to show and hide the popup menu, and to implement proper keyboard support for interacting with the menu. Also, please note that the ARIA `menu` role refers to a very specific type of menu. It generally refers to a list of actions that the user can invoke, similar to a menu on a desktop application. It does not include more common uses of what we typically refer to as "menus", such as navigation menus. Realistically, most "menus" you create on the web will not be ARIA menus and you will not use `aria-haspopup` with them.
The next ARIA state we will discuss is `aria-required`. The `aria-required` attribute is used to indicate that a field needs to be filled out before the form is submitted.
Here is an example of working with the `aria-required` attribute for a custom form control.
:::interactive_editor
```html
<link rel="stylesheet" href="styles.css">
<div id="name-label">Full Name*</div>
<div role="textbox" contenteditable aria-labelledby="name-label" aria-required="true" id="name"></div>
```
```css
#name-label {
font-family: system-ui, sans-serif;
font-size: 0.95rem;
font-weight: 600;
margin-bottom: 0.25rem;
color: #333;
}
[role="textbox"] {
display: block;
width: 100%;
min-height: 2rem;
padding: 0.5rem 0.75rem;
border: 1.5px solid #ccc;
border-radius: 4px;
font-family: system-ui, sans-serif;
font-size: 1rem;
color: #222;
background-color: #fff;
line-height: 1.4;
transition: border-color 0.2s, box-shadow 0.2s;
}
[role="textbox"]:hover {
border-color: #999;
}
[role="textbox"]:focus {
outline: none;
border-color: #0078d4;
box-shadow: 0 0 0 3px rgba(0, 120, 212, 0.25);
}
[role="textbox"]:empty::before {
content: attr(data-placeholder);
color: #aaa;
pointer-events: none;
}
[role="textbox"][aria-required="true"] {
border-left: 3px solid #e81123;
padding-left: calc(0.75rem - 3px);
}
```
:::
We need to use the `contenteditable` attribute so users can type in their input. We are also using the `aria-required` attribute set to `true` to indicate that this custom form control is required.
To make the form control look like a normal form control, you would need to add CSS. You would also need to add JavaScript to prevent the form from being submitted without content.
@@ -83,10 +319,64 @@ The last ARIA state we will discuss is `aria-checked`. This attribute is used to
Here is an example of how you can use `aria-checked` on a custom checkbox control:
:::interactive_editor
```html
<link rel="stylesheet" href="styles.css">
<div role="checkbox" aria-checked="true" tabindex="0">Checkbox</div>
```
```css
[role="checkbox"] {
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-family: system-ui, sans-serif;
font-size: 1rem;
cursor: pointer;
user-select: none;
color: #222;
}
[role="checkbox"]::before {
content: "";
display: inline-block;
width: 1rem;
height: 1rem;
border: 2px solid #666;
border-radius: 4px;
background-color: #fff;
transition: all 0.2s ease;
box-sizing: border-box;
}
[role="checkbox"]:hover::before {
border-color: #0078d4;
}
[role="checkbox"]:focus::before {
outline: 2px solid #0078d4;
outline-offset: 2px;
}
[role="checkbox"][aria-checked="true"]::before {
background-color: #0078d4;
border-color: #0078d4;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'><path fill='white' d='M5.2 10.4L2 7.2l1.1-1.1 2.1 2.1L10.9 2.5 12 3.6z'/></svg>");
background-repeat: no-repeat;
background-position: center;
}
[role="checkbox"][aria-disabled="true"] {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
}
```
:::
Native checkbox elements have a built-in `checked` state that is conveyed to assistive technologies. But if you are creating a custom checkbox control, you will need to use the `aria-checked` attribute to indicate its state.
When the user interacts with the custom checkbox control, you will need to use the `aria-checked` state to reflect the new state of the checkbox. When the checkbox is checked, the `aria-checked` attribute is set to `true`. When it is unchecked, it is set to `false`.