Vue NativeVue Native
Guide
Components
Composables
Navigation
  • iOS
  • Android
  • macOS
GitHub
Guide
Components
Composables
Navigation
  • iOS
  • Android
  • macOS
GitHub
  • Getting Started

    • Introduction
    • Installation
    • Your First App
    • Project Structure
  • Core Concepts

    • Components
    • Styling
    • Navigation
    • Native Modules
    • Native Code Blocks
    • Hot Reload
  • Advanced

    • Error Handling
    • Accessibility
    • TypeScript
    • Performance
    • Shared Element Transitions
    • Testing
    • Security
    • Debugging
    • Teleport
    • Forms and v-model
  • Integration Guides

    • State Management
    • Deep Linking & Universal Links
    • State Persistence
    • Push Notifications
    • Error Reporting & Monitoring
  • Tooling

    • Managed Workflow
    • VS Code Extension
    • Neovim Plugin
  • Building & Releasing

    • Building for Release
    • Deployment & App Store Submission
  • Reference

    • Migration & Upgrade Guide
    • Known Limitations & Platform Differences
    • Troubleshooting

Accessibility

Vue Native maps accessibility props directly to the native accessibility APIs on each platform — UIAccessibility on iOS and AccessibilityNodeInfo on Android. This means screen readers like VoiceOver and TalkBack work with your app out of the box, as long as you provide the right metadata.

Accessibility Props

Most Vue Native components accept the following accessibility props:

PropTypeDescription
accessibilityLabelstringThe primary text read by VoiceOver/TalkBack. Describes what the element is.
accessibilityHintstringAdditional context about what will happen when the user interacts with the element.
accessibilityRolestringSemantic role that tells the screen reader what kind of element this is.
accessibilityStateobjectDynamic state flags: { disabled, selected, checked, expanded }.

accessibilityLabel

The most important prop. It replaces the visible text as the screen reader announcement. Use it whenever the visual content alone does not convey the element's purpose — for example, icon-only buttons.

<VButton
  :onPress="toggleFavorite"
  accessibilityLabel="Add to favorites"
>
  <VText>❤️</VText>
</VButton>

Without the label, a screen reader would either skip this button or announce something meaningless. With the label, VoiceOver announces "Add to favorites, button".

accessibilityHint

Provides a secondary description of what happens when the element is activated. Screen readers typically announce it after a short pause.

<VButton
  :onPress="deleteAccount"
  accessibilityLabel="Delete account"
  accessibilityHint="Permanently removes your account and all data"
>
  <VText :style="{ color: 'red' }">Delete Account</VText>
</VButton>

VoiceOver announces: "Delete account, button. Permanently removes your account and all data."

accessibilityRole

Tells the screen reader the semantic type of the element. This affects how the element is announced and how users can interact with it.

RoleDescriptioniOS TraitAndroid Role
"button"A tappable control.buttonAccessibilityNodeInfo.ACTION_CLICK
"header"A section heading.headerheading = true
"image"An image.imageclassName = ImageView
"link"A link that navigates somewhere.linkclassName = Link
"text"Static text.staticTextclassName = TextView
"search"A search field.searchFieldclassName = EditText
"adjustable"A slider or adjustable control.adjustablerangeInfo
"switch"A toggle switch.toggleButtonclassName = Switch
"none"Element is not accessibleisAccessibilityElement = falseimportantForAccessibility = no
<VText
  accessibilityRole="header"
  :style="{ fontSize: 28, fontWeight: 'bold' }"
>
  Settings
</VText>

accessibilityState

Communicates dynamic state to the screen reader. Pass an object with any combination of the following keys:

KeyTypeDescription
disabledbooleanThe element is not interactive.
selectedbooleanThe element is currently selected.
checkedboolean | 'mixed'Checkbox or toggle state.
expandedbooleanWhether a collapsible section is open.
<script setup>
import { ref } from 'vue'
import { VButton, VText, VView } from '@thelacanians/vue-native-runtime'

const isExpanded = ref(false)
</script>

<template>
  <VButton
    :onPress="() => (isExpanded = !isExpanded)"
    accessibilityLabel="Notifications"
    accessibilityHint="Expand to see notification preferences"
    :accessibilityState="{ expanded: isExpanded }"
  >
    <VText>Notifications {{ isExpanded ? '▲' : '▼' }}</VText>
  </VButton>

  <VView v-if="isExpanded">
    <!-- notification settings -->
  </VView>
</template>

VoiceOver announces: "Notifications, collapsed, button. Double-tap to expand."

Platform Mapping

Vue Native translates the cross-platform accessibility props into native API calls on each platform.

iOS (UIAccessibility)

Vue Native PropUIKit Property / Method
accessibilityLabelview.accessibilityLabel
accessibilityHintview.accessibilityHint
accessibilityRoleview.accessibilityTraits (mapped to trait flags)
accessibilityState.disabledAdds .notEnabled trait
accessibilityState.selectedview.isAccessibilityElement + .selected trait
Any a11y prop presentview.isAccessibilityElement = true

Android (Accessibility APIs)

Vue Native PropAndroid Property / Method
accessibilityLabelview.contentDescription
accessibilityHintAppended to contentDescription or set via AccessibilityDelegate
accessibilityRoleAccessibilityNodeInfo.className or role-specific properties
accessibilityState.disabledview.isEnabled = false + importantForAccessibility
accessibilityState.selectedview.isSelected = true
Any a11y prop presentview.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_YES

Common Patterns

Icon-Only Buttons

Buttons that display only an icon or emoji must have an accessibilityLabel:

<VButton
  :onPress="goBack"
  accessibilityLabel="Go back"
  accessibilityRole="button"
  :style="{ padding: 10 }"
>
  <VText :style="{ fontSize: 20 }">←</VText>
</VButton>

<VButton
  :onPress="openSettings"
  accessibilityLabel="Settings"
  accessibilityRole="button"
  :style="{ padding: 10 }"
>
  <VImage
    source="gear-icon"
    :style="{ width: 24, height: 24 }"
    accessibilityRole="none"
  />
</VButton>

Note that the VImage inside the button uses accessibilityRole="none" to avoid being announced separately — the button's label is sufficient.

Form Inputs with Labels

Pair every input with a descriptive label:

<script setup>
import { ref } from 'vue'
import { VView, VText, VInput } from '@thelacanians/vue-native-runtime'

const email = ref('')
const password = ref('')
</script>

<template>
  <VView :style="{ padding: 20, gap: 16 }">
    <VView>
      <VText
        accessibilityRole="text"
        :style="{ fontSize: 14, color: '#666', marginBottom: 4 }"
      >
        Email address
      </VText>
      <VInput
        :value="email"
        :onChangeText="(t) => (email = t)"
        placeholder="you@example.com"
        accessibilityLabel="Email address"
        accessibilityHint="Enter your email to sign in"
        :style="{
          borderWidth: 1,
          borderColor: '#ccc',
          borderRadius: 8,
          padding: 12,
          fontSize: 16,
        }"
      />
    </VView>

    <VView>
      <VText
        accessibilityRole="text"
        :style="{ fontSize: 14, color: '#666', marginBottom: 4 }"
      >
        Password
      </VText>
      <VInput
        :value="password"
        :onChangeText="(t) => (password = t)"
        secureTextEntry
        placeholder="••••••••"
        accessibilityLabel="Password"
        accessibilityHint="Enter your password to sign in"
        :style="{
          borderWidth: 1,
          borderColor: '#ccc',
          borderRadius: 8,
          padding: 12,
          fontSize: 16,
        }"
      />
    </VView>
  </VView>
</template>

Images with Descriptions

Informational images should have a label. Decorative images should be hidden from the accessibility tree:

<!-- Informational image — describe what it shows -->
<VImage
  source="https://example.com/chart.png"
  accessibilityLabel="Sales chart showing 40% growth in Q4"
  accessibilityRole="image"
  :style="{ width: 300, height: 200 }"
/>

<!-- Decorative image — hide from screen reader -->
<VImage
  source="background-pattern"
  accessibilityRole="none"
  :style="{ width: '100%', height: 150 }"
/>

Toggle Controls with State

<script setup>
import { ref } from 'vue'
import { VView, VText, VSwitch } from '@thelacanians/vue-native-runtime'

const darkMode = ref(false)
const notifications = ref(true)
</script>

<template>
  <VView :style="{ padding: 20, gap: 16 }">
    <VView :style="{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }">
      <VText>Dark Mode</VText>
      <VSwitch
        :value="darkMode"
        :onValueChange="(v) => (darkMode = v)"
        accessibilityLabel="Dark Mode"
        accessibilityRole="switch"
        :accessibilityState="{ checked: darkMode }"
      />
    </VView>

    <VView :style="{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }">
      <VText>Notifications</VText>
      <VSwitch
        :value="notifications"
        :onValueChange="(v) => (notifications = v)"
        accessibilityLabel="Notifications"
        accessibilityRole="switch"
        :accessibilityState="{ checked: notifications }"
      />
    </VView>
  </VView>
</template>

Disabled Buttons

<script setup>
import { ref, computed } from 'vue'
import { VButton, VText } from '@thelacanians/vue-native-runtime'

const formValid = ref(false)
</script>

<template>
  <VButton
    :onPress="submit"
    :disabled="!formValid"
    accessibilityLabel="Submit form"
    accessibilityHint="Fill in all required fields to enable"
    :accessibilityState="{ disabled: !formValid }"
    :style="{
      padding: 14,
      backgroundColor: formValid ? '#007AFF' : '#ccc',
      borderRadius: 8,
    }"
  >
    <VText :style="{ color: '#fff', textAlign: 'center' }">Submit</VText>
  </VButton>
</template>

Section Headers

Mark headings so screen reader users can navigate between sections:

<VView :style="{ flex: 1 }">
  <VText
    accessibilityRole="header"
    :style="{ fontSize: 24, fontWeight: 'bold', padding: 16 }"
  >
    Account
  </VText>
  <!-- account settings... -->

  <VText
    accessibilityRole="header"
    :style="{ fontSize: 24, fontWeight: 'bold', padding: 16 }"
  >
    Privacy
  </VText>
  <!-- privacy settings... -->
</VView>

On iOS, VoiceOver users can swipe up/down with the rotor set to "Headings" to jump between these sections.

RTL and Internationalization

Vue Native's useI18n composable provides an isRTL ref that you can use to flip layouts for right-to-left languages. Accessibility labels should also be localized:

<script setup>
import { useI18n } from '@thelacanians/vue-native-runtime'
import { VButton, VText } from '@thelacanians/vue-native-runtime'

const { t, isRTL } = useI18n()
</script>

<template>
  <VButton
    :onPress="goBack"
    :accessibilityLabel="t('common.goBack')"
    :style="{ flexDirection: isRTL ? 'row-reverse' : 'row' }"
  >
    <VText>{{ isRTL ? '→' : '←' }}</VText>
  </VButton>
</template>

Testing Accessibility

iOS — VoiceOver

  1. Open Settings > Accessibility > VoiceOver on your device or simulator.
  2. Enable VoiceOver.
  3. Swipe right to move through elements. Listen for labels, roles, hints, and state.
  4. Use the Accessibility Inspector in Xcode (Xcode > Open Developer Tool > Accessibility Inspector) to audit your views without enabling VoiceOver.

Android — TalkBack

  1. Open Settings > Accessibility > TalkBack on your device or emulator.
  2. Enable TalkBack.
  3. Swipe right to move through elements. Listen for content descriptions and roles.
  4. Use Layout Inspector in Android Studio to verify contentDescription values.

Automated Checks

During development, you can log accessibility metadata from your components:

app.config.errorHandler = (err, instance, info) => {
  // ...existing error handling
}

// In development, warn about missing accessibility labels on interactive elements
if (__DEV__) {
  console.warn('Remember to test with VoiceOver/TalkBack before releasing.')
}

Best Practices

  1. Always label interactive elements. Every button, input, switch, and link should have an accessibilityLabel. If the visible text is already descriptive, the screen reader will use it automatically — but verify with VoiceOver or TalkBack.

  2. Use roles for semantic meaning. A VView acting as a button should have accessibilityRole="button" so the screen reader announces it correctly and tells the user they can double-tap to activate it.

  3. Keep labels concise. "Delete" is better than "Tap this button to delete the current item from your list". Use accessibilityHint for the additional context.

  4. Update state dynamically. When a checkbox toggles or a section expands, update accessibilityState so the screen reader reflects the current state.

  5. Hide decorative elements. Background images, divider lines, and other non-informational views should use accessibilityRole="none" to avoid cluttering the screen reader experience.

  6. Test on real devices. Simulators and emulators support screen readers, but the experience can differ from physical devices. Test on both platforms before shipping.

  7. Localize labels. If your app supports multiple languages, accessibility labels must be translated along with the rest of your UI text.

  8. Group related elements. If a card contains a title, subtitle, and action button, consider grouping them so the screen reader announces them as a single unit rather than three separate elements.

Edit this page
Last Updated: 2/28/26, 11:24 PM
Contributors: Abdul Hamid, Claude Opus 4.6
Prev
Error Handling
Next
TypeScript