slogan3

Creëer een Aanpasbare Angular Directive voor Toegankelijkheidsverbeteringen

description

Met deze prompt kun je een krachtige en herbruikbare Angular directive maken die de toegankelijkheid van je applicatie aanzienlijk verbetert. Dit helpt bij het naleven van WCAG-standaarden, vergroot de gebruikersvriendelijkheid voor mensen met een beperking, en onderscheidt je applicatie door betere inclusiviteit. Het is een specifieke en praktische aanpak die verder gaat dan standaard componentontwikkeling, gericht op cruciale toegankelijkheidsaspecten.

prompt

Help me een Angular directive te maken die de toegankelijkheid van mijn componenten verbetert. Leg uit hoe ik ARIA-attributes kan toepassen en keyboard navigatie ...

try_prompt

generate_helper
disclaimerOnPageApi

ai_answers

provider: openai
model: gpt-4.1-nano
Natuurlijk! Hier is een overzicht en voorbeeld van hoe je een Angular directive kunt maken die de toegankelijkheid van je formulier met invoervelden verbetert door ARIA-attributes toe te passen en keyboard navigatie te ondersteunen, zodat je voldoet aan WCAG-richtlijnen. ### Stap 1: Maak een Angular directive voor toegankelijkheid Je kunt een directive maken die ARIA-attributes toevoegt en keyboard events afhandelt. ```typescript import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core'; @Directive({ selector: '[appAccessibleForm]' }) export class AccessibleFormDirective { constructor(private el: ElementRef, private renderer: Renderer2) {} ngOnInit() { // Voeg ARIA-attributes toe aan het formulier this.renderer.setAttribute(this.el.nativeElement, 'role', 'form'); this.renderer.setAttribute(this.el.nativeElement, 'aria-labelledby', 'formLabel'); // Voorbeeld: voeg label toe als er nog geen label is if (!this.el.nativeElement.querySelector('label')) { const label = this.renderer.createElement('label'); this.renderer.setAttribute(label, 'id', 'formLabel'); this.renderer.setProperty(label, 'innerText', 'Mijn formulier'); this.renderer.insertBefore(this.el.nativeElement, label, this.el.nativeElement.firstChild); } } @HostListener('keydown', ['$event']) handleKeyboardNavigation(event: KeyboardEvent) { const focusableElements = this.el.nativeElement.querySelectorAll('input, select, textarea, button, [tabindex]:not([tabindex="-1"])'); const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1]; if (!focusableElements.length) return; if (event.key === 'Tab') { if (event.shiftKey) { // Shift + Tab: ga naar vorige focusbare element if (document.activeElement === firstElement) { event.preventDefault(); (lastElement as HTMLElement).focus(); } } else { // Tab: ga naar volgende focusbare element if (document.activeElement === lastElement) { event.preventDefault(); (firstElement as HTMLElement).focus(); } } } } } ``` ### Stap 2: Gebruik de directive in je component Voeg de directive toe aan je formulier: ```html <form appAccessibleForm> <label for="naam">Naam:</label> <input type="text" id="naam" aria-required="true" required /> <label for="email">E-mail:</label> <input type="email" id="email" aria-required="true" required /> <button type="submit">Verzenden</button> </form> ``` ### Toelichting: - **ARIA-attributes**: We gebruiken `role="form"` en `aria-labelledby` voor het formulier. Voor invoervelden kunnen `aria-required="true"` toegevoegd worden zodat schermlezers dit aangeven. - **Label en associatie**: Labels worden gekoppeld aan inputs via `for` en `id`. - **Keyboard navigatie**: De directive zorgt dat tab en shift+tab correct werken, ook bij dynamisch toegevoegde velden. - **Focus indicatie**: Zorg dat je CSS hebt die focus duidelijk zichtbaar maakt. ### Extra tips: - Voeg `aria-describedby` toe voor hulptekst. - Zorg dat alle invoervelden een label hebben. - Test met schermlezers en op verschillende browsers. - Overweeg gebruik van Angular Material of andere componenten die al goede toegankelijkheid ondersteunen. Heb je nog specifieke vragen of wil je dat ik een volledige voorbeeldapplicatie voor je opzet?
provider: deepseek
model: deepseek-chat
Zeker! Ik help je een Angular directive te maken voor betere toegankelijkheid van formulieren. Hier is een complete uitleg met praktische implementatie. ## 1. Basis ARIA Directive Eerst maken we een directive die ARIA-attributen toevoegt: ```typescript // aria-enhanced-form.directive.ts import { Directive, ElementRef, AfterViewInit, HostListener } from '@angular/core'; @Directive({ selector: '[appAriaEnhancedForm]' }) export class AriaEnhancedFormDirective implements AfterViewInit { constructor(private el: ElementRef) {} ngAfterViewInit() { this.enhanceFormAccessibility(); } private enhanceFormAccessibility() { const form = this.el.nativeElement; // ARIA-attributen voor het formulier form.setAttribute('role', 'form'); form.setAttribute('aria-label', 'Invoerformulier'); // Verbetering voor alle input velden const inputs = form.querySelectorAll('input, textarea, select'); inputs.forEach((input: HTMLElement, index: number) => { this.enhanceInputElement(input, index); }); } private enhanceInputElement(input: HTMLElement, index: number) { // Voeg ARIA-labels toe als ze ontbreken if (!input.getAttribute('aria-label') && !input.getAttribute('aria-labelledby')) { const label = input.previousElementSibling?.tagName === 'LABEL' ? input.previousElementSibling : input.closest('label'); if (label) { input.setAttribute('aria-labelledby', label.id || this.generateId(label, index)); } } // Voeg ARIA-required toe voor verplichte velden if (input.hasAttribute('required')) { input.setAttribute('aria-required', 'true'); } // Verbeter focus management input.setAttribute('tabindex', input.hasAttribute('disabled') ? '-1' : '0'); } private generateId(element: Element, index: number): string { const id = `label-${index}-${Date.now()}`; element.id = id; return id; } } ``` ## 2. Keyboard Navigation Directive Nu maken we een directive voor toetsenbordnavigatie: ```typescript // keyboard-navigation.directive.ts import { Directive, ElementRef, AfterViewInit, HostListener } from '@angular/core'; @Directive({ selector: '[appKeyboardNavigation]' }) export class KeyboardNavigationDirective implements AfterViewInit { private focusableElements: HTMLElement[] = []; constructor(private el: ElementRef) {} ngAfterViewInit() { this.initializeFocusableElements(); } private initializeFocusableElements() { const form = this.el.nativeElement; this.focusableElements = Array.from(form.querySelectorAll( 'input:not([disabled]):not([type="hidden"]), ' + 'textarea:not([disabled]), ' + 'select:not([disabled]), ' + 'button:not([disabled]), ' + '[tabindex]:not([tabindex="-1"])' )); this.setupFocusManagement(); } private setupFocusManagement() { this.focusableElements.forEach((element, index) => { element.setAttribute('tabindex', index === 0 ? '0' : '-1'); }); } @HostListener('keydown', ['$event']) handleKeyboardNavigation(event: KeyboardEvent) { const currentIndex = this.focusableElements.indexOf(event.target as HTMLElement); switch (event.key) { case 'Tab': this.handleTabNavigation(event, currentIndex); break; case 'ArrowDown': case 'ArrowRight': event.preventDefault(); this.focusNextElement(currentIndex); break; case 'ArrowUp': case 'ArrowLeft': event.preventDefault(); this.focusPreviousElement(currentIndex); break; case 'Enter': this.handleEnterKey(event, currentIndex); break; case 'Escape': this.handleEscapeKey(event); break; } } private handleTabNavigation(event: KeyboardEvent, currentIndex: number) { if (event.shiftKey && currentIndex === 0) { event.preventDefault(); this.focusableElements[this.focusableElements.length - 1]?.focus(); } else if (!event.shiftKey && currentIndex === this.focusableElements.length - 1) { event.preventDefault(); this.focusableElements[0]?.focus(); } } private focusNextElement(currentIndex: number) { const nextIndex = (currentIndex + 1) % this.focusableElements.length; this.focusableElements[nextIndex]?.focus(); } private focusPreviousElement(currentIndex: number) { const prevIndex = currentIndex > 0 ? currentIndex - 1 : this.focusableElements.length - 1; this.focusableElements[prevIndex]?.focus(); } private handleEnterKey(event: KeyboardEvent, currentIndex: number) { const element = this.focusableElements[currentIndex]; if (element.tagName === 'BUTTON' || element.getAttribute('role') === 'button') { element.click(); } } private handleEscapeKey(event: KeyboardEvent) { // Reset form of sluit modal const form = this.el.nativeElement; form.dispatchEvent(new Event('cancel', { bubbles: true })); } } ``` ## 3. Gebruik in Component Template ```html <!-- form.component.html --> <form appAriaEnhancedForm appKeyboardNavigation class="accessible-form" (ngSubmit)="onSubmit()" > <div class="form-group"> <label for="name" class="required">Naam</label> <input id="name" type="text" required aria-describedby="name-help" [(ngModel)]="user.name" name="name" > <span id="name-help" class="help-text">Vul uw volledige naam in</span> </div> <div class="form-group"> <label for="email">E-mailadres</label> <input id="email" type="email" required aria-invalid="false" [(ngModel)]="user.email" name="email" > </div> <fieldset aria-describedby="newsletter-description"> <legend>Nieuwsbrief voorkeuren</legend> <p id="newsletter-description">Kies hoe vaak u onze nieuwsbrief wilt ontvangen</p> <div class="radio-group" role="radiogroup" aria-labelledby="frequency-label"> <span id="frequency-label" class="sr-only">Frequentie</span> <label> <input type="radio" name="frequency" value="daily" aria-checked="false"> Dagelijks </label> <label> <input type="radio" name="frequency" value="weekly" aria-checked="false"> Wekelijks </label> </div> </fieldset> <div class="form-actions"> <button type="submit" aria-label="Formulier verzenden"> Verzenden </button> <button type="button" aria-label="Formulier annuleren"> Annuleren </button> </div> </form> ``` ## 4. Aanvullende CSS voor Toegankelijkheid ```css /* accessible-form.component.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; } .accessible-form input:focus, .accessible-form button:focus, .accessible-form select:focus { outline: 2px solid #005fcc; outline-offset: 2px; } .required::after { content: " *"; color: #d63384; } .help-text { font-size: 0.875rem; color: #6c757d; } /* Hoge contrast ondersteuning */ @media (prefers-contrast: high) { .accessible-form { border: 2px solid #000; } .accessible-form input { border: 2px solid #000; } } ``` ## 5. Module Registratie ```typescript // app.module.ts import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { AriaEnhancedFormDirective } from './aria-enhanced-form.directive'; import { KeyboardNavigationDirective } from './keyboard-navigation.directive'; @NgModule({ declarations: [ AriaEnhancedFormDirective, KeyboardNavigationDirective, // ... andere componenten ], imports: [ FormsModule ] }) export class AppModule { } ``` ## Belangrijke WCAG Richtlijnen die worden afgedekt: 1. **WCAG 2.1.1 Keyboard** - Volledige toetsenbordnavigatie 2. **WCAG 4.1.2 Name, Role, Value** - Juiste ARIA-attributen 3. **WCAG 3.3.2 Labels or Instructions** - Duidelijke labels 4. **WCAG 2.4.7 Focus Visible** - Zichtbare focus indicators Deze implementatie zorgt voor: - Logische tab-volgorde - Pijltjestoets navigatie binnen formuliergroepen - Juiste ARIA-labels en beschrijvingen - Keyboard shortcuts (Escape, Enter) - Focus management - Screen reader compatibiliteit Test altijd met screen readers (NVDA, VoiceOver) en alleen met toetsenbord om de toegankelijkheid te verifiëren!