or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-context.mdcontent-accessibility.mddialog-root.mdindex.mdportal-layout.mdtriggers-controls.md

content-accessibility.mddocs/

0

# Content and Accessibility

1

2

Main content container with focus management and accessibility components for proper dialog implementation.

3

4

## Capabilities

5

6

### DialogContent

7

8

Primary content container that handles focus management, keyboard navigation, and dismissal behavior.

9

10

```typescript { .api }

11

/**

12

* Main content container for dialog with comprehensive accessibility features

13

* Handles focus trapping, keyboard navigation, and dismissal behavior

14

*/

15

type DialogContentElement = React.ComponentRef<typeof Primitive.div>;

16

interface DialogContentProps extends DialogContentTypeProps {

17

/** Force mounting when more control is needed for animations */

18

forceMount?: true;

19

}

20

21

interface DialogContentTypeProps extends Omit<DismissableLayerProps, 'onDismiss'> {

22

/**

23

* When `true`, focus cannot escape the `Content` via keyboard,

24

* pointer, or a programmatic focus.

25

* @defaultValue false (for non-modal), true (for modal)

26

*/

27

trapFocus?: boolean;

28

29

/**

30

* When `true`, hover/focus/click interactions will be disabled on elements outside

31

* the `DismissableLayer`. Users will need to click twice on outside elements to

32

* interact with them: once to close the `DismissableLayer`, and again to trigger the element.

33

*/

34

disableOutsidePointerEvents?: boolean;

35

36

/**

37

* Event handler called when auto-focusing on open.

38

* Can be prevented.

39

*/

40

onOpenAutoFocus?: (event: Event) => void;

41

42

/**

43

* Event handler called when auto-focusing on close.

44

* Can be prevented.

45

*/

46

onCloseAutoFocus?: (event: Event) => void;

47

48

/**

49

* Event handler called when the escape key is down.

50

* Can be prevented.

51

*/

52

onEscapeKeyDown?: (event: KeyboardEvent) => void;

53

54

/**

55

* Event handler called when the a `pointerdown` event happens outside of the `DismissableLayer`.

56

* Can be prevented.

57

*/

58

onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;

59

60

/**

61

* Event handler called when the focus moves outside of the `DismissableLayer`.

62

* Can be prevented.

63

*/

64

onFocusOutside?: (event: FocusOutsideEvent) => void;

65

66

/**

67

* Event handler called when an interaction happens outside the `DismissableLayer`.

68

* Specifically, when a `pointerdown` event happens outside or focus moves outside of it.

69

* Can be prevented.

70

*/

71

onInteractOutside?: (event: PointerDownOutsideEvent | FocusOutsideEvent) => void;

72

}

73

74

// Event types from DismissableLayer

75

type PointerDownOutsideEvent = CustomEvent<{ originalEvent: PointerEvent }>;

76

type FocusOutsideEvent = CustomEvent<{ originalEvent: FocusEvent }>;

77

78

type DismissableLayerProps = React.ComponentPropsWithoutRef<typeof Primitive.div> & {

79

disableOutsidePointerEvents?: boolean;

80

onEscapeKeyDown?: (event: KeyboardEvent) => void;

81

onPointerDownOutside?: (event: PointerDownOutsideEvent) => void;

82

onFocusOutside?: (event: FocusOutsideEvent) => void;

83

onInteractOutside?: (event: PointerDownOutsideEvent | FocusOutsideEvent) => void;

84

onDismiss?: () => void;

85

};

86

87

const DialogContent: React.ForwardRefExoticComponent<

88

DialogContentProps & React.RefAttributes<DialogContentElement>

89

>;

90

```

91

92

**Usage Examples:**

93

94

```typescript

95

import {

96

Dialog,

97

DialogTrigger,

98

DialogPortal,

99

DialogOverlay,

100

DialogContent,

101

DialogTitle,

102

DialogDescription,

103

DialogClose

104

} from "@radix-ui/react-dialog";

105

106

// Basic content

107

function BasicContent() {

108

return (

109

<Dialog>

110

<DialogTrigger>Open</DialogTrigger>

111

<DialogPortal>

112

<DialogOverlay />

113

<DialogContent>

114

<DialogTitle>Dialog Title</DialogTitle>

115

<DialogDescription>Dialog description here</DialogDescription>

116

<DialogClose>Close</DialogClose>

117

</DialogContent>

118

</DialogPortal>

119

</Dialog>

120

);

121

}

122

123

// Content with custom focus handling

124

function CustomFocusContent() {

125

const handleOpenAutoFocus = (event: Event) => {

126

// Prevent default auto-focus and focus specific element

127

event.preventDefault();

128

const firstInput = document.querySelector('input');

129

firstInput?.focus();

130

};

131

132

const handleCloseAutoFocus = (event: Event) => {

133

// Custom focus restoration

134

event.preventDefault();

135

const customButton = document.getElementById('custom-trigger');

136

customButton?.focus();

137

};

138

139

return (

140

<Dialog>

141

<DialogTrigger id="custom-trigger">Open</DialogTrigger>

142

<DialogPortal>

143

<DialogOverlay />

144

<DialogContent

145

onOpenAutoFocus={handleOpenAutoFocus}

146

onCloseAutoFocus={handleCloseAutoFocus}

147

>

148

<DialogTitle>Custom Focus Dialog</DialogTitle>

149

<input placeholder="This gets focused on open" />

150

<DialogClose>Close</DialogClose>

151

</DialogContent>

152

</DialogPortal>

153

</Dialog>

154

);

155

}

156

157

// Content with outside interaction handling

158

function OutsideInteractionContent() {

159

const handlePointerDownOutside = (event: PointerDownOutsideEvent) => {

160

console.log('Pointer down outside dialog');

161

// Prevent default to stop dialog from closing

162

// event.preventDefault();

163

};

164

165

const handleInteractOutside = (event: PointerDownOutsideEvent | FocusOutsideEvent) => {

166

// Handle any interaction outside the dialog

167

if (event.target?.closest('.keep-open-trigger')) {

168

event.preventDefault(); // Don't close when clicking this element

169

}

170

};

171

172

return (

173

<Dialog>

174

<DialogTrigger>Open</DialogTrigger>

175

<DialogPortal>

176

<DialogOverlay />

177

<DialogContent

178

onPointerDownOutside={handlePointerDownOutside}

179

onInteractOutside={handleInteractOutside}

180

>

181

<DialogTitle>Outside Interaction Dialog</DialogTitle>

182

<p>Try clicking outside or pressing ESC</p>

183

<DialogClose>Close</DialogClose>

184

</DialogContent>

185

</DialogPortal>

186

</Dialog>

187

);

188

}

189

190

// Non-modal content

191

function NonModalContent() {

192

return (

193

<Dialog modal={false}>

194

<DialogTrigger>Open Non-Modal</DialogTrigger>

195

<DialogPortal>

196

<DialogContent

197

trapFocus={false}

198

disableOutsidePointerEvents={false}

199

>

200

<DialogTitle>Non-Modal Dialog</DialogTitle>

201

<p>Focus is not trapped, background remains interactive</p>

202

<DialogClose>Close</DialogClose>

203

</DialogContent>

204

</DialogPortal>

205

</Dialog>

206

);

207

}

208

209

// Modal content with disabled outside pointer events

210

function DisabledOutsidePointerEventsContent() {

211

return (

212

<Dialog>

213

<DialogTrigger>Open Modal</DialogTrigger>

214

<DialogPortal>

215

<DialogOverlay />

216

<DialogContent

217

trapFocus={true}

218

disableOutsidePointerEvents={true}

219

>

220

<DialogTitle>Modal Dialog</DialogTitle>

221

<p>Outside interactions require two clicks: one to close dialog, one to activate element</p>

222

<DialogClose>Close</DialogClose>

223

</DialogContent>

224

</DialogPortal>

225

</Dialog>

226

);

227

}

228

```

229

230

### DialogTitle

231

232

Title component for dialog accessibility, automatically linked via ARIA attributes.

233

234

```typescript { .api }

235

/**

236

* Dialog title component for proper accessibility labeling

237

* Automatically referenced by DialogContent via aria-labelledby

238

*/

239

type DialogTitleElement = React.ComponentRef<typeof Primitive.h2>;

240

interface DialogTitleProps extends React.ComponentPropsWithoutRef<typeof Primitive.h2> {}

241

242

const DialogTitle: React.ForwardRefExoticComponent<

243

DialogTitleProps & React.RefAttributes<DialogTitleElement>

244

>;

245

```

246

247

**Usage Examples:**

248

249

```typescript

250

// Basic title

251

function BasicTitle() {

252

return (

253

<Dialog>

254

<DialogTrigger>Open</DialogTrigger>

255

<DialogPortal>

256

<DialogOverlay />

257

<DialogContent>

258

<DialogTitle>Confirm Delete</DialogTitle>

259

<p>Are you sure you want to delete this item?</p>

260

</DialogContent>

261

</DialogPortal>

262

</Dialog>

263

);

264

}

265

266

// Styled title

267

function StyledTitle() {

268

return (

269

<Dialog>

270

<DialogTrigger>Open</DialogTrigger>

271

<DialogPortal>

272

<DialogOverlay />

273

<DialogContent>

274

<DialogTitle className="dialog-title" style={{ fontSize: '24px' }}>

275

Settings

276

</DialogTitle>

277

<p>Configure your application settings</p>

278

</DialogContent>

279

</DialogPortal>

280

</Dialog>

281

);

282

}

283

284

// Visually hidden title (for accessibility only)

285

function VisuallyHiddenTitle() {

286

return (

287

<Dialog>

288

<DialogTrigger>Open</DialogTrigger>

289

<DialogPortal>

290

<DialogOverlay />

291

<DialogContent>

292

<DialogTitle className="sr-only">

293

Image Gallery

294

</DialogTitle>

295

{/* Visual content without visible title */}

296

<div className="image-gallery">

297

{/* Images here */}

298

</div>

299

</DialogContent>

300

</DialogPortal>

301

</Dialog>

302

);

303

}

304

```

305

306

### DialogDescription

307

308

Description component for dialog accessibility, automatically linked via ARIA attributes.

309

310

```typescript { .api }

311

/**

312

* Dialog description component for additional accessibility context

313

* Automatically referenced by DialogContent via aria-describedby

314

*/

315

type DialogDescriptionElement = React.ComponentRef<typeof Primitive.p>;

316

interface DialogDescriptionProps extends React.ComponentPropsWithoutRef<typeof Primitive.p> {}

317

318

const DialogDescription: React.ForwardRefExoticComponent<

319

DialogDescriptionProps & React.RefAttributes<DialogDescriptionElement>

320

>;

321

```

322

323

**Usage Examples:**

324

325

```typescript

326

// Basic description

327

function BasicDescription() {

328

return (

329

<Dialog>

330

<DialogTrigger>Delete Item</DialogTrigger>

331

<DialogPortal>

332

<DialogOverlay />

333

<DialogContent>

334

<DialogTitle>Confirm Deletion</DialogTitle>

335

<DialogDescription>

336

This action cannot be undone. The item will be permanently removed.

337

</DialogDescription>

338

<div className="dialog-actions">

339

<DialogClose>Cancel</DialogClose>

340

<DialogClose>Delete</DialogClose>

341

</div>

342

</DialogContent>

343

</DialogPortal>

344

</Dialog>

345

);

346

}

347

348

// Multiple descriptions

349

function MultipleDescriptions() {

350

return (

351

<Dialog>

352

<DialogTrigger>Show Details</DialogTrigger>

353

<DialogPortal>

354

<DialogOverlay />

355

<DialogContent>

356

<DialogTitle>User Profile</DialogTitle>

357

<DialogDescription>

358

View and edit your profile information below.

359

</DialogDescription>

360

<DialogDescription className="warning">

361

Changes will be saved automatically.

362

</DialogDescription>

363

<form>

364

{/* Form fields */}

365

</form>

366

</DialogContent>

367

</DialogPortal>

368

</Dialog>

369

);

370

}

371

372

// Optional description

373

function OptionalDescription() {

374

const [showDetails, setShowDetails] = React.useState(false);

375

376

return (

377

<Dialog>

378

<DialogTrigger>Open</DialogTrigger>

379

<DialogPortal>

380

<DialogOverlay />

381

<DialogContent aria-describedby={showDetails ? undefined : undefined}>

382

<DialogTitle>Simple Dialog</DialogTitle>

383

{showDetails && (

384

<DialogDescription>

385

Additional details when needed

386

</DialogDescription>

387

)}

388

<button onClick={() => setShowDetails(!showDetails)}>

389

{showDetails ? 'Hide' : 'Show'} Details

390

</button>

391

</DialogContent>

392

</DialogPortal>

393

</Dialog>

394

);

395

}

396

```

397

398

## Accessibility Features

399

400

### DialogContent Accessibility

401

402

DialogContent automatically provides:

403

404

- `role="dialog"` for proper semantic role

405

- `aria-labelledby` referencing DialogTitle ID

406

- `aria-describedby` referencing DialogDescription ID

407

- `data-state` attribute with "open" or "closed" values

408

- Focus management and trapping

409

- Keyboard navigation (ESC to close)

410

411

### Focus Management

412

413

#### Auto Focus Behavior

414

415

**On Open:**

416

1. Focus moves to first focusable element in DialogContent

417

2. If no focusable elements, focus goes to DialogContent itself

418

3. Can be customized with `onOpenAutoFocus`

419

420

**On Close:**

421

1. Focus returns to DialogTrigger that opened the dialog

422

2. Can be customized with `onCloseAutoFocus`

423

424

#### Focus Trapping

425

426

For modal dialogs:

427

- Tab cycles through focusable elements within DialogContent

428

- Shift+Tab cycles backwards

429

- Focus cannot escape the dialog via keyboard

430

431

### Keyboard Navigation

432

433

- **ESC**: Closes the dialog

434

- **Tab/Shift+Tab**: Navigate focusable elements

435

- **Enter/Space**: Activate buttons and controls

436

437

### Screen Reader Support

438

439

- Dialog is announced when opened

440

- Title and description are read to users

441

- Focus changes are announced

442

- State changes (open/closed) are communicated

443

444

## Development Warnings

445

446

In development mode, Radix UI provides helpful warnings:

447

448

- Missing DialogTitle warns in console

449

- Missing DialogDescription warns if aria-describedby is not undefined

450

- These warnings help ensure accessibility compliance

451

452

```typescript

453

// This will warn in development

454

<DialogContent>

455

{/* Missing DialogTitle */}

456

<p>Content without title</p>

457

</DialogContent>

458

459

// This will not warn

460

<DialogContent aria-describedby={undefined}>

461

<DialogTitle>Title Present</DialogTitle>

462

<p>Content without description (intentional)</p>

463

</DialogContent>

464

```