# Code

> Developer-focused accessibility guidance for ix design system, with practical code examples and implementation patterns to meet WCAG requirements and improve usability for everyone.

import Playground from '@site/src/components/Playground'
import AriaLabelProperties_html_html from '@site/docs/autogenerated/usage/html/aria-label-properties.html.md';

## Accessibility in HTML and JavaScript

This chapter describes best practices for designing HTML/JavaScript applications that work well for all users, including those who rely on assistive technologies.
We covered most common use cases in our components with built-in accessibility features.

For a detailed introduction to basic concepts and general techniques for designing accessible applications, see the [accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility) section of the [MDN Web Docs](https://developer.mozilla.org/en-US/).

### Accessibility attributes

Building accessible web experiences often involves setting [ARIA attributes](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) to provide semantic meaning where it might otherwise be missing. Use JavaScript to dynamically control the values of accessibility-related attributes.

When you work with ARIA attributes in JavaScript, use the `setAttribute()` method or direct property assignment:

```javascript
// Use setAttribute for ARIA attributes
const button = document.querySelector('button');
button.setAttribute('aria-label', 'Save document');

// Or use direct property assignment
button.ariaLabel = 'Save document';
```

Static ARIA attributes can be set directly in HTML:

```html
<!-- Static ARIA attributes require no extra syntax -->
<button aria-label="Save document"></button>
```

For iX components, use the dedicated `ariaLabel` attribute for elements contained inside components.
**Example**: Setting the `aria-label` for the icon buttons contained in ix-date-picker component.

<Playground
name="aria-label-properties"
height="25rem"
source={{
           html: {
             'aria-label-properties.html': AriaLabelProperties_html_html,
           }
        }}
files={{
                html: {
                    'aria-label-properties.html': 'html/aria-label-properties.html',
                }
        }}
onlyFramework={'html'}

> </Playground>

### Keyboard only and no keyboard trap

We recommend that all functions are usable with a keyboard and don’t depend on timed key presses, e.g. pressing enter within 3 seconds to confirm. The only exception is functionality that relies on the path of a movement, not just its start and end points, e.g. freehand drawing or signing a signature.

For components that use a simple, linear structure, stick to the default tab-based navigation. Make sure every clickable surface is both reachable and clickable by keyboard. You can also set the [tabindex](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/tabindex) attribute to manipulate the default tab-navigation.

For components that use a more complex, two-dimensional structure, consider implementing keyboard interaction using the arrow keys:

```javascript
// Arrow key navigation for complex components
function handleArrowNavigation(event, container) {
  const items = Array.from(
    container.querySelectorAll('[role="gridcell"], [role="option"]'),
  );
  const currentIndex = items.indexOf(event.target);

  switch (event.key) {
    case 'ArrowRight':
      focusItem(items[currentIndex + 1] || items[0]);
      break;
    case 'ArrowLeft':
      focusItem(items[currentIndex - 1] || items[items.length - 1]);
      break;
    case 'ArrowDown':
      // Implement based on your layout
      break;
    case 'ArrowUp':
      // Implement based on your layout
      break;
  }
}

function focusItem(item) {
  if (item) {
    item.focus();
  }
}
```

Always attempt to keep the focus on changes to prevent the user from manually having to navigate back to the previous location.

#### Focus management

Implement focus trapping for modal dialogs and other overlay components:

```javascript
function trapFocus(container) {
  const focusableElements = container.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
  );
  const firstFocusable = focusableElements[0];
  const lastFocusable = focusableElements[focusableElements.length - 1];

  container.addEventListener('keydown', function (event) {
    if (event.key === 'Tab') {
      if (event.shiftKey) {
        if (document.activeElement === firstFocusable) {
          lastFocusable.focus();
          event.preventDefault();
        }
      } else {
        if (document.activeElement === lastFocusable) {
          firstFocusable.focus();
          event.preventDefault();
        }
      }
    }
  });
}
```

#### How to test

Disconnect your mouse and try to operate your service using only the keyboard.

### Text alternatives and labels

Label any non-text content, e.g. images, buttons, links or inputs, to comply with [WCAG guideline 2.1](https://www.w3.org/WAI/WCAG22/quickref/?showtechniques=141%2C131#keyboard-accessible). The general rule to follow is:

1. Use `aria-labelledby`
2. Otherwise use `aria-label`
3. Otherwise use `alt` attribute
4. Otherwise use `title` attribute
5. If none of the above yield a usable text string, there is no accessible name

When you are using iX components, the relevant attribute will be set when you set the component's labelling attribute:

### React Examples

#### input-label.tsx
```tsx
import { IxInput } from '@siemens/ix-react';

export default () => {
  return (
    <IxInput
      label="Some label"
      placeholder="Some placeholder"
      value="Some example text"
      helperText="Some helper text"
    ></IxInput>
  );
};
```

### Angular Examples

#### input-label.ts
```ts
import { Component } from '@angular/core';

@Component({
  standalone: false,
  selector: 'app-example',
  templateUrl: './input-label.html',
})
export default class InputLabel {}
```

#### input-label.html
```html
<ix-input
  label='Some label'
  placeholder='Some placeholder'
  value='Some example text'
  helper-text='Some helper text'
></ix-input>
```

### Angular Standalone Examples

#### input-label.ts
```ts
import { Component } from '@angular/core';
import {
  IxInput,
  IxTextValueAccessorDirective,
} from '@siemens/ix-angular/standalone';

@Component({
  selector: 'app-example',
  imports: [IxInput, IxTextValueAccessorDirective],
  templateUrl: './input-label.html',
})
export default class InputLabel {}
```

#### input-label.html
```html
<ix-input
  label="Some label"
  placeholder="Some placeholder"
  value="Some example text"
  helper-text="Some helper text"
></ix-input>
```

### Vue Examples

#### input-label.vue
```vue
<script setup lang="ts">
import { IxInput } from '@siemens/ix-vue';
</script>

<template>
  <IxInput
    label="Some label"
    placeholder="Some placeholder"
    value="Some example text"
    helper-text="Some helper text"
  ></IxInput>
</template>
```

#### ARIA-labelledby

Use when there is already a text which describes the element. An example are forms where there is a field description followed by the input.

```html
This is the input label
<input aria-labelledby="input-name-label" />
```

#### ARIA-label

The `aria-label` describes elements that have no text, like images, buttons or links. The interaction result or impact
of clicking / activating a button or link shall be explained by the `aria-label`.

```html
<!-- Static ARIA attributes require no extra syntax -->
<button aria-label="Save document"></button>
```

You can also set these dynamically with JavaScript:

```javascript
const button = document.querySelector('#save-button');
button.setAttribute('aria-label', 'Save document');
```

#### Alt attribute

Use `alt` attributes some elements offer to add an additional description to an element. Depending on the screen reader it might not get picked up.

```html

```

#### Title attribute

Similar to the `alt` attribute, the `title` allows adding additional information about an element. Again, it might not be caught up by screen readers.

```html
<button title="Save document"></button>
```

#### Visually hidden text

When e.g. `aria-label` isn’t allowed or doesn't make sense to use, use hidden text to make specific description text that is read by a screen reader but isn’t visible in the UI.

```html
physical input
```

```css
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
```

#### How to test

If you are using chrome dev tools you have some options that make your life easier here. You can either use the [audit](https://developer.chrome.com/docs/devtools/accessibility/reference#audits) functionality to generate a report or use the [accesibility tree](https://developer.chrome.com/blog/full-accessibility-tree).

Alternatively, you can use browser extensions or playwright in conjunction with [@axe-core/playwright](https://www.npmjs.com/package/@axe-core/playwright) within the CI/CD Pipeline. Additionally, using a screen reader to test it is beneficial.

### Navigation and landmarks

Navigation provides a means for users to get around in applications. Most of the HTML sectioning elements provide default ARIA landmarks.

The `aria-label` attribute enables the logical navigation definition and separation of elements, which have the same type.
For example, if multiple `<nav>` elements are present on a page, use the `aria-label` attribute.

```html
<nav aria-label="Main"></nav>
<nav aria-label="Secondary"></nav>
```

The `navigation` role can also be used instead of the `<nav>` element:

```html

  
    Help
    Order tracking
    Shipping &amp; Delivery
    Returns
    Contact us
    Find a store
  

```

### Lists

Lists must be correctly structured to be recognized by screen readers. This means that parent (`` or ``) must be directly followed by `` and no other content element is allowed to be in-between.

Correct:

```html

  
    text
  
  

```

Incorrect:

```html

  This breaks the list structure
  List item

```

Or use one of the lists:

[Event list](../../components/event-list/)
### React Examples

#### event-list.tsx
```tsx
import { IxEventList, IxEventListItem } from '@siemens/ix-react';

export default function EventList() {
  return (
    <IxEventList>
      <IxEventListItem itemColor="color-primary">Text 1</IxEventListItem>
      <IxEventListItem itemColor="color-primary">Text 2</IxEventListItem>
      <IxEventListItem itemColor="color-alarm">Text 3</IxEventListItem>
      <IxEventListItem itemColor="color-success">Text 4</IxEventListItem>
    </IxEventList>
  );
}
```

### Angular Examples

#### event-list.ts
```ts
import { Component } from '@angular/core';

@Component({
  standalone: false,
  selector: 'app-example',
  template: `
    <ix-event-list>
      <ix-event-list-item item-color="color-primary">Text 1</ix-event-list-item>
      <ix-event-list-item item-color="color-primary">Text 2</ix-event-list-item>
      <ix-event-list-item item-color="color-alarm">Text 3</ix-event-list-item>
      <ix-event-list-item item-color="color-success">Text 4</ix-event-list-item>
    </ix-event-list>
  `,
})
export default class EventList {}
```

### Angular Standalone Examples

#### event-list.ts
```ts
import { Component } from '@angular/core';
import { IxEventList, IxEventListItem } from '@siemens/ix-angular/standalone';

@Component({
  selector: 'app-example',
  imports: [IxEventList, IxEventListItem],
  template: `
    <ix-event-list>
      <ix-event-list-item item-color="color-primary">Text 1</ix-event-list-item>
      <ix-event-list-item item-color="color-primary">Text 2</ix-event-list-item>
      <ix-event-list-item item-color="color-alarm">Text 3</ix-event-list-item>
      <ix-event-list-item item-color="color-success">Text 4</ix-event-list-item>
    </ix-event-list>
  `,
})
export default class EventList {}
```

### Vue Examples

#### event-list.vue
```vue
<script setup lang="ts">
import { IxEventList, IxEventListItem } from '@siemens/ix-vue';
</script>

<template>
  <IxEventList>
    <IxEventListItem itemColor="color-primary">Text 1</IxEventListItem>
    <IxEventListItem itemColor="color-primary">Text 2</IxEventListItem>
    <IxEventListItem itemColor="color-alarm">Text 3</IxEventListItem>
    <IxEventListItem itemColor="color-success">Text 4</IxEventListItem>
  </IxEventList>
</template>
```

[Card list](../../components/card-list/)
### React Examples

#### card-list.tsx
```tsx
import { iconBulb, iconRocket } from '@siemens/ix-icons/icons';
import { IxCardList, IxPushCard } from '@siemens/ix-react';

export default () => {
  return (
    <>
      <IxCardList label="Stack Layout" showAllCount={12} listStyle={'stack'}>
        <IxPushCard
          icon={iconBulb}
          notification="3"
          heading="Heading content"
          subheading="Subheading"
          variant="filled"
        ></IxPushCard>
        <IxPushCard
          icon={iconBulb}
          notification="1"
          heading="Heading content"
          subheading="Subheading"
          variant="warning"
        ></IxPushCard>
        <IxPushCard
          icon={iconRocket}
          notification="3"
          heading="Heading content"
          subheading="Subheading"
          variant="success"
        ></IxPushCard>
      </IxCardList>

      <IxCardList label="Flow Layout" showAllCount={12} listStyle={'scroll'}>
        <IxPushCard
          icon={iconRocket}
          notification="3"
          heading="Heading content"
          subheading="Subheading"
          variant="filled"
        ></IxPushCard>
        <IxPushCard
          icon={iconBulb}
          notification="1"
          heading="Heading content"
          subheading="Subheading"
          variant="warning"
        ></IxPushCard>
        <IxPushCard
          icon={iconRocket}
          notification="3"
          heading="Heading content"
          subheading="Subheading"
          variant="filled"
        ></IxPushCard>
        <IxPushCard
          icon={iconRocket}
          notification="3"
          heading="Heading content"
          subheading="Subheading"
          variant="filled"
        ></IxPushCard>
        <IxPushCard
          icon={iconRocket}
          notification="3"
          heading="Heading content"
          subheading="Subheading"
          variant="filled"
        ></IxPushCard>
      </IxCardList>
    </>
  );
};
```

### Angular Examples

#### card-list.ts
```ts
import { Component } from '@angular/core';

@Component({
  standalone: false,
  selector: 'app-example',
  templateUrl: './card-list.html',
})
export default class CardList {}
```

#### card-list.html
```html
<ix-card-list label="Stack Layout" show-all-count="12" list-style="stack">
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="filled"
  ></ix-push-card>
  <ix-push-card
    icon="bulb"
    notification="1"
    heading="Heading content"
    subheading="Subheading"
    variant="warning"
  ></ix-push-card>
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="success"
  ></ix-push-card>
</ix-card-list>

<ix-card-list label="Flow Layout" show-all-count="12" list-style="scroll">
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="filled"
  ></ix-push-card>
  <ix-push-card
    icon="bulb"
    notification="1"
    heading="Heading content"
    subheading="Subheading"
    variant="warning"
  ></ix-push-card>
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="filled"
  ></ix-push-card>
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="filled"
  ></ix-push-card>
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="filled"
  ></ix-push-card>
</ix-card-list>
```

### Angular Standalone Examples

#### card-list.ts
```ts
import { Component } from '@angular/core';
import { IxCardList, IxPushCard } from '@siemens/ix-angular/standalone';

@Component({
  selector: 'app-example',
  imports: [IxCardList, IxPushCard],
  templateUrl: './card-list.html',
})
export default class CardList {}
```

#### card-list.html
```html
<ix-card-list label="Stack Layout" show-all-count="12" list-style="stack">
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="filled"
  ></ix-push-card>
  <ix-push-card
    icon="bulb"
    notification="1"
    heading="Heading content"
    subheading="Subheading"
    variant="warning"
  ></ix-push-card>
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="success"
  ></ix-push-card>
</ix-card-list>

<ix-card-list label="Flow Layout" show-all-count="12" list-style="scroll">
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="filled"
  ></ix-push-card>
  <ix-push-card
    icon="bulb"
    notification="1"
    heading="Heading content"
    subheading="Subheading"
    variant="warning"
  ></ix-push-card>
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="filled"
  ></ix-push-card>
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="filled"
  ></ix-push-card>
  <ix-push-card
    icon="rocket"
    notification="3"
    heading="Heading content"
    subheading="Subheading"
    variant="filled"
  ></ix-push-card>
</ix-card-list>
```

### Vue Examples

#### card-list.vue
```vue
<script setup lang="ts">
import { iconBulb, iconRocket } from '@siemens/ix-icons/icons';
import { IxCardList, IxPushCard } from '@siemens/ix-vue';
</script>

<template>
  <IxCardList label="Stack Layout" :showAllCount="12" listStyle="stack">
    <IxPushCard
      :icon="iconRocket"
      notification="3"
      heading="Heading content"
      subheading="Subheading"
      variant="filled"
    ></IxPushCard>
    <IxPushCard
      :icon="iconBulb"
      notification="1"
      heading="Heading content"
      subheading="Subheading"
      variant="warning"
    ></IxPushCard>
    <IxPushCard
      :icon="iconRocket"
      notification="3"
      heading="Heading content"
      subheading="Subheading"
      variant="success"
    ></IxPushCard>
  </IxCardList>

  <IxCardList label="Flow Layout" :showAllCount="12" listStyle="scroll">
    <IxPushCard
      :icon="iconRocket"
      notification="3"
      heading="Heading content"
      subheading="Subheading"
      variant="filled"
    ></IxPushCard>
    <IxPushCard
      :icon="iconBulb"
      notification="1"
      heading="Heading content"
      subheading="Subheading"
      variant="filled"
    ></IxPushCard>
    <IxPushCard
      :icon="iconRocket"
      notification="3"
      heading="Heading content"
      subheading="Subheading"
      variant="filled"
    ></IxPushCard>
    <IxPushCard
      :icon="iconRocket"
      notification="3"
      heading="Heading content"
      subheading="Subheading"
      variant="filled"
    ></IxPushCard>
    <IxPushCard
      :icon="iconRocket"
      notification="3"
      heading="Heading content"
      subheading="Subheading"
      variant="filled"
    ></IxPushCard>
  </IxCardList>
</template>
```

### Roles

Roles help identify the purpose of elements in applications. Most of the native HTML elements already contain the role.
However, if there is a need for more complex structures, the roles must be set explicitly.

An example is a custom built menu. Setting the roles is important for the screen reader to distinguish the elements and announce them properly.

```html

  Heading
  
    
  
  
  
    Link
  

```

You can also set roles dynamically with JavaScript:

```javascript
const menuItem = document.createElement('a');
menuItem.setAttribute('role', 'menuitem');
menuItem.href = '#';
menuItem.textContent = 'Menu Item';
```

See [WAI-ARIA Roles](https://www.w3.org/TR/wai-aria-1.1/#role_definitions) for more information.

### Language of a page and parts

Set the language of the human readable text for a web page or each paragraph or phrase via the `lang` attribute:

```html
<html lang="en">
  This is written in English
  Das ist in Deutsch geschrieben
</html>
```

You can also set language dynamically with JavaScript:

```javascript
document.documentElement.lang = 'en';
// Or for specific elements
document.querySelector('#german-content').lang = 'de';
```

### Using semantic HTML elements

Native HTML elements capture several standard interaction patterns that are important to accessibility.
When building web applications, you should re-use these native elements directly when possible, rather
than re-implementing well-supported behaviors.

For example, instead of creating a custom element for a button, use the native `<button>` element:

```html
<!-- Good: Uses semantic button -->
<button onclick="saveDocument()">
  
  Save
</button>

<!-- Avoid: Non-semantic div that requires extra ARIA -->

  
  Save

```

### Live regions for dynamic content

Use ARIA live regions to announce dynamic content changes to screen readers:

```html

```

```javascript
function announceStatus(message) {
  const statusDiv = document.getElementById('status');
  statusDiv.textContent = message;
}

// Usage
announceStatus('Document saved successfully');
```

For urgent announcements, use `aria-live="assertive"`:

```html

```

## Mobile accessibility

Mobile accessibility is crucial as mobile devices are increasingly the primary way users access digital content. Modern mobile platforms like iOS and Android have built-in accessibility tools, making it essential to ensure your web applications work seamlessly with these technologies.

### Mobile-specific considerations

When developing accessible mobile applications, focus on these key areas:

#### Control mechanisms

Ensure interface controls work across different interaction methods:

```javascript
// Support both touch and mouse events
function handleInteraction(element) {
  // Mouse events
  element.addEventListener('mousedown', startAction);
  element.addEventListener('mouseup', endAction);

  // Touch events for mobile
  element.addEventListener('touchstart', (e) => {
    // Prevent mouse events from firing
    e.preventDefault();
    startAction(e);
  });

  element.addEventListener('touchend', (e) => {
    e.preventDefault();
    endAction(e);
  });

  // Keyboard support
  element.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      startAction(e);
    }
  });
}
```

Avoid mouse-specific events that don’t work on touch devices:

```javascript
// ❌ Avoid: Mouse-only events
div.onmousedown = startDrag;
document.onmouseup = stopDrag;

// ✅ Better: Multi-modal support
function addDragSupport(element) {
  // Mouse support
  element.addEventListener('mousedown', initiateDrag);

  // Touch support
  element.addEventListener('touchstart', initiateDrag);

  // Keyboard support
  element.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      initiateDrag(e);
    }
  });
}
```

#### User input optimization

Minimize typing requirements on mobile devices:

```html
<!-- Use semantic input types for better mobile experience -->
<input type="tel" placeholder="Phone number" />
<input type="email" placeholder="Email address" />
<input type="number" placeholder="Age" />
<input type="date" placeholder="Birth date" />

<!-- Provide options instead of free text when possible -->
<select aria-label="Job title">
  <option>Software Engineer</option>
  <option>Product Manager</option>
  <option>Designer</option>
  <option value="other">Other</option>
</select>

<!-- Show additional field only when "Other" is selected -->
<input
  type="text"
  id="other-job"
  placeholder="Please specify"
  style="display: none;"
/>
```

```javascript
// Progressive enhancement for job selection
const jobSelect = document.querySelector('select[aria-label="Job title"]');
const otherInput = document.getElementById('other-job');

jobSelect.addEventListener('change', function () {
  if (this.value === 'other') {
    otherInput.style.display = 'block';
    otherInput.focus();
    otherInput.setAttribute('required', 'true');
  } else {
    otherInput.style.display = 'none';
    otherInput.removeAttribute('required');
  }
});
```

#### Responsive design for accessibility

Ensure your layouts work across different screen sizes and orientations:

```css
/* Ensure text remains readable when zoomed */
@media screen and (max-width: 768px) {
  body {
    font-size: 16px; /* Minimum readable size */
    line-height: 1.5;
  }

  /* Ensure touch targets are large enough */
  button,
  input,
  select,
  textarea {
    min-height: 44px;
    min-width: 44px;
  }

  /* Provide adequate spacing between interactive elements */
  .interactive-element {
    margin: 8px 0;
  }
}

/* Support high-resolution displays */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
  .icon {
    /* Use SVG or high-resolution images */
    background-image: url('icon@2x.png');
    background-size: contain;
  }
}
```

#### Viewport configuration

Always enable zooming for accessibility:

```html
<!-- ✅ Correct: Allow user scaling -->
<meta
  name="viewport"
  content="width=device-width, initial-scale=1.0, user-scalable=yes"
/>

<!-- ❌ Never disable zoom -->
<meta
  name="viewport"
  content="width=device-width, initial-scale=1.0, user-scalable=no"
/>
```

#### Mobile Navigation Patterns

Implement accessible hamburger menus and mobile navigation:

```html
<nav role="navigation" aria-label="Main navigation">
  <button
    class="menu-toggle"
    aria-controls="mobile-menu"
    aria-expanded="false"
    aria-label="Toggle navigation menu"
  >
    
    
    
  </button>

  
    Home
    About
    Contact
  
</nav>
```

```javascript
function initializeMobileMenu() {
  const menuToggle = document.querySelector('.menu-toggle');
  const mobileMenu = document.getElementById('mobile-menu');

  menuToggle.addEventListener('click', toggleMenu);
  menuToggle.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      toggleMenu();
    }
  });

  function toggleMenu() {
    const isExpanded = menuToggle.getAttribute('aria-expanded') === 'true';

    menuToggle.setAttribute('aria-expanded', !isExpanded);
    mobileMenu.setAttribute('aria-hidden', isExpanded);
    mobileMenu.classList.toggle('open');

    // Focus management
    if (!isExpanded) {
      // Focus first menu item when opening
      const firstMenuItem = mobileMenu.querySelector('a');
      if (firstMenuItem) {
        firstMenuItem.focus();
      }
    }
  }

  // Close menu when clicking outside
  document.addEventListener('click', (e) => {
    if (!e.target.closest('nav')) {
      const isExpanded = menuToggle.getAttribute('aria-expanded') === 'true';
      if (isExpanded) {
        toggleMenu();
      }
    }
  });

  // Handle escape key
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
      const isExpanded = menuToggle.getAttribute('aria-expanded') === 'true';
      if (isExpanded) {
        toggleMenu();
        menuToggle.focus();
      }
    }
  });
}
```

```css
.menu-toggle {
  background: none;
  border: none;
  padding: 12px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  width: 44px;
  height: 44px;
}

.hamburger-line {
  width: 20px;
  height: 2px;
  background-color: currentColor;
  transition: all 0.3s ease;
}

.mobile-menu {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: white;
  border: 1px solid #ccc;
  transform: translateY(-100%);
  opacity: 0;
  visibility: hidden;
  transition: all 0.3s ease;
}

.mobile-menu.open {
  transform: translateY(0);
  opacity: 1;
  visibility: visible;
}

.mobile-menu a {
  display: block;
  padding: 16px;
  text-decoration: none;
  border-bottom: 1px solid #eee;
  min-height: 44px;
  display: flex;
  align-items: center;
}

.mobile-menu a:focus {
  outline: 2px solid #0066cc;
  outline-offset: -2px;
}
```

### Screen reader testing

#### Android TalkBack

To test with TalkBack:

1. Enable TalkBack in Settings > Accessibility
2. Use these gestures:
   - **Single tap**: Select and announce item
   - **Double tap**: Activate selected item
   - **Swipe left/right**: Navigate between items
   - **Two-finger swipe up**: Access global menu
   - **Swipe up then right**: Access local menu

#### iOS VoiceOver

To test with VoiceOver:

1. Enable in Settings > Accessibility > VoiceOver
2. Use these gestures:
   - **Single tap**: Select item
   - **Double tap**: Activate item
   - **Swipe left/right**: Navigate between items
   - **Three-finger swipe**: Scroll
   - **Rotor gesture**: Access navigation options

### Testing guidelines

#### Manual testing checklist

☑ All interactive elements have minimum 44px touch targets 
☑ Content is readable when zoomed to 200% 
☑ Navigation works with touch, keyboard, and screen readers 
☑ Form inputs use appropriate mobile keyboards 
☑ No horizontal scrolling on mobile devices 
☑ Focus indicators are visible and clear 
☑ Screen reader announces all important content changes 

#### Automated testing

```javascript
// Example Playwright test with axe-core
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('mobile accessibility compliance', async ({ page }) => {
  await page.goto('/your-page');

  // Set mobile viewport
  await page.setViewportSize({ width: 375, height: 667 });

  const accessibilityScanResults = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
    .analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});

test('touch target sizes', async ({ page }) => {
  await page.goto('/your-page');
  await page.setViewportSize({ width: 375, height: 667 });

  const interactiveElements = page.locator(
    'button, a, input, select, textarea',
  );
  const count = await interactiveElements.count();

  for (let i = 0; i < count; i++) {
    const element = interactiveElements.nth(i);
    const box = await element.boundingBox();

    expect(box.width).toBeGreaterThanOrEqual(44);
    expect(box.height).toBeGreaterThanOrEqual(44);
  }
});
```

### Mobile-specific ARIA considerations

When developing for mobile pay special attention to:

```html
<!-- Announce loading states -->

<!-- Provide clear button labels -->
<button aria-label="Add item to shopping cart">
  
</button>

<!-- Group related form fields -->
<fieldset>
  <legend>Shipping Address</legend>
  <input type="text" aria-label="Street address" />
  <input type="text" aria-label="City" />
  <input type="text" aria-label="Postal code" />
</fieldset>
```

```javascript
// Announce dynamic content changes
function updateLoadingStatus(message) {
  const statusElement = document.getElementById('loading-status');
  statusElement.textContent = message;
}

// Usage
updateLoadingStatus('Loading search results...');
setTimeout(() => {
  updateLoadingStatus('Search completed. Found 25 results.');
}, 2000);
```

### Key takeaways

1. **Test on actual devices** with screen readers enabled
2. **Ensure adequate touch target sizes** (minimum 44px)
3. **Support multiple input methods** (touch, keyboard, voice)
4. **Allow zoom and scaling** for users with visual impairments
5. **Optimize form inputs** for mobile keyboards
6. **Implement proper focus management** in mobile navigation
7. **Test with both portrait and landscape orientations**
8. **Provide clear feedback** for all user actions
