Overview
The accessibility implementation in MarkdownView:- Converts all visual content to spoken descriptions
- Replaces attachment characters with meaningful text
- Exposes content via
accessibilityValueandaccessibilityLabel - Configures proper accessibility traits
- Supports both UIKit (iOS/visionOS) and AppKit (macOS)
Accessibility Architecture
LTXLabel Configuration
The underlyingLTXLabel component is configured as an accessibility element during initialization:
The view is marked as
.staticText since markdown content is read-only. Interactive elements like links are handled separately.MarkdownTextView Configuration
The mainMarkdownTextView is configured to let its child LTXLabel handle accessibility:
LTXLabel directly.
Building Accessible Strings
The core of accessibility support is thebuildAccessibleString() method, which converts the visual attributed string into a plain string suitable for VoiceOver.
Attachment Replacement
Markdown content uses the Unicode replacement character (\u{FFFC}) as a placeholder for attachments (images, math). The accessibility system replaces these with meaningful text:
The method walks backward through the string to avoid invalidating indices as replacements are made.
Accessibility for Different Content Types
Text Content
Plain text, bold, italic, strikethrough, and other inline styles are naturally accessible. VoiceOver reads the text content directly:Images
Images use their alt text for accessibility. When an image is rendered, itsLTXAttachment stores the alt text:
Math Expressions
LaTeX math expressions store their LaTeX source code for VoiceOver:Code Blocks
Code blocks are rendered as custom views (CodeView), which are separate subviews. These should implement their own accessibility support by:
- Setting
isAccessibilityElement = true - Providing the code content as
accessibilityValue - Optionally announcing the language:
accessibilityLabel = "Swift code"
Tables
Tables are rendered as custom views (TableView), which are also separate subviews. For proper accessibility, tables should:
- Set
isAccessibilityElement = falseon the container - Make each cell an accessibility element
- Use
accessibilityLabelto describe row/column positions - Consider reading order (left-to-right, top-to-bottom)
Links
Links are accessible as tappable elements within the text. The link destination is announced as a hint:Accessibility Value vs Label
BothaccessibilityValue and accessibilityLabel return the same built accessible string:
Providing both ensures compatibility with different VoiceOver reading modes and iOS versions.
Dynamic Content Updates
When markdown content changes, the accessibility system is automatically updated because:- The
attributedTextproperty changes buildAccessibleString()is called lazily when VoiceOver queries the view- VoiceOver receives the updated accessible string
Selection and Editing
Since MarkdownView displays read-only content, it doesn’t implement:- Text editing accessibility APIs
- Cursor position announcements
- Text field traits
UITextView with a custom NSLayoutManager or implement the full UIAccessibilityReadingContent protocol.
Multi-Platform Support
Accessibility implementations differ between UIKit and AppKit:iOS/visionOS (UIKit)
macOS (AppKit)
AppKit uses methods instead of properties for accessibility, but the logic is identical.
Testing Accessibility
Xcode Accessibility Inspector
- Open Xcode → Developer Tools → Accessibility Inspector
- Select your app process
- Inspect the MarkdownView
- Verify
accessibilityValuecontains readable text - Check that attachments are replaced with text
VoiceOver Testing
iOS/visionOS:- Enable VoiceOver: Settings → Accessibility → VoiceOver
- Swipe through your markdown content
- Verify all text, images, and math are announced correctly
- Enable VoiceOver: System Settings → Accessibility → VoiceOver
- Use Cmd+F5 to toggle VoiceOver
- Navigate with Ctrl+Option+Arrow keys
Best Practices
Provide Alt Text
Always include descriptive alt text for images:Simplify Math Expressions
For complex equations, provide a text description:Structure Content Logically
Use headings, lists, and paragraphs to provide structure:Test with Real Users
Automated testing catches technical issues, but usability testing with VoiceOver users reveals:- Unclear alt text
- Confusing reading order
- Missing context
- Overly verbose descriptions
Limitations
- Code blocks: Accessibility depends on
CodeViewimplementation (outside LTXLabel) - Tables: Accessibility depends on
TableViewimplementation - Interactive elements: Only basic link detection is provided
- Math expressions: LaTeX is read character-by-character, which may be unclear
Accessibility Traits
Currently, the view uses.staticText trait. Consider adding:
.headerfor heading content.linkfor link-heavy content.allowsDirectInteractionfor interactive elements