Help mij bij het implementeren van React Portalen voor geavanceerd focusbeheer en toegankelijkheid
description
Met deze prompt krijg je gerichte begeleiding om React Portalen effectief te gebruiken voor complexe UI-elementen waarbij toegankelijkheid en gebruikerservaring centraal staan. Het helpt je om focusbeheer en keyboard navigatie correct te implementeren, wat essentieel is voor gebruikers met beperkingen. Dit voorkomt veelvoorkomende valkuilen en zorgt voor een robuuste, toegankelijke interface die beter presteert dan standaard oplossingen.
prompt
Help mij bij het implementeren van React Portalen om complexe UI-elementen zoals interactieve modals, dialoogvensters of contextmenu's te renderen met geavanceerd focusbeheer en optimale toegankelijkheid. ...
try_prompt
generate_helper
disclaimerOnPageApi
tags
ai_answers
provider: openai
model: gpt-4.1-nano
Natuurlijk! Hier is een uitgebreide gids voor het implementeren van React Portalen voor complexe UI-elementen zoals modale vensters, inclusief focus trap, keyboard navigatie en ARIA-rollen:
1. Gebruik React Portalen voor rendering:
- Maak een aparte DOM-node (bijvoorbeeld `<div id="modal-root"></div>`) in je HTML.
- Gebruik ReactDOM.createPortal() om je modaal in die node te renderen.
```jsx
import { createPortal } from 'react-dom';
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
return createPortal(
<div role="dialog" aria-modal="true" aria-labelledby="modalTitle" aria-describedby="modalDesc" tabIndex="-1" ref={modalRef}>
{children}
</div>,
document.getElementById('modal-root')
);
}
```
2. Focus trap implementeren:
- Gebruik een ref op het modale element.
- Bij openen, focus de eerste focusbare element binnen de modal.
- Voeg een keydown event listener toe om Tab en Shift+Tab te beheren, zodat focus binnen de modal blijft.
- Bijvoorbeeld:
```jsx
import { useRef, useEffect } from 'react';
function useFocusTrap(isOpen, onClose) {
const modalRef = useRef(null);
useEffect(() => {
if (isOpen) {
const focusableElements = modalRef.current.querySelectorAll(
'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
firstElement.focus();
const handleKeyDown = (e) => {
if (e.key === 'Tab') {
const focusables = Array.prototype.slice.call(focusableElements);
const index = focusables.indexOf(document.activeElement);
if (e.shiftKey) {
if (index === 0) {
e.preventDefault();
focusables[focusables.length - 1].focus();
}
} else {
if (index === focusables.length - 1) {
e.preventDefault();
focusables[0].focus();
}
}
}
if (e.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [isOpen, onClose]);
return modalRef;
}
```
3. Keyboard navigatie:
- Zorg dat alle interactieve elementen binnen de modal toegankelijk zijn via Tab en Shift+Tab.
- Voeg eventueel extra toetsenbordintegraties toe afhankelijk van je behoeften.
4. ARIA-rollen en labels:
- Gebruik `role="dialog"` of `role="alertdialog"` afhankelijk van het scenario.
- Voeg `aria-modal="true"` toe voor schermlezercommunicatie.
- Gebruik `aria-labelledby` en `aria-describedby` om de titel en beschrijving te koppelen:
```jsx
<div
role="dialog"
aria-modal="true"
aria-labelledby="modalTitle"
aria-describedby="modalDesc"
tabIndex="-1"
ref={modalRef}
>
<h2 id="modalTitle">Titel van de modal</h2>
<p id="modalDesc">Beschrijving of instructies</p>
{/* rest van inhoud */}
</div>
```
5. Samenvatting:
- Render je modal via React Portalen.
- Beheer focus zodat deze automatisch naar de modal gaat en binnen de modal blijft (focus trap).
- Zorg dat toetsenbordintegraties soepel verlopen.
- Pas ARIA-rollen en labels toe voor toegankelijkheid.
Wil je dat ik een compleet voorbeeld voor je opstel inclusief alle onderdelen?
provider: deepseek
model: deepseek-chat
Zeker! Ik help je bij het implementeren van een toegankelijk modaal venster met React Portalen. Hier is een complete handleiding:
## 1. Portal Setup voor Modaal Venster
Eerst maak je een portal component:
```jsx
// Portal.jsx
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
const Portal = ({ children }) => {
const [portalElement, setPortalElement] = useState(null);
useEffect(() => {
const element = document.createElement('div');
element.setAttribute('id', 'modal-portal');
document.body.appendChild(element);
setPortalElement(element);
return () => {
document.body.removeChild(element);
};
}, []);
if (!portalElement) return null;
return createPortal(children, portalElement);
};
export default Portal;
```
## 2. Toegankelijk Modaal Component
```jsx
// AccessibleModal.jsx
import { useEffect, useRef, useCallback } from 'react';
import Portal from './Portal';
const AccessibleModal = ({ isOpen, onClose, title, children }) => {
const modalRef = useRef(null);
const previousActiveElement = useRef(null);
// Focus management
useEffect(() => {
if (isOpen) {
// Sla huidige focus element op
previousActiveElement.current = document.activeElement;
// Focus op modal zetten
if (modalRef.current) {
modalRef.current.focus();
}
// Voorkom scrollen op achtergrond
document.body.style.overflow = 'hidden';
}
return () => {
// Herstel scroll gedrag
document.body.style.overflow = 'unset';
// Focus terugzetten naar vorig element
if (previousActiveElement.current) {
previousActiveElement.current.focus();
}
};
}, [isOpen]);
// Focus trapping
const handleKeyDown = useCallback((event) => {
if (!isOpen) return;
const focusableElements = modalRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (!focusableElements || focusableElements.length === 0) return;
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
switch (event.key) {
case 'Tab':
if (event.shiftKey) {
if (document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
}
} else {
if (document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}
break;
case 'Escape':
onClose();
break;
default:
break;
}
}, [isOpen, onClose]);
// Click outside to close
const handleBackdropClick = (event) => {
if (event.target === event.currentTarget) {
onClose();
}
};
if (!isOpen) return null;
return (
<Portal>
{/* Backdrop */}
<div
className="modal-backdrop"
onClick={handleBackdropClick}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000,
}}
>
{/* Modal container */}
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-description"
tabIndex={-1}
onKeyDown={handleKeyDown}
style={{
background: 'white',
padding: '2rem',
borderRadius: '8px',
minWidth: '300px',
maxWidth: '90vw',
maxHeight: '90vh',
overflow: 'auto',
}}
>
{/* Titel */}
<h2 id="modal-title" className="modal-title">
{title}
</h2>
{/* Beschrijving */}
<div id="modal-description" className="sr-only">
Dialoogvenster voor gebruikerstoegang
</div>
{/* Inhoud */}
<div className="modal-content">
{children}
</div>
{/* Actie knoppen */}
<div style={{ marginTop: '1rem', display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
<button
type="button"
onClick={onClose}
style={{ padding: '0.5rem 1rem' }}
>
Annuleren
</button>
<button
type="submit"
style={{ padding: '0.5rem 1rem' }}
>
Bevestigen
</button>
</div>
</div>
</div>
</Portal>
);
};
export default AccessibleModal;
```
## 3. Gebruikers Toegang Modal Implementatie
```jsx
// UserAccessModal.jsx
import { useState } from 'react';
import AccessibleModal from './AccessibleModal';
const UserAccessModal = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [formData, setFormData] = useState({
username: '',
password: '',
rememberMe: false
});
const handleSubmit = (event) => {
event.preventDefault();
// Verwerk login logica hier
console.log('Form data:', formData);
setIsModalOpen(false);
};
const handleInputChange = (event) => {
const { name, value, type, checked } = event.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
return (
<div>
<button
onClick={() => setIsModalOpen(true)}
aria-haspopup="dialog"
>
Gebruikerstoegang Beheren
</button>
<AccessibleModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
title="Gebruikerstoegang Beheren"
>
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: '1rem' }}>
<label htmlFor="username" style={{ display: 'block', marginBottom: '0.5rem' }}>
Gebruikersnaam:
</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleInputChange}
required
aria-required="true"
style={{ width: '100%', padding: '0.5rem' }}
/>
</div>
<div style={{ marginBottom: '1rem' }}>
<label htmlFor="password" style={{ display: 'block', marginBottom: '0.5rem' }}>
Wachtwoord:
</label>
<input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleInputChange}
required
aria-required="true"
style={{ width: '100%', padding: '0.5rem' }}
/>
</div>
<div style={{ marginBottom: '1rem' }}>
<label>
<input
type="checkbox"
name="rememberMe"
checked={formData.rememberMe}
onChange={handleInputChange}
/>
Onthoud mij
</label>
</div>
</form>
</AccessibleModal>
</div>
);
};
export default UserAccessModal;
```
## 4. CSS voor Screen Reader Ondersteuning
```css
/* screen-reader.css */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Focus styles voor toegankelijkheid */
.modal-backdrop:focus-within {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
button:focus,
input:focus,
select:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
```
## Belangrijke ARIA-attributen en Toegankelijkheidsfuncties:
1. **`role="dialog"`**: Identificeert het element als een dialoogvenster
2. **`aria-modal="true"`**: Gaat aan dat de modal de onderliggende content blokkeert
3. **`aria-labelledby`**: Koppelt de titel aan de modal
4. **`aria-describedby`**: Voorziet aanvullende beschrijving
5. **Focus trapping**: Houdt focus binnen de modal
6. **Escape-toets**: Sluit modal met Escape key
7. **Keyboard navigatie**: Volledige tab-ondersteuning
## Gebruik in je App:
```jsx
// App.jsx
import UserAccessModal from './components/UserAccessModal';
function App() {
return (
<div className="App">
<h1>Mijn Applicatie</h1>
<UserAccessModal />
</div>
);
}
export default App;
```
Deze implementatie zorgt voor een volledig toegankelijke modal met focus management, keyboard navigatie en ARIA-ondersteuning volgens WCAG richtlijnen.
error: Generation failed [deepseek] prompt_id=4073 locale=en err=DeepSeek connection/timeout | Generation failed [deepseek] prompt_id=4073 locale=es err=DeepSeek connection/timeout