---
id: 672a54f29d783890d1f94740
title: What Are Some Common ARIA States Used on Custom Control Elements?
challengeType: 19
dashedName: what-are-some-common-aria-states-used-on-custom-control-elements
---

# --interactive--

Semantic form control elements like `input`, `select`, `textarea`, `button`, and `fieldset` have built-in states that are conveyed to assistive technologies.

For example, you could use the `disabled` attribute to disable a button or the `checked` attribute to indicate that a checkbox is checked.

But if you are creating a custom control element, you need to use ARIA attributes to convey the state of the control to assistive technologies.

In this lesson, we will discuss a few common ARIA states that you can use on custom control elements.

The first ARIA state we will discuss is `aria-selected`. This state is used to indicate that an element is selected. You can use this state on custom controls like a tabbed interface, a listbox, or a grid.

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`.

Another common ARIA state is `aria-disabled`. This state is used to indicate that an element is disabled only to people using assistive technologies, such as screen readers. It is important to note that `aria-disabled` does not actually disable the element. It is up to you, the developer, to make it look and act like a disabled element. This attribute is also commonly used on native HTML elements in place of the `disabled` attribute. Which one you choose will depend on the context the button is being used.

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`:

:::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>
  <li role="menuitem" tabindex="-1">New</li>
  <li role="menuitem" tabindex="-1">Save</li>
  <li role="menuitem" tabindex="-1">Delete</li>
</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.

If the label already has the word `required`, then you should omit the `aria-required` attribute. This ensures that screen readers only announce the word required once.

In most cases, you will probably use the native `label` and `form` elements with the `required` attribute. But if you need to create a custom form control, then it is important to add the `aria-required` attribute when necessary.

Additionally, the `aria-required` attribute can also be used on native form inputs, such as the `input`, `textarea`, and `select` elements. This is often preferred to the native required attribute, since the required attribute may have potential usability and accessibility concerns, particularly with the default error handling provided by the browser. Ultimately, you will need to test in order to determine which attribute is best for your situation.

The last ARIA state we will discuss is `aria-checked`. This attribute is used to indicate whether an element is in the checked state. It is most commonly used when creating custom checkboxes, radio buttons, switches, and listboxes.

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`.

Native elements typically have better support and built-in accessibility features.

However, if you must create custom controls, using ARIA attributes is essential to convey the state of these controls to assistive technologies effectively.

As always, test your work to ensure that ARIA attributes are applied correctly and that the custom control functions in a way that is both accessible and user-friendly.

# --questions--

## --text--

What ARIA state would you use to indicate that a tab is currently selected?

## --answers--

`aria-disabled`

### --feedback--

This state shows which tab is active.

---

`aria-selected`

---

`aria-haspopup`

### --feedback--

This state shows which tab is active.

---

`aria-checked`

### --feedback--

This state shows which tab is active.

## --video-solution--

2

## --text--

In the context of custom controls, which ARIA state is used to indicate that an element is currently disabled?

## --answers--

`aria-checked`

### --feedback--

This state shows that an element cannot be interacted with.

---

`aria-disabled`

---

`aria-selected`

### --feedback--

This state shows that an element cannot be interacted with.

---

`aria-haspopup`

### --feedback--

This state shows that an element cannot be interacted with.

## --video-solution--

2

## --text--

When you want to indicate that a menu item has a submenu, which ARIA state should be used?

## --answers--

`aria-checked`

### --feedback--

One of the state heavily suggests that a popup is present.

---

`aria-disabled`

### --feedback--

One of the state heavily suggests that a popup is present.

---

`aria-haspopup`

---

`aria-selected`

### --feedback--

One of the state heavily suggests that a popup is present.

## --video-solution--

3
