or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

animation-and-transitions.mdevent-management.mdfocus-and-accessibility.mdid-and-refs.mdindex.mdlinks-and-navigation.mdmiscellaneous-utilities.mdplatform-detection.mdprops-and-events.mdscrolling-and-layout.mdshadow-dom-support.mdstate-and-effects.mdvirtual-events-and-input.md

focus-and-accessibility.mddocs/

0

# Focus & Accessibility

1

2

Focus management, element accessibility checks, and ARIA labeling utilities for building accessible React components.

3

4

## Capabilities

5

6

### Focus Management

7

8

Utilities for managing focus behavior and determining focusability.

9

10

```typescript { .api }

11

/**

12

* Focuses element without scrolling the page

13

* Polyfill for {preventScroll: true} option in older browsers

14

* @param element - Element to focus

15

*/

16

function focusWithoutScrolling(element: FocusableElement): void;

17

18

/**

19

* Determines if element can receive focus

20

* @param element - Element to check

21

* @returns true if element is focusable

22

*/

23

function isFocusable(element: Element): boolean;

24

25

/**

26

* Determines if element is reachable via Tab key

27

* @param element - Element to check

28

* @returns true if element is tabbable (excludes tabindex="-1")

29

*/

30

function isTabbable(element: Element): boolean;

31

32

type FocusableElement = HTMLElement | SVGElement;

33

```

34

35

**Usage Examples:**

36

37

```typescript

38

import { focusWithoutScrolling, isFocusable, isTabbable } from "@react-aria/utils";

39

40

function FocusManager({ children }) {

41

const containerRef = useRef<HTMLDivElement>(null);

42

43

const focusFirstElement = () => {

44

if (!containerRef.current) return;

45

46

// Find first focusable element

47

const focusableElements = Array.from(

48

containerRef.current.querySelectorAll('*')

49

).filter(isFocusable);

50

51

if (focusableElements.length > 0) {

52

focusWithoutScrolling(focusableElements[0] as FocusableElement);

53

}

54

};

55

56

const focusFirstTabbable = () => {

57

if (!containerRef.current) return;

58

59

// Find first tabbable element (keyboard accessible)

60

const tabbableElements = Array.from(

61

containerRef.current.querySelectorAll('*')

62

).filter(isTabbable);

63

64

if (tabbableElements.length > 0) {

65

focusWithoutScrolling(tabbableElements[0] as FocusableElement);

66

}

67

};

68

69

return (

70

<div ref={containerRef}>

71

<button onClick={focusFirstElement}>Focus First Focusable</button>

72

<button onClick={focusFirstTabbable}>Focus First Tabbable</button>

73

{children}

74

</div>

75

);

76

}

77

78

// Modal focus management

79

function Modal({ isOpen, children }) {

80

const modalRef = useRef<HTMLDivElement>(null);

81

82

useEffect(() => {

83

if (isOpen && modalRef.current) {

84

// Focus first tabbable element when modal opens

85

const tabbable = Array.from(modalRef.current.querySelectorAll('*'))

86

.filter(isTabbable);

87

88

if (tabbable.length > 0) {

89

focusWithoutScrolling(tabbable[0] as FocusableElement);

90

}

91

}

92

}, [isOpen]);

93

94

return isOpen ? (

95

<div ref={modalRef} role="dialog">

96

{children}

97

</div>

98

) : null;

99

}

100

```

101

102

### ARIA Labeling

103

104

Utilities for managing ARIA labels and descriptions.

105

106

```typescript { .api }

107

/**

108

* Processes aria-label and aria-labelledby attributes

109

* @param props - Props containing labeling attributes

110

* @param defaultLabel - Fallback label when none provided

111

* @returns Props with processed labeling attributes

112

*/

113

function useLabels(

114

props: AriaLabelingProps,

115

defaultLabel?: string

116

): DOMProps & AriaLabelingProps;

117

118

/**

119

* Manages aria-describedby attributes

120

* @param description - Description text to associate with element

121

* @returns Props with aria-describedby attribute

122

*/

123

function useDescription(description: string | undefined): DOMProps & AriaLabelingProps;

124

125

interface AriaLabelingProps {

126

"aria-label"?: string;

127

"aria-labelledby"?: string;

128

"aria-describedby"?: string;

129

"aria-details"?: string;

130

}

131

```

132

133

**Usage Examples:**

134

135

```typescript

136

import { useLabels, useDescription, useId } from "@react-aria/utils";

137

138

function LabeledInput({ label, description, placeholder, ...props }) {

139

const inputId = useId();

140

141

// Handle labeling - creates element if aria-label provided

142

const labelProps = useLabels({

143

"aria-label": label,

144

...props

145

}, placeholder);

146

147

// Handle description - creates element for description text

148

const descriptionProps = useDescription(description);

149

150

// Merge all labeling props

151

const finalProps = {

152

id: inputId,

153

...labelProps,

154

...descriptionProps,

155

placeholder

156

};

157

158

return (

159

<div>

160

{/* Label element created by useLabels if needed */}

161

<input {...finalProps} />

162

{/* Description element created by useDescription if needed */}

163

</div>

164

);

165

}

166

167

// Usage

168

<LabeledInput

169

aria-label="Search query"

170

description="Enter keywords to search the catalog"

171

placeholder="Search..."

172

/>

173

```

174

175

### Advanced Labeling Patterns

176

177

Complex labeling scenarios with multiple label sources:

178

179

```typescript

180

import { useLabels, useDescription, useId, mergeIds } from "@react-aria/utils";

181

182

function ComplexForm() {

183

const fieldId = useId();

184

const groupId = useId();

185

186

return (

187

<fieldset>

188

<legend id={groupId}>Personal Information</legend>

189

190

<LabeledField

191

id={fieldId}

192

label="Full Name"

193

description="Enter your first and last name"

194

groupLabelId={groupId}

195

required

196

/>

197

</fieldset>

198

);

199

}

200

201

function LabeledField({

202

id,

203

label,

204

description,

205

groupLabelId,

206

required,

207

...props

208

}) {

209

const labelId = useId();

210

const defaultId = useId();

211

const finalId = mergeIds(defaultId, id);

212

213

// Create labeling props with multiple sources

214

const labelingProps = useLabels({

215

"aria-labelledby": mergeIds(groupLabelId, labelId),

216

"aria-label": !label ? `${required ? 'Required' : 'Optional'} field` : undefined

217

});

218

219

const descriptionProps = useDescription(description);

220

221

return (

222

<div>

223

<label id={labelId} htmlFor={finalId}>

224

{label}

225

{required && <span aria-hidden="true">*</span>}

226

</label>

227

<input

228

id={finalId}

229

required={required}

230

{...labelingProps}

231

{...descriptionProps}

232

{...props}

233

/>

234

</div>

235

);

236

}

237

```

238

239

### Focus Trap Implementation

240

241

Using focus utilities to create a focus trap:

242

243

```typescript

244

import { isTabbable, focusWithoutScrolling } from "@react-aria/utils";

245

246

function useFocusTrap(isActive: boolean) {

247

const containerRef = useRef<HTMLElement>(null);

248

249

useEffect(() => {

250

if (!isActive || !containerRef.current) return;

251

252

const container = containerRef.current;

253

const tabbableElements = Array.from(container.querySelectorAll('*'))

254

.filter(isTabbable) as FocusableElement[];

255

256

if (tabbableElements.length === 0) return;

257

258

const firstTabbable = tabbableElements[0];

259

const lastTabbable = tabbableElements[tabbableElements.length - 1];

260

261

const handleKeyDown = (e: KeyboardEvent) => {

262

if (e.key !== 'Tab') return;

263

264

if (e.shiftKey) {

265

// Shift+Tab: focus last element if currently on first

266

if (document.activeElement === firstTabbable) {

267

e.preventDefault();

268

focusWithoutScrolling(lastTabbable);

269

}

270

} else {

271

// Tab: focus first element if currently on last

272

if (document.activeElement === lastTabbable) {

273

e.preventDefault();

274

focusWithoutScrolling(firstTabbable);

275

}

276

}

277

};

278

279

// Focus first element initially

280

focusWithoutScrolling(firstTabbable);

281

282

container.addEventListener('keydown', handleKeyDown);

283

return () => container.removeEventListener('keydown', handleKeyDown);

284

}, [isActive]);

285

286

return containerRef;

287

}

288

289

// Usage in modal

290

function Modal({ isOpen, onClose, children }) {

291

const trapRef = useFocusTrap(isOpen);

292

293

return isOpen ? (

294

<div ref={trapRef} role="dialog" aria-modal="true">

295

<button onClick={onClose}>Close</button>

296

{children}

297

</div>

298

) : null;

299

}

300

```

301

302

## Types

303

304

```typescript { .api }

305

interface DOMProps {

306

id?: string;

307

}

308

309

interface AriaLabelingProps {

310

"aria-label"?: string;

311

"aria-labelledby"?: string;

312

"aria-describedby"?: string;

313

"aria-details"?: string;

314

}

315

316

type FocusableElement = HTMLElement | SVGElement;

317

```