Accessibility for MUI components

Introduction

In the early days of the web, websites were a vast playground for creativity and experimentation. Everyone wanted to stand out, craft a unique design, and develop custom form fields, menus, and pop-ups that would make their site memorable. Unexpected background music, animations, videos — everything was fair game to impress users. Developers created their own UI elements, some successful, some not so much. Naturally, as the web grew, there was a need to bring order to this chaos and make websites more user-friendly. That’s where W3C came in, an organization that establishes web standards, including accessibility guidelines.

Accessibility isn't just for people with disabilities (such as those with visual or hearing impairments), as many tend to assume and therefore, often ignore.

For me, accessibility is about courtesy in web development. A website should help users accomplish their goals — whether it's logging in, filling out a form, or finding information — rather than forcing them to struggle with navigation or decipher error messages written in tiny, barely visible text.

Accessibility is about respect for the user. Want to navigate with a keyboard? No problem. Need to understand why your form submission failed? Here’s a clear, visible error message in an easily distinguishable color, along with a notification that stays on screen long enough to be read. Prefer reading on a dark background? Here’s a toggle for dark mode. Accessibility is a collection of small but crucial details that make an interface intuitive and user-friendly.

This is why accessibility is a collaboration between designers and frontend developers. A well-designed interface isn't just about color palettes and layout, it also requires thoughtful implementation. That means using proper ARIA attributes, handling focus correctly, and taking care of many other small but important details.

That’s why I appreciate using pre-built component libraries like MUI, which are designed with accessibility in mind and come with built-in attributes. However, that doesn’t mean they’re foolproof, you still need to know how to use them correctly. In this article, I’ll highlight key considerations when working with MUI components. This is by no means an exhaustive list, and I’ll also touch on some of MUI’s shortcomings along the way.

1. Using Color Schemes for Native Elements

If your website includes a light/dark theme toggle, don’t forget to add enableColorScheme to the <CssBaseline> component. This ensures that native elements like the scroll view also adapt to the dark theme, maintaining a consistent appearance.

<CssBaseline enableColorScheme />

2. Highlighting Focused Elements (for Keyboard Navigation)

When using a mouse, we rely on the :focus pseudo-class to indicate focus. However, when navigating with a keyboard, :focus doesn’t always work, :focus-visible is triggered instead. Many developers either forget about this or aren’t aware of it.

Fortunately, MUI provides built-in focus styles, but if you're using a custom theme with your own colors, don’t forget to override the focus styles as well.

You can define focus styles globally using:

<GlobalStyles
  styles={{
    '.focus-visible:focus-visible': {
      outline: `2px solid ${theme.palette.primary.main}`
    }
  }}
 />

or apply them to individual components where needed. For example:

createTheme({
  components: {
    MuiMenuItem: {
      styleOverrides: {
        root: {
          ':focus-visible': {
            background: theme.palette.action.hover
          }
        }
      }
    }
  }
})

This ensures that focused elements remain clearly visible, enhancing accessibility for keyboard users.

2.1 Material Design Inconsistencies

There is a certain inconsistency in how links and Material Design components handle focus states in MUI. For example, when a button is focused via keyboard navigation, it doesn’t get a blue outline like links do. Instead, a circular ripple effect appears in the background to indicate focus.

This isn’t an MUI-specific issue, it’s a quirk of Material Design itself, which MUI follows. There’s even an open issue about this in the repository, but as of now, it remains unresolved. Hopefully, future versions will address this and provide a more consistent focus indication across components.

3. Semantics

There are plenty of articles on this topic, but the key idea is simple: every HTML tag has a specific purpose. Instead of using <div> for everything, it's better to choose the most appropriate elements for the context.

A common mistake is using a link where a button is needed, and vice versa. Links (<a> or <Link>) should be used for navigation whether to external resources or different pages within your app. Buttons (<button> or <Button>) are meant for actions like submitting forms, opening pop-ups, or triggering UI interactions.

Using the right elements not only improves accessibility but also helps browsers and assistive technologies interpret your page correctly.

3.1 “Buttons Inside Buttons”

Sometimes designers create clickable blocks that contain buttons inside them. However, nesting a <button> inside another <button> is incorrect HTML and breaks accessibility.

If the element looks like a card, the ideal choice would be MUI’s <Card> component, specifically <CardActionArea>, which designates the main clickable area. However, <CardActionArea> doesn’t fully cover our case because it doesn’t overlap with another button inside the card.

What if both the area and the inner button need to be clickable?

One possible solution is using <CardActionArea> with component="div", so it renders as a <div> instead of a <button>. However, I see two issues with this approach:

  1. Using <CardActionArea> outside the context of <Card> feels semantically off.
  2. When focused, it behaves like a button with a shadow effect, rather than using an outline (see section 2.1), which might not always match the intended design.

Using <button component="div"> has the same issue as point 2 above.

If we simply use a <div>, making it fully accessible would require a lot of extra attributes and logic. Instead, I prefer using <ButtonBase>, which is MUI’s low-level button component. It includes all necessary accessibility attributes but comes without styles, giving full control over the design.

Here’s how it looks in practice:

<ButtonBase component='div' onClick={...} className='focus-visible'>

Of course, styling is still required, but this approach allows for nesting buttons without breaking accessibility.

3.2 One Click — One Action

There’s a simple rule: one click = one event. However, designers sometimes break this rule with :hover effects.

A common issue is when hovering over an element triggers a large tooltip with extra details, while clicking the same element performs an action (e.g., navigating to a details page). This creates problems for both keyboard users and touchscreen devices, where hover doesn’t exist. On touchscreens, there’s only a tap, and for keyboard users, there’s focus.

How to handle this?

For keyboard navigation, avoid JavaScript workarounds like “first click shows the tooltip, second click triggers the main action.” This approach forces keyboard users to press twice, while mouse users only click once, which is inconsistent and frustrating. That’s why I like the one click = one action rule, it’s simple, intuitive, and predictable.

What about hover-based tooltips?

Instead of relying on :hover, display the tooltip when the element is focused using :focus-visible.

What about touchscreens?

Simply put — you don’t show the tooltip. Designers should understand that important information shouldn't be hidden inside a hover effect, as it won’t be accessible on mobile. Instead, key details should be visible by default or accessible in another way (e.g., through an info icon that users can tap).

4. Content Loading and Feedback Indicators

Providing timely feedback to users is crucial, whether it’s confirming that an action has been triggered or indicating that content is still loading. Users should always have a clear understanding of what’s happening on the page.

One of the best practices is using Skeleton components, which can be customized to mimic the structure of the actual content, such as a table or text blocks. This helps set user expectations about what will appear once the data loads.

Another useful approach is replacing a standard Button with LoadingButton. This solves multiple problems at once:

  • It visually indicates that an action has been triggered (e.g., data submission, form processing) by displaying a spinner when loading={true}.
  • It prevents double-clicking, reducing the risk of sending multiple requests by mistake.

These small details significantly improve user experience and accessibility, making interactions smoother and more predictable.

5. Keyboard Navigation

Users primarily rely on two key combinations for navigation:

  • Tab – moves focus to the next interactive element.
  • Shift + Tab – moves focus to the previous interactive element.

Other keys, like Arrow keys, Space, and Enter, are used for interacting within elements, but Tab is the only way to move between them.

The Problem: Context Menus

Context menus, which usually open via a right-click, cannot be accessed with the keyboard. This creates an accessibility issue since keyboard users are unable to interact with these options.

The Solution

To ensure full keyboard accessibility, an alternative way to access context menu actions should be provided. One of the best approaches is to replicate the menu functionality nearby (or within the element itself).

A common and effective pattern is using a "..." button that opens a dropdown menu with the same options as the context menu. This method is widely used and ensures that all users, regardless of their input method, can access the necessary actions.

5.1 Custom Design Components

Sometimes designers come up with custom components that are difficult to implement using standard UI elements but serve a specific purpose in a given context. Before diving into development, it's worth discussing with the designer whether the custom component is truly necessary and how it should work with keyboard navigation.

Building custom interactive components is usually time-consuming, not only in terms of implementation but also in defining and refining their expected behavior. If handling keyboard interactions properly is too complex, a practical workaround is to provide a standard alternative alongside the custom component. This way, users can choose to interact with the UI using either a mouse or a keyboard, ensuring accessibility without overcomplicating the development process.

5.2 Pressing Enter to Submit a Form

One of the easiest ways to test your application’s accessibility is to navigate through it using only a keyboard. Ask yourself:

  • Is it comfortable to use?
  • Would you change anything?
  • Is the focus order logical?

Optimizing Form Submission

For many forms, it’s more intuitive to submit them by pressing Enter while focused on an input field, rather than Tabbing to the submit button first and then pressing Enter. By allowing users to submit a form directly from an input field, you eliminate one extra step, making interactions faster and smoother.

This behavior is already default for <form> elements, but if your form uses custom handlers, you may need to ensure that pressing Enter triggers the submission when inside an input field.

<TextField onKeyDown={(event: KeyboardEvent<HTMLDivElement>) => {
  if (event.key === 'Enter') { ... }
 }}>

5.3 Focus Trap

MUI does a fantastic job handling accessibility out of the box. One great example is dialogs and modals, where focus is automatically trapped inside the open window, preventing users from tabbing outside of it. This ensures predictable keyboard navigation experience. But what’s even more impressive is that MUI provides developers with a built-in mechanism to create custom focus traps using focus-trap. This allows developers to apply the same accessibility principles to other components where restricting focus within a specific area is necessary.

This is particularly useful for:

  • Custom dropdowns or menus
  • Popovers that should stay focused until dismissed
  • Interactive widgets that require constrained navigation

6. Chrome Extensions for Accessibility

When developing an application, it’s easy to overlook accessibility issues. Fortunately, some great Chrome extensions can help automate this process:

  1. Lighthouse – a powerful tool for evaluating performance, best practices, SEO, and accessibility. It provides a detailed accessibility score and suggests improvements.
  2. Accessibility Insights for Web – specifically designed to detect accessibility issues and suggest possible fixes, making it an essential tool for improving web accessibility.

Using these plugins regularly can save time, catch hidden issues, and ensure your application meets accessibility standards.

7. Other Useful Tools

Besides Chrome extensions, there are other great tools worth considering:

  • axe DevTools® for Web – a paid but highly powerful tool for in-depth accessibility testing. It provides detailed reports and actionable recommendations.
  • Auto-VO – a free screen reader simulation for testing web applications. It helps developers understand how their app sounds when navigated using assistive technologies (tutorial).

Conclusion

Ensuring accessibility in web applications is not just about compliance—it’s about respecting users and improving their experience. By following best practices and leveraging tools like MUI components, focus management, and accessibility testing plugins, developers can create inclusive and user-friendly interfaces.

Комментарии

Популярные сообщения из этого блога

Стайлгайд и компонентная разработка

Погружение в React Native: навигация, работа оффлайн, пуш нотификации

z-index