---
name: gluestack-accessibility
description: Use when ensuring accessible gluestack-ui implementations. Covers WAI-ARIA patterns, screen reader support, keyboard navigation, focus management, and WCAG 2.1 AA compliance.
allowed-tools:
- Read
- Write
- Edit
- Bash
- Grep
- Glob
---
# gluestack-ui - Accessibility
Expert knowledge of building accessible user interfaces with gluestack-ui, ensuring WCAG 2.1 AA compliance across React and React Native platforms.
## Overview
gluestack-ui components are built with accessibility in mind, following WAI-ARIA guidelines and providing built-in support for screen readers, keyboard navigation, and focus management. This skill covers best practices for maintaining and enhancing accessibility.
## Key Concepts
### Built-in Accessibility
gluestack-ui components include accessibility features out of the box:
```tsx
// Button automatically has role="button" and handles focus
// Modal manages focus trap and escape key handling
Content
// Form controls link labels to inputs
Email
```
### Accessibility Props
React Native accessibility props supported by gluestack-ui:
```tsx
```
### ARIA Attributes for Web
For web platforms, use ARIA attributes:
```tsx
import { Platform } from 'react-native';
```
## Screen Reader Support
### Meaningful Labels
Provide descriptive labels for interactive elements:
```tsx
// Bad: No context for screen reader users
// Good: Clear accessibility label
```
### Announcing Dynamic Changes
Use accessibility live regions for dynamic content:
```tsx
import { AccessibilityInfo } from 'react-native';
function SearchResults({ results, isLoading }: {
results: Item[];
isLoading: boolean;
}) {
useEffect(() => {
if (!isLoading) {
AccessibilityInfo.announceForAccessibility(
`${results.length} results found`
);
}
}, [results, isLoading]);
return (
{results.map((item) => (
{item.name}
))}
);
}
```
### Image Accessibility
Always provide alt text for images:
```tsx
import { Image } from '@/components/ui/image';
// Informative image
// Decorative image (hide from screen readers)
```
## Keyboard Navigation
### Focus Management
Ensure proper focus order and visibility:
```tsx
import { useRef, useEffect } from 'react';
import { TextInput } from 'react-native';
function SearchModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
const searchInputRef = useRef(null);
useEffect(() => {
if (isOpen) {
// Focus the search input when modal opens
searchInputRef.current?.focus();
}
}, [isOpen]);
return (
Search
);
}
```
### Focus Trap in Modals
gluestack-ui Modal automatically traps focus, but you can enhance it:
```tsx
function AccessibleModal({ isOpen, onClose, children }: {
isOpen: boolean;
onClose: () => void;
children: React.ReactNode;
}) {
return (
{children}
);
}
```
### Keyboard Shortcuts
Implement keyboard shortcuts for web:
```tsx
import { useEffect } from 'react';
import { Platform } from 'react-native';
function useKeyboardShortcut(key: string, callback: () => void) {
useEffect(() => {
if (Platform.OS !== 'web') return;
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === key && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
callback();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [key, callback]);
}
// Usage
function SearchBar() {
const inputRef = useRef(null);
useKeyboardShortcut('k', () => {
inputRef.current?.focus();
});
return (
);
}
```
## Form Accessibility
### Label Association
Properly associate labels with form controls:
```tsx
import {
FormControl,
FormControlLabel,
FormControlLabelText,
FormControlHelper,
FormControlHelperText,
FormControlError,
FormControlErrorIcon,
FormControlErrorText,
} from '@/components/ui/form-control';
import { Input, InputField } from '@/components/ui/input';
import { AlertCircleIcon } from 'lucide-react-native';
function AccessibleFormField({
label,
placeholder,
helperText,
error,
isRequired,
value,
onChange,
}: {
label: string;
placeholder: string;
helperText?: string;
error?: string;
isRequired?: boolean;
value: string;
onChange: (text: string) => void;
}) {
return (
{label}
{error ? (
{error}
) : helperText ? (
{helperText}
) : null}
);
}
```
### Error Announcement
Announce form errors to screen readers:
```tsx
import { AccessibilityInfo } from 'react-native';
function FormWithValidation() {
const [errors, setErrors] = useState>({});
const validateAndSubmit = () => {
const newErrors: Record = {};
if (!formData.email) {
newErrors.email = 'Email is required';
}
if (!formData.password) {
newErrors.password = 'Password is required';
}
setErrors(newErrors);
const errorCount = Object.keys(newErrors).length;
if (errorCount > 0) {
// Announce errors to screen readers
AccessibilityInfo.announceForAccessibility(
`Form has ${errorCount} error${errorCount > 1 ? 's' : ''}. ${Object.values(newErrors).join('. ')}`
);
return;
}
submitForm();
};
return (
);
}
```
### Required Field Indication
Clearly indicate required fields:
```tsx
function RequiredLabel({ label }: { label: string }) {
return (
{label}
{' *'}
);
}
```
## Best Practices
### 1. Use Semantic Components
Choose appropriate components for their semantic meaning:
```tsx
// Good: Semantic components
Page Title
// Bad: Generic elements for interactive content
Submit
```
### 2. Provide Sufficient Color Contrast
Ensure text meets WCAG contrast requirements (4.5:1 for normal text, 3:1 for large text):
```tsx
// Good: High contrast
Readable text
// Bad: Low contrast
Hard to read text
```
### 3. Support Reduced Motion
Respect user preferences for reduced motion:
```tsx
import { useReducedMotion } from 'react-native-reanimated';
function AnimatedCard({ children }: { children: React.ReactNode }) {
const reducedMotion = useReducedMotion();
return (
{children}
);
}
```
### 4. Handle Touch Target Sizes
Ensure touch targets are at least 44x44 points:
```tsx
// Good: Adequate touch target
// Or use Pressable with hitSlop
```
### 5. Group Related Elements
Use accessibility groups for related content:
```tsx
{product.name}
{product.description}
{formatPrice(product.price)}
```
## Examples
### Accessible Navigation Menu
```tsx
import { useState } from 'react';
import { HStack } from '@/components/ui/hstack';
import { Pressable } from '@/components/ui/pressable';
import { Text } from '@/components/ui/text';
interface NavItem {
id: string;
label: string;
href: string;
}
function AccessibleNav({ items, currentPath }: {
items: NavItem[];
currentPath: string;
}) {
return (
{items.map((item) => {
const isActive = currentPath === item.href;
return (
navigate(item.href)}
className={cn(
'px-4 py-2 rounded-lg',
isActive
? 'bg-primary-500'
: 'bg-transparent hover:bg-background-100'
)}
>
{item.label}
);
})}
);
}
```
### Accessible Data Table
```tsx
import { VStack } from '@/components/ui/vstack';
import { HStack } from '@/components/ui/hstack';
import { Box } from '@/components/ui/box';
import { Text } from '@/components/ui/text';
interface Column {
key: keyof T;
header: string;
accessibilityLabel?: string;
}
interface AccessibleTableProps {
columns: Column[];
data: T[];
caption: string;
}
function AccessibleTable({
columns,
data,
caption,
}: AccessibleTableProps) {
return (
{/* Caption for screen readers */}
{caption}
{/* Header Row */}
{columns.map((column) => (
{column.header}
))}
{/* Data Rows */}
{data.map((row, rowIndex) => (
{columns.map((column) => (
{String(row[column.key])}
))}
))}
);
}
```
### Accessible Alert Component
```tsx
import { HStack } from '@/components/ui/hstack';
import { VStack } from '@/components/ui/vstack';
import { Box } from '@/components/ui/box';
import { Text } from '@/components/ui/text';
import { Icon } from '@/components/ui/icon';
import {
AlertCircleIcon,
CheckCircleIcon,
InfoIcon,
AlertTriangleIcon,
} from 'lucide-react-native';
type AlertType = 'info' | 'success' | 'warning' | 'error';
interface AccessibleAlertProps {
type: AlertType;
title: string;
message: string;
}
const alertConfig: Record = {
info: {
icon: InfoIcon,
containerClass: 'bg-info-50 dark:bg-info-900 border-info-200',
iconClass: 'text-info-500',
role: 'status',
},
success: {
icon: CheckCircleIcon,
containerClass: 'bg-success-50 dark:bg-success-900 border-success-200',
iconClass: 'text-success-500',
role: 'status',
},
warning: {
icon: AlertTriangleIcon,
containerClass: 'bg-warning-50 dark:bg-warning-900 border-warning-200',
iconClass: 'text-warning-500',
role: 'alert',
},
error: {
icon: AlertCircleIcon,
containerClass: 'bg-error-50 dark:bg-error-900 border-error-200',
iconClass: 'text-error-500',
role: 'alert',
},
};
export function AccessibleAlert({ type, title, message }: AccessibleAlertProps) {
const config = alertConfig[type];
return (
{title}
{message}
);
}
```
## Common Patterns
### Skip Navigation Link
```tsx
import { useState } from 'react';
import { Pressable } from '@/components/ui/pressable';
import { Text } from '@/components/ui/text';
function SkipLink() {
const [isFocused, setIsFocused] = useState(false);
return (
{
// Focus main content
document.getElementById('main-content')?.focus();
}}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
accessibilityRole="link"
accessibilityLabel="Skip to main content"
className={cn(
'absolute left-4 z-50 px-4 py-2 bg-primary-500 rounded-md',
'transition-all duration-200',
isFocused ? 'top-4' : '-top-20'
)}
>
Skip to main content
);
}
```
### Loading State Announcement
```tsx
import { useEffect } from 'react';
import { AccessibilityInfo } from 'react-native';
import { Spinner } from '@/components/ui/spinner';
import { Text } from '@/components/ui/text';
import { VStack } from '@/components/ui/vstack';
function LoadingState({ isLoading, loadingText = 'Loading...' }: {
isLoading: boolean;
loadingText?: string;
}) {
useEffect(() => {
if (isLoading) {
AccessibilityInfo.announceForAccessibility(loadingText);
}
}, [isLoading, loadingText]);
if (!isLoading) return null;
return (
{loadingText}
);
}
```
## Anti-Patterns
### Do Not Hide Interactive Elements
```tsx
// Bad: Interactive element hidden from accessibility
Click me
// Good: Interactive element accessible
Click me
```
### Do Not Use Color Alone to Convey Information
```tsx
// Bad: Only color indicates error
// Good: Color plus icon and text
This field is required
```
### Do Not Remove Focus Indicators
```tsx
// Bad: Removing focus outline
Click
// Good: Visible focus indicator
Click
```
### Do Not Use Placeholder as Label
```tsx
// Bad: Placeholder only
// Good: Proper label
Email
```
## WCAG 2.1 AA Checklist
### Perceivable
- [ ] Text has 4.5:1 contrast ratio (3:1 for large text)
- [ ] Images have alt text
- [ ] Form inputs have visible labels
- [ ] Content is readable when zoomed to 200%
- [ ] Color is not the only means of conveying information
### Operable
- [ ] All functionality available via keyboard
- [ ] Focus order is logical
- [ ] Focus indicators are visible
- [ ] Touch targets are at least 44x44 points
- [ ] Users have enough time to read and interact
### Understandable
- [ ] Language is specified
- [ ] Navigation is consistent
- [ ] Form errors are identified and described
- [ ] Labels and instructions are provided
### Robust
- [ ] Valid markup/component structure
- [ ] Name, role, and value are programmatically determined
- [ ] Status messages are announced to screen readers
## Related Skills
- **gluestack-components**: Building UI with gluestack-ui components
- **gluestack-theming**: Customizing themes and design tokens