From 93ceb61b7c0985c6ea077bf40e4cd61695c32b2e Mon Sep 17 00:00:00 2001
From: Sagar Kumar <117724228+Unknownmaster0@users.noreply.github.com>
Date: Tue, 18 Nov 2025 03:02:57 +0530
Subject: [PATCH] feat(curriculum): add interactive examples to common aria
states lesson (#63887)
---
.../672a54f29d783890d1f94740.md | 294 +++++++++++++++++-
1 file changed, 292 insertions(+), 2 deletions(-)
diff --git a/curriculum/challenges/english/blocks/lecture-understanding-aria-expanded-aria-live-and-common-aria-states/672a54f29d783890d1f94740.md b/curriculum/challenges/english/blocks/lecture-understanding-aria-expanded-aria-live-and-common-aria-states/672a54f29d783890d1f94740.md
index ea2f49026c3..91f578d1e29 100644
--- a/curriculum/challenges/english/blocks/lecture-understanding-aria-expanded-aria-live-and-common-aria-states/672a54f29d783890d1f94740.md
+++ b/curriculum/challenges/english/blocks/lecture-understanding-aria-expanded-aria-live-and-common-aria-states/672a54f29d783890d1f94740.md
@@ -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
+
+
```
+```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
+
Edit
```
+```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
+
Open
@@ -57,18 +184,127 @@ Here is an example of a file editor menu that uses `aria-haspopup`:
```
+```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
+
Full Name*
```
+```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
+
Checkbox
```
+```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,");
+ 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`.