No results found

Accessibility

Accessibility

The Americans with Disabilities Act (ADA) requires applications to be accessible to people with disabilities. Following accessibility best practices helps:

Getting started

The Aria APG is a great starting point for understanding how to make your component pieces and API's accessible.

Keep in mind, that this is more of a starting point and not a comprehensive guide. It is important to test your component on a regular basis to ensure that it is accessible to all users.

Qwik specific

Most accessibility features in Qwik work through standard HTML attributes added on initial render.

There are however, some Qwik specific features that can be used to manage attributes, elements, focus management, and state.

Attributes and state

In this example, we're creating an accessible toggle button that properly announces its state to screen readers:

const isPressedSig = useSignal(false);

const handleClick$ = $(() => {
    isPressedSig.value = !isPressedSig.value
})

return (
  <>
    <button 
      onClick$={[handleClick$, props.onClick$]}
      aria-pressed={isPressedSig.value}
    >
      Toggle
    </button>
  </>
);

Make sure to use reactive state to manage attributes (signals, stores, props).

In the case of data-* attributes, you can use a ternary to indicate the presence of the attribute, otherwise it will be undefined.

const isPressedSig = useSignal(false);

const handleClick$ = $(() => {
    isPressedSig.value = !isPressedSig.value
})

return (
  <>
    <button 
      onClick$={[handleClick$, props.onClick$]}
      aria-pressed={isPressedSig.value}
      data-pressed={isPressedSig.value ? '' : undefined}
    >
      Toggle
    </button>
  </>
);

Refs

Signals in Qwik are also refs. This means that you can use a signal to manage the ref for an element.

The ref value becomes the DOM element when it is resolved on the client.

const triggerRef = useSignal<HTMLButtonElement>();

const handleClick$ = $(() => {
    // on the client I resolve to the button
    console.log(triggerRef.value)
})

return (
    <button ref={triggerRef} onClick$={[handleClick$, props.onClick$]}>
       <Slot />
    </button>
)

Merging refs

The ref attribute can also accept a function, with the first argument being the element in the DOM when resolved on the client.

This can be useful when consumers of your component provide a ref to the component.

return (
    <button ref={(el) => {
        context.triggerRef = el;
        
        if (props.ref) {
            props.ref.value = el;
        }
    }}>
        <Slot />
    </button>
)

The ref function can also be used for other more complex use cases, such as handling an array of refs.

Managing Multiple Instances

When working with multiple instances of components (like tabs, accordions, or carousels), it's important to manage DOM elements correctly for accessibility. Never use querySelector or getElementById - instead, use Qwik's ref system.

Single Instance vs Multiple Instances

For a single instance (e.g., one modal trigger):

// In your root component
const triggerRef = useSignal<HTMLButtonElement>();

// Make it available to child components
const context = {
  triggerRef
};
useContextProvider(modalContextId, context);

For multiple instances (e.g., carousel slides), use an array of refs:

// In your root component
type CarouselContext = {
  slideRefs: Signal<HTMLElement[]>;
}

const context: CarouselContext = {
  slideRefs: useSignal([]),
};
useContextProvider(carouselContextId, context);

Managing Individual Refs

When rendering multiple instances, each needs its own ref:

export const CarouselSlideBase = component$((props) => {
  const context = useContext(carouselContextId);
  const slideRef = useSignal<HTMLElement>();

  useTask$(function setIndexOrder({ cleanup }) {
    if (props._index !== undefined) {
      context.slideRefsArray.value[props._index] = slideRef;
    } else {
      throw new Error('Qwik UI: Carousel Slide cannot find its proper index.');
    }

    cleanup(() => {
      context.slideRefsArray.value[props._index] = undefined;
    })
  });

  return (
    <Render
      ref={slideRef}
      role="tabpanel"
      aria-hidden={context.currentIndex.value !== props._index}
    >
      <Slot />
    </Render>
  );
});

export const CarouselSlide = withAsChild(CarouselSlideBase, (props) => {
  const index = getNextIndex("carousel")

  props._index = index;

  return props;
});

Please read the indexing docs for more information on how to manage multiple instances of components.

Notice that an array of refs is used to manage the refs for each instance. You will also need to use the resetIndexes(namespace) to tie the component to the render lifecycle.

Using Refs for focus management

Once refs are set up, you can manage focus and ARIA attributes properly:

const focusNextSlide$ = $((currentIndex: number) => {
    const slides = context.slideRefs.value;
    const totalSlides = slides.length;

    if (!totalSlides) return;

    const nextIndex = (currentIndex + 1) % totalSlides;
    return focusSlide(slides[nextIndex], nextIndex);
});

JSX in Qwik

There are a couple of differences between JSX and HTML that are important to know when working with Qwik.

Self-Closing Tags

Unlike HTML which can be forgiving with unclosed tags, JSX requires explicit closing of all elements. For elements that can't have children (like img, input, etc.), use self-closing syntax:

// ❌ Invalid JSX
<img src="/images/logo.png">

// ✅ Valid JSX
<img src="/images/logo.png" />

Case Sensitivity

Tags

HTML tags must be lowercase in JSX:

// ❌ Invalid JSX
<MAIN>
  <H1>Hello World!</H1>
</MAIN>

// ✅ Valid JSX
<main>
  <h1>Hello World!</h1>
</main>

Attributes

Most attributes in JSX use camelCase:

// ❌ Invalid JSX
<video
  src="/videos/demo.mp4"
  autoplay={true}
/>

// ✅ Valid JSX
<video
  src="/videos/demo.mp4"
  autoPlay={true}
/>

Exception: data-* and aria-* attributes keep their original kebab-case format:

<button
  data-test-id="submit-button"
  aria-label="Submit form"
>
  Submit
</button>

Inline Styles

In JSX, the style attribute takes an object instead of a string:

// ❌ Invalid JSX
<div style="color: blue; font-size: 16px;">

// ✅ Valid JSX
<div style={{ color: 'blue', fontSize: '16px' }}>

CSS properties are written in camelCase:

Event Handlers in Qwik

Qwik has a unique approach to event handlers using the $ suffix:

export const Counter = component$(() => {
  const count = useSignal(0);

  // ❌ Invalid in Qwik
  return <button onClick={() => count.value++}>

  // ✅ Valid in Qwik
  return <button onClick$={() => count.value++}>
    Count: {count.value}
  </button>
});

The $ suffix is crucial in Qwik as it enables modular lazy-loading of code.

Expression Slots

Like React, Qwik JSX uses curly braces {} for JavaScript expressions:

export const Greeting = component$(() => {
  const name = "World";
  return (
    <div>
      <h1>Hello {name}!</h1>
      <div>{2 + 2}</div>
      <div>{getGreeting()}</div>
    </div>
  );
});

Boolean Attributes

In JSX, boolean attributes can be written in shorthand when the value is true:

// These are equivalent:
<input disabled={true} />
<input disabled />

// To set false, omit the attribute:
<input /> // disabled={false}

A11y Testing

Automated Accessibility Testing

This should be the first step in testing your application for accessibility. Automated testing tools can help identify accessibility issues with minimal effort.

Manual Accessibility Testing

Manual testing should not be skipped. It is beneficial to test using different platforms and devices and review and fix any issues that automated testing may have missed. While pairing of screen readers and browsers is outside the scope of this guide, it is important to note that there are screen readers that work better with certain browsers and platforms.

Testing order:

1. Keyboard Navigation Testing

2. Screen Reader Testing

Cross-Platform Accessibility Testing

Testing accessibility across multiple platforms and devices is essential to ensure your application is accessible to all users.

Note: This section does not replace automated and manual testing, it is in addition to those methods.

Test with key platforms and assistive technologies

Establish a Testing Plan

Continuous process

Accessibility testing should be integrated into your development workflow. Test regularly and continuously improve - solving an issue on one platform might affect another, so ongoing testing across platforms is essential.

Accessibility Resources

A growing collection of useful links, tools, and resources to have handy for your everyday accessibility work.

Accessibility Standards

Useful Resources and References

Guides & Cheatsheets

Official Screen Reader User Guides

NVDA
JAWS
Narrator
VoiceOver
TalkBack

Screen Reader Cheatsheets

Checklists

Auditing and Testing Tools

Virtual Machines

Accessibility Auditing

Browser Extensions & Bookmarklets

Other Helpful Resources