Many WCAG/Accessibility Issues Unaddressed
-
I’m trying to get my website in compliance with WCAG 2.2 standards, which has been fairly easy UNTIL I scan pages with RTCL elements. Things like Search Filter fields and listing “thumbs” are not ADA/WCAG compliant, and I can’t seem to “force” compliance via things like manually injecting ARIA labels via HTML/JS, likely because of how the container is wrapped via AJAX… Really wish someone from the RTCL dev would look into this. ADA/ECAG compliance is a legal requirement in the US and UE, so this is a pretty HUGE oversight on part of RT.
The page I need help with: [log in to see the link]
-
Hi,
I see there exist aria-label for input field which field don’t have label element and also available alt attribute for image elements. Could you inform how did you scan it to check WCAG 2.2 standards compliant?Thank you
Yes, I had to insert a TON of custom PHP/JS/HTML to get this to work. The solution is not native to your plugin. So, you will be seeing MY FIX right now, which got things to work and will show ARIA labels correctly. But the problem is still there for other users and is still underlying in your plugin. My site is fixed and accessible because of all the custom code I wrote, I would be happy to share that code here.
You can use any WCAG checker you would like (it’s pretty obvious you don’t build any ADA/WCAG accessibility into your plugin if you are asking what tools to use to check). But I use GetWCAG, Accessibe, EqualWeb, and WAVE, to name a few of the best tools).Here is the JS I wrote to handle the Search Filter/Slider (Radius) Aria Label Add:
/* Accessibility fixer for noUiSlider handles (labels + aria-valuetext).
Drop into Code Snippets as JS (Run everywhere). */
(function () {
const DEFAULT_LABEL = ‘Radius in miles’;// Try to find a meaningful visible label near the slider element. function getMeaningfulLabelForSlider(sliderEl) { // 1) Check for an explicit aria-labelledby on the slider or ancestors let el = sliderEl; for (let i = 0; i < 6 && el; i++, el = el.parentElement) { const labelledBy = el.getAttribute && el.getAttribute('aria-labelledby'); if (labelledBy) { const referenced = document.getElementById(labelledBy); if (referenced && referenced.textContent.trim()) return referenced.textContent.trim(); } } // 2) Look for preceding label/heading text (immediate siblings / ancestors) el = sliderEl; for (let depth = 0; depth < 6 && el; depth++, el = el.parentElement) { const prev = el.previousElementSibling; if (prev && prev.textContent && prev.textContent.trim()) { const txt = prev.textContent.trim(); // If it looks like a label (contains letters), return it if (/[A-Za-z]/.test(txt)) return txt; } // also check for a label element inside parent const label = el.querySelector && el.querySelector('label, .label, .filter-label, .rtcl-label'); if (label && label.textContent && label.textContent.trim()) return label.textContent.trim(); } // fallback return DEFAULT_LABEL; } // Set aria attributes on a handle and hook updates function setHandleAccessibility(handleEl, sliderEl, labelText, handleIndex) { if (!handleEl) return; // If there is already an aria-labelledby, skip setting aria-label if (!handleEl.hasAttribute('aria-label') && !handleEl.hasAttribute('aria-labelledby')) { handleEl.setAttribute('aria-label', labelText); } // Initialize aria-valuetext from aria-valuenow if present const now = handleEl.getAttribute('aria-valuenow'); if (now !== null) handleEl.setAttribute('aria-valuetext', now + ' miles'); // Attach to noUiSlider update if available on the parent slider element try { if (sliderEl && sliderEl.noUiSlider && typeof sliderEl.noUiSlider.on === 'function' && !sliderEl.__ariaUpdateHooked) { sliderEl.noUiSlider.on('update', function (values, idx) { const handles = sliderEl.querySelectorAll('.noUi-handle'); const h = handles[idx]; if (h) h.setAttribute('aria-valuetext', values[idx] + ' miles'); }); sliderEl.__ariaUpdateHooked = true; } else { // Fallback: observe aria-valuenow changes on this handle if (!handleEl.__ariaObserver) { const obs = new MutationObserver((recs) => { recs.forEach(r => { if (r.type === 'attributes' && r.attributeName === 'aria-valuenow') { const v = handleEl.getAttribute('aria-valuenow'); if (v !== null) handleEl.setAttribute('aria-valuetext', v + ' miles'); } }); }); obs.observe(handleEl, { attributes: true, attributeFilter: ['aria-valuenow'] }); handleEl.__ariaObserver = obs; } } } catch (e) { // be silent if anything goes wrong console.error && console.error('aria-fix slider hook error', e); } } // Fix a given .noUi-target element (the slider container) function fixSliderElement(sliderEl) { if (!sliderEl || sliderEl.dataset.__ariaFixed === '1') return; const label = getMeaningfulLabelForSlider(sliderEl); const handles = sliderEl.querySelectorAll('.noUi-handle'); if (!handles || handles.length === 0) { // nothing yet – don't mark as fixed so we can retry later return; } handles.forEach((h, idx) => { // For multi-handle sliders, give meaningful min/max labels let labelText = label; if (handles.length > 1) { labelText = (idx === 0) ? (label + ' (minimum)') : (label + ' (maximum)'); } setHandleAccessibility(h, sliderEl, labelText, idx); }); // mark fixed sliderEl.dataset.__ariaFixed = '1'; } // 1) Monkey-patch noUiSlider.create if present so we can fix immediately after creation if (window.noUiSlider && typeof window.noUiSlider.create === 'function') { const origCreate = window.noUiSlider.create; window.noUiSlider.create = function (target, options) { const result = origCreate.apply(this, arguments); // Timeout 0 to allow plugin's creation to finish then apply the accessibility fixes setTimeout(function () { try { fixSliderElement(target); } catch (e) { /* noop */ } }, 0); return result; }; } // 2) MutationObserver to catch sliders added or replaced dynamically const bodyObserver = new MutationObserver((mutations) => { mutations.forEach(m => { if (m.type === 'childList') { m.addedNodes.forEach(node => { if (node.nodeType !== 1) return; // If the node itself is a slider if (node.matches && node.matches('.noUi-target')) { fixSliderElement(node); } // Or contains slider descendants if (node.querySelectorAll) { node.querySelectorAll('.noUi-target').forEach(fixSliderElement); } }); } }); }); bodyObserver.observe(document.documentElement || document.body, { childList: true, subtree: true }); // 3) Initial pass for any already-present sliders document.querySelectorAll('.noUi-target').forEach(fixSliderElement); // 4) Poll briefly as a last resort for super-late renders (stop after ~10s) let tries = 0; const poll = setInterval(() => { document.querySelectorAll('.noUi-target').forEach(fixSliderElement); if (++tries > 100) clearInterval(poll); // 100 * 100ms = 10s }, 100); // Optional: expose a debugger hook window.__noUiA11yFix = { fixSliderElement };})();
Hi,
Yes, we noticed it and checked with this tool https://wave.webaim.org/We will sort out soon.
Thank you
You must be logged in to reply to this topic.