Accessibility
Vy UI components are accessible by default. Roles, state (checked, expanded, selected…), and focusability are wired into every component for Lynx's native screen readers, VoiceOver on iOS and TalkBack on Android. The one thing a component can't infer is a name for a control with no visible text, so that's the main thing you add.
What you get for free
Drop a component in and the native accessibility data is already on the element. State stays in sync automatically, so a checkbox announces "checked" / "unchecked" as it toggles, an accordion "expanded" / "collapsed", a slider its value. This is true whether you use the headless @vyui/core primitives or the styled @vyui/kit components:
<script setup>
import { CheckboxRoot, CheckboxIndicator } from '@vyui/core'
import { ref } from 'vue'
const checked = ref(false)
</script>
<template>
<CheckboxRoot v-model="checked">
<CheckboxIndicator>✓</CheckboxIndicator>
</CheckboxRoot>
</template>
<script setup>
import { VyCheckbox } from '@vyui/kit'
import { ref } from 'vue'
const checked = ref(false)
</script>
<template>
<VyCheckbox v-model="checked" />
</template>
Add a label to controls without visible text
A component can't guess a name for a control that has no text, such as an icon-only button, an avatar, or a close "×". Give those an accessibility-label (Lynx's equivalent of aria-label) so screen readers announce something meaningful:
<template>
<!-- icon-only controls need a label -->
<VyButton accessibility-label="Add to favorites">
<Icon name="heart" />
</VyButton>
<!-- a button with text is announced from that text, no label needed -->
<VyButton>Save changes</VyButton>
</template>
accessibility-label on icon-only controls, which have no text to announce.Why Lynx is different from the web
On the web, <button> and <input type="checkbox"> get their accessibility from the browser, and you only reach for ARIA on custom widgets. Lynx renders to native <view> and <text>, generic boxes with no built-in roles, so accessibility has to be attached explicitly. Vy UI does that for you. Note that Lynx ignores web aria-* attributes; native accessibility uses accessibility-* props instead, which the components handle.
Building your own component
If you compose your own primitive on top of @vyui/core, use the useA11y composable to attach native accessibility the same way the built-ins do. Pass it a descriptor and spread the result onto your root element:
<script setup>
import { useA11y } from '@vyui/core'
import { ref } from 'vue'
const checked = ref(false)
const a11y = useA11y(() => ({
role: 'checkbox',
state: checked.value ? 'checked' : 'unchecked',
}))
</script>
<template>
<view v-bind="a11y" @tap="checked = !checked">
<text>Wifi</text>
</view>
</template>
It's reactive: when checked changes, the announced state updates automatically.
Descriptor reference
| Field | What it does | Example |
|---|---|---|
role | The kind of control | 'button', 'checkbox', 'switch', 'tab', 'slider', 'heading', 'dialog' |
label | Spoken name (for controls without text) | 'Add to favorites' |
state | Spoken state | 'checked', 'expanded', 'on', 'pressed' |
selected | For tabs/options, announces "selected" when true, nothing when false | isActive |
value | A range value | { now: 30, max: 100 } → "30 of 100" |
disabled | Marks the control unavailable | true |
exclusiveFocus | Traps screen-reader focus inside (modals) | true |
disabled control announces "disabled" rather than its usual role. For tabs and options, pass selected rather than a state string, since selected items announce "selected" and unselected ones stay quiet, which is how a screen reader expects a list to behave.On the roadmap
Spoken announcements for transient UI like toasts, moving focus into a dialog when it opens, and an ARIA layer for the Lynx web target are in progress. See the roadmap.