@accesslint/core 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/i18n/registry.d.ts +2 -1
- package/dist/i18n/registry.d.ts.map +1 -1
- package/dist/i18n/types.d.ts +3 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.cjs +5 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.iife.js +5 -5
- package/dist/index.js +983 -936
- package/dist/rules/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
let
|
|
2
|
-
function
|
|
3
|
-
|
|
1
|
+
let U = /* @__PURE__ */ new WeakMap();
|
|
2
|
+
function Le() {
|
|
3
|
+
U = /* @__PURE__ */ new WeakMap();
|
|
4
4
|
}
|
|
5
|
-
function
|
|
5
|
+
function ve(t) {
|
|
6
6
|
var n;
|
|
7
7
|
const a = t.tagName.toLowerCase(), e = (n = t.getAttribute("type")) == null ? void 0 : n.toLowerCase();
|
|
8
8
|
switch (a) {
|
|
@@ -117,25 +117,25 @@ function ge(t) {
|
|
|
117
117
|
return null;
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
-
function
|
|
120
|
+
function N(t) {
|
|
121
121
|
var i;
|
|
122
|
-
const a =
|
|
122
|
+
const a = U.get(t);
|
|
123
123
|
if (a !== void 0) return a;
|
|
124
|
-
const n = ((i = t.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase()) || null ||
|
|
125
|
-
return
|
|
124
|
+
const n = ((i = t.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase()) || null || ve(t);
|
|
125
|
+
return U.set(t, n), n;
|
|
126
126
|
}
|
|
127
|
-
let
|
|
128
|
-
function
|
|
129
|
-
|
|
127
|
+
let B = /* @__PURE__ */ new WeakMap();
|
|
128
|
+
function Te() {
|
|
129
|
+
B = /* @__PURE__ */ new WeakMap();
|
|
130
130
|
}
|
|
131
131
|
function v(t) {
|
|
132
|
-
const a =
|
|
132
|
+
const a = B.get(t);
|
|
133
133
|
if (a !== void 0) return a;
|
|
134
|
-
const e =
|
|
135
|
-
return
|
|
134
|
+
const e = Re(t);
|
|
135
|
+
return B.set(t, e), e;
|
|
136
136
|
}
|
|
137
|
-
function
|
|
138
|
-
var
|
|
137
|
+
function Re(t) {
|
|
138
|
+
var o, r, s, l, p;
|
|
139
139
|
const a = t.getAttribute("aria-labelledby");
|
|
140
140
|
if (a) {
|
|
141
141
|
const c = a.split(/\s+/).map((d) => {
|
|
@@ -144,7 +144,7 @@ function Ee(t) {
|
|
|
144
144
|
}).filter(Boolean);
|
|
145
145
|
if (c.length) return c.join(" ");
|
|
146
146
|
}
|
|
147
|
-
const e = (
|
|
147
|
+
const e = (o = t.getAttribute("aria-label")) == null ? void 0 : o.trim();
|
|
148
148
|
if (e) return e;
|
|
149
149
|
if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement) {
|
|
150
150
|
if (t.id) {
|
|
@@ -154,7 +154,7 @@ function Ee(t) {
|
|
|
154
154
|
const c = t.closest("label"), d = c ? A(c).trim() : "";
|
|
155
155
|
if (d) return d;
|
|
156
156
|
}
|
|
157
|
-
const n = (
|
|
157
|
+
const n = (r = t.getAttribute("title")) == null ? void 0 : r.trim();
|
|
158
158
|
if (n) return n;
|
|
159
159
|
if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) {
|
|
160
160
|
const c = (s = t.getAttribute("placeholder")) == null ? void 0 : s.trim();
|
|
@@ -181,7 +181,7 @@ function Ee(t) {
|
|
|
181
181
|
}
|
|
182
182
|
return t instanceof HTMLImageElement || t instanceof HTMLAreaElement ? ((l = t.alt) == null ? void 0 : l.trim()) ?? "" : t instanceof HTMLInputElement && t.type === "image" ? ((p = t.alt) == null ? void 0 : p.trim()) ?? "" : "";
|
|
183
183
|
}
|
|
184
|
-
const
|
|
184
|
+
const Ce = /* @__PURE__ */ new Set([
|
|
185
185
|
"alert",
|
|
186
186
|
"alertdialog",
|
|
187
187
|
"application",
|
|
@@ -265,29 +265,29 @@ const Te = /* @__PURE__ */ new Set([
|
|
|
265
265
|
"treegrid",
|
|
266
266
|
"treeitem"
|
|
267
267
|
]);
|
|
268
|
-
function
|
|
268
|
+
function Ne(t) {
|
|
269
269
|
const a = t.trim().toLowerCase().replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g, "");
|
|
270
|
-
return
|
|
270
|
+
return Ce.has(a);
|
|
271
271
|
}
|
|
272
272
|
function q(t) {
|
|
273
273
|
let a = t;
|
|
274
274
|
for (; a; ) {
|
|
275
|
-
if (
|
|
275
|
+
if (ye(a)) return !0;
|
|
276
276
|
a = a.parentElement;
|
|
277
277
|
}
|
|
278
278
|
return !1;
|
|
279
279
|
}
|
|
280
|
-
let
|
|
281
|
-
function
|
|
282
|
-
|
|
280
|
+
let O = /* @__PURE__ */ new WeakMap();
|
|
281
|
+
function Me() {
|
|
282
|
+
O = /* @__PURE__ */ new WeakMap();
|
|
283
283
|
}
|
|
284
284
|
function b(t) {
|
|
285
|
-
const a =
|
|
285
|
+
const a = O.get(t);
|
|
286
286
|
if (a !== void 0) return a;
|
|
287
287
|
let e;
|
|
288
|
-
return t.getAttribute("aria-hidden") === "true" || t instanceof HTMLElement && (t.hidden || t.style.display === "none") ? e = !0 : t.parentElement ? e = b(t.parentElement) : e = !1,
|
|
288
|
+
return t.getAttribute("aria-hidden") === "true" || t instanceof HTMLElement && (t.hidden || t.style.display === "none") ? e = !0 : t.parentElement ? e = b(t.parentElement) : e = !1, O.set(t, e), e;
|
|
289
289
|
}
|
|
290
|
-
function
|
|
290
|
+
function ye(t) {
|
|
291
291
|
if (t.getAttribute("aria-hidden") === "true" || t instanceof HTMLElement && t.hidden) return !0;
|
|
292
292
|
if (typeof getComputedStyle == "function") {
|
|
293
293
|
const a = getComputedStyle(t);
|
|
@@ -297,14 +297,14 @@ function fe(t) {
|
|
|
297
297
|
return !1;
|
|
298
298
|
}
|
|
299
299
|
function A(t) {
|
|
300
|
-
var e, n, i,
|
|
300
|
+
var e, n, i, o, r;
|
|
301
301
|
let a = "";
|
|
302
302
|
for (const s of t.childNodes)
|
|
303
303
|
if (s.nodeType === 3)
|
|
304
304
|
a += s.textContent ?? "";
|
|
305
305
|
else if (s.nodeType === 1) {
|
|
306
306
|
const l = s;
|
|
307
|
-
if (!
|
|
307
|
+
if (!ye(l)) {
|
|
308
308
|
const p = (e = l.tagName) == null ? void 0 : e.toLowerCase();
|
|
309
309
|
if (p === "img" || p === "area") {
|
|
310
310
|
const c = l.getAttribute("aria-labelledby");
|
|
@@ -320,26 +320,26 @@ function A(t) {
|
|
|
320
320
|
}
|
|
321
321
|
a += ((n = l.getAttribute("aria-label")) == null ? void 0 : n.trim()) ?? l.getAttribute("alt") ?? ((i = l.getAttribute("title")) == null ? void 0 : i.trim()) ?? "";
|
|
322
322
|
} else if (p === "svg") {
|
|
323
|
-
const c = (
|
|
323
|
+
const c = (o = l.getAttribute("aria-label")) == null ? void 0 : o.trim();
|
|
324
324
|
if (c)
|
|
325
325
|
a += c;
|
|
326
326
|
else {
|
|
327
327
|
const d = l.querySelector("title");
|
|
328
328
|
d && (a += d.textContent ?? "");
|
|
329
329
|
}
|
|
330
|
-
} else (
|
|
330
|
+
} else (r = l.getAttribute("aria-label")) != null && r.trim() ? a += l.getAttribute("aria-label").trim() : a += A(l);
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
return a;
|
|
334
334
|
}
|
|
335
|
-
let
|
|
336
|
-
function
|
|
337
|
-
|
|
335
|
+
let V = /* @__PURE__ */ new WeakMap();
|
|
336
|
+
function De() {
|
|
337
|
+
V = /* @__PURE__ */ new WeakMap();
|
|
338
338
|
}
|
|
339
|
-
function
|
|
339
|
+
function $e(t) {
|
|
340
340
|
return t.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
341
341
|
}
|
|
342
|
-
const
|
|
342
|
+
const He = [
|
|
343
343
|
"data-testid",
|
|
344
344
|
"data-test-id",
|
|
345
345
|
"data-cy",
|
|
@@ -349,18 +349,18 @@ const $e = [
|
|
|
349
349
|
"for",
|
|
350
350
|
"aria-label"
|
|
351
351
|
];
|
|
352
|
-
function
|
|
352
|
+
function Pe(t) {
|
|
353
353
|
const a = t.tagName.toLowerCase();
|
|
354
|
-
for (const n of
|
|
354
|
+
for (const n of He) {
|
|
355
355
|
const i = t.getAttribute(n);
|
|
356
356
|
if (i != null && i.length > 0 && i.length < 100)
|
|
357
|
-
return `${a}[${n}="${
|
|
357
|
+
return `${a}[${n}="${$e(i)}"]`;
|
|
358
358
|
}
|
|
359
359
|
const e = t.parentElement;
|
|
360
360
|
if (e) {
|
|
361
361
|
let n = 0, i = 0;
|
|
362
|
-
for (let
|
|
363
|
-
e.children[
|
|
362
|
+
for (let o = 0; o < e.children.length; o++)
|
|
363
|
+
e.children[o].tagName === t.tagName && (n++, e.children[o] === t && (i = n));
|
|
364
364
|
if (n > 1)
|
|
365
365
|
return `${a}:nth-of-type(${i})`;
|
|
366
366
|
}
|
|
@@ -375,11 +375,11 @@ function z(t) {
|
|
|
375
375
|
n.unshift(`#${CSS.escape(i.id)}`);
|
|
376
376
|
break;
|
|
377
377
|
}
|
|
378
|
-
if (n.unshift(
|
|
379
|
-
const
|
|
378
|
+
if (n.unshift(Pe(i)), n.length >= 2) {
|
|
379
|
+
const o = n.join(" > ");
|
|
380
380
|
try {
|
|
381
|
-
const
|
|
382
|
-
if (
|
|
381
|
+
const r = a.querySelectorAll(o);
|
|
382
|
+
if (r.length === 1 && r[0] === t) return o;
|
|
383
383
|
} catch {
|
|
384
384
|
}
|
|
385
385
|
}
|
|
@@ -388,17 +388,17 @@ function z(t) {
|
|
|
388
388
|
return n.join(" > ");
|
|
389
389
|
}
|
|
390
390
|
function m(t) {
|
|
391
|
-
var
|
|
392
|
-
const a =
|
|
391
|
+
var o;
|
|
392
|
+
const a = V.get(t);
|
|
393
393
|
if (a !== void 0) return a;
|
|
394
394
|
const e = [];
|
|
395
395
|
let n = t;
|
|
396
396
|
for (; n; ) {
|
|
397
|
-
const
|
|
398
|
-
if (
|
|
399
|
-
e.unshift({ selector: z(n), delimiter: " >>> " }), n =
|
|
397
|
+
const r = n.getRootNode();
|
|
398
|
+
if (r instanceof ShadowRoot)
|
|
399
|
+
e.unshift({ selector: z(n), delimiter: " >>> " }), n = r.host;
|
|
400
400
|
else {
|
|
401
|
-
const s = (
|
|
401
|
+
const s = (o = r.defaultView) == null ? void 0 : o.frameElement;
|
|
402
402
|
if (s)
|
|
403
403
|
e.unshift({ selector: z(n), delimiter: " >>>iframe> " }), n = s;
|
|
404
404
|
else {
|
|
@@ -407,39 +407,39 @@ function m(t) {
|
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
|
-
const i = e.map((
|
|
411
|
-
return
|
|
410
|
+
const i = e.map((r, s) => (s === 0 ? "" : r.delimiter) + r.selector).join("");
|
|
411
|
+
return V.set(t, i), i;
|
|
412
412
|
}
|
|
413
|
-
function
|
|
413
|
+
function Un(t) {
|
|
414
414
|
const a = [], e = [];
|
|
415
415
|
let n = t;
|
|
416
416
|
for (; n; ) {
|
|
417
|
-
const
|
|
418
|
-
if (
|
|
419
|
-
a.push(n.slice(0,
|
|
420
|
-
else if (
|
|
421
|
-
a.push(n.slice(0,
|
|
417
|
+
const o = n.indexOf(" >>>iframe> "), r = n.indexOf(" >>> ");
|
|
418
|
+
if (o !== -1 && (r === -1 || o <= r))
|
|
419
|
+
a.push(n.slice(0, o).trim()), e.push("iframe"), n = n.slice(o + 12);
|
|
420
|
+
else if (r !== -1)
|
|
421
|
+
a.push(n.slice(0, r).trim()), e.push("shadow"), n = n.slice(r + 5);
|
|
422
422
|
else {
|
|
423
423
|
a.push(n.trim());
|
|
424
424
|
break;
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
427
|
let i = document;
|
|
428
|
-
for (let
|
|
429
|
-
const
|
|
430
|
-
if (!
|
|
431
|
-
if (
|
|
432
|
-
if (e[
|
|
433
|
-
const s =
|
|
428
|
+
for (let o = 0; o < a.length; o++) {
|
|
429
|
+
const r = i.querySelector(a[o]);
|
|
430
|
+
if (!r) return null;
|
|
431
|
+
if (o < a.length - 1)
|
|
432
|
+
if (e[o] === "iframe") {
|
|
433
|
+
const s = r.contentDocument;
|
|
434
434
|
if (!s) return null;
|
|
435
435
|
i = s;
|
|
436
436
|
} else {
|
|
437
|
-
const s =
|
|
437
|
+
const s = r.shadowRoot;
|
|
438
438
|
if (!s) return null;
|
|
439
439
|
i = s;
|
|
440
440
|
}
|
|
441
441
|
else
|
|
442
|
-
return
|
|
442
|
+
return r;
|
|
443
443
|
}
|
|
444
444
|
return null;
|
|
445
445
|
}
|
|
@@ -447,7 +447,7 @@ function u(t) {
|
|
|
447
447
|
const a = t.outerHTML;
|
|
448
448
|
return a.length > 200 ? a.slice(0, 200) + "..." : a;
|
|
449
449
|
}
|
|
450
|
-
const
|
|
450
|
+
const ze = /* @__PURE__ */ new Set([
|
|
451
451
|
"aria-activedescendant",
|
|
452
452
|
"aria-atomic",
|
|
453
453
|
"aria-autocomplete",
|
|
@@ -501,7 +501,7 @@ const De = /* @__PURE__ */ new Set([
|
|
|
501
501
|
"aria-valuemin",
|
|
502
502
|
"aria-valuenow",
|
|
503
503
|
"aria-valuetext"
|
|
504
|
-
]),
|
|
504
|
+
]), ee = /* @__PURE__ */ new Set([
|
|
505
505
|
"aria-atomic",
|
|
506
506
|
"aria-busy",
|
|
507
507
|
"aria-disabled",
|
|
@@ -512,7 +512,7 @@ const De = /* @__PURE__ */ new Set([
|
|
|
512
512
|
"aria-multiselectable",
|
|
513
513
|
"aria-readonly",
|
|
514
514
|
"aria-required"
|
|
515
|
-
]),
|
|
515
|
+
]), te = /* @__PURE__ */ new Set(["aria-checked", "aria-pressed"]), Fe = /* @__PURE__ */ new Set([
|
|
516
516
|
"aria-colcount",
|
|
517
517
|
"aria-colindex",
|
|
518
518
|
"aria-colspan",
|
|
@@ -526,7 +526,7 @@ const De = /* @__PURE__ */ new Set([
|
|
|
526
526
|
"aria-valuemax",
|
|
527
527
|
"aria-valuemin",
|
|
528
528
|
"aria-valuenow"
|
|
529
|
-
]),
|
|
529
|
+
]), ae = {
|
|
530
530
|
"aria-autocomplete": /* @__PURE__ */ new Set(["inline", "list", "both", "none"]),
|
|
531
531
|
"aria-expanded": /* @__PURE__ */ new Set(["true", "false", "undefined"]),
|
|
532
532
|
"aria-current": /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"]),
|
|
@@ -537,7 +537,7 @@ const De = /* @__PURE__ */ new Set([
|
|
|
537
537
|
"aria-orientation": /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]),
|
|
538
538
|
"aria-relevant": /* @__PURE__ */ new Set(["additions", "all", "removals", "text"]),
|
|
539
539
|
"aria-sort": /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"])
|
|
540
|
-
},
|
|
540
|
+
}, ne = /* @__PURE__ */ new Set([
|
|
541
541
|
"caption",
|
|
542
542
|
"code",
|
|
543
543
|
"deletion",
|
|
@@ -582,7 +582,7 @@ const De = /* @__PURE__ */ new Set([
|
|
|
582
582
|
u: !0,
|
|
583
583
|
var: !0,
|
|
584
584
|
wbr: !0
|
|
585
|
-
},
|
|
585
|
+
}, Ue = {
|
|
586
586
|
alert: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
|
|
587
587
|
article: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
|
|
588
588
|
banner: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
|
|
@@ -615,26 +615,26 @@ const De = /* @__PURE__ */ new Set([
|
|
|
615
615
|
time: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
|
|
616
616
|
tooltip: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"])
|
|
617
617
|
};
|
|
618
|
-
let
|
|
619
|
-
function
|
|
620
|
-
|
|
618
|
+
let T = null, R = null;
|
|
619
|
+
function Be() {
|
|
620
|
+
T = null, R = null;
|
|
621
621
|
}
|
|
622
|
-
function
|
|
622
|
+
function J(t) {
|
|
623
623
|
var i;
|
|
624
|
-
if (
|
|
624
|
+
if (R && (T == null ? void 0 : T.deref()) === t) return R;
|
|
625
625
|
const a = [], e = [], n = [];
|
|
626
|
-
for (const
|
|
627
|
-
let
|
|
628
|
-
for (const c of
|
|
626
|
+
for (const o of t.querySelectorAll("*")) {
|
|
627
|
+
let r = !1;
|
|
628
|
+
for (const c of o.attributes)
|
|
629
629
|
if (c.name.startsWith("aria-")) {
|
|
630
|
-
|
|
630
|
+
r = !0;
|
|
631
631
|
break;
|
|
632
632
|
}
|
|
633
|
-
if (!
|
|
633
|
+
if (!r) continue;
|
|
634
634
|
let s, l;
|
|
635
|
-
const p = () => (s === void 0 && (s = m(
|
|
636
|
-
for (const c of
|
|
637
|
-
if (c.name.startsWith("aria-") && !
|
|
635
|
+
const p = () => (s === void 0 && (s = m(o), l = u(o)), { selector: s, html: l });
|
|
636
|
+
for (const c of o.attributes)
|
|
637
|
+
if (c.name.startsWith("aria-") && !ze.has(c.name)) {
|
|
638
638
|
const d = p();
|
|
639
639
|
a.push({
|
|
640
640
|
ruleId: "aria-valid-attr",
|
|
@@ -645,11 +645,11 @@ function X(t) {
|
|
|
645
645
|
});
|
|
646
646
|
break;
|
|
647
647
|
}
|
|
648
|
-
for (const c of
|
|
648
|
+
for (const c of o.attributes) {
|
|
649
649
|
if (!c.name.startsWith("aria-")) continue;
|
|
650
650
|
const d = c.value.trim();
|
|
651
|
-
if (!(d === "" && !
|
|
652
|
-
if (
|
|
651
|
+
if (!(d === "" && !ee.has(c.name) && !te.has(c.name))) {
|
|
652
|
+
if (ee.has(c.name)) {
|
|
653
653
|
if (d !== "true" && d !== "false") {
|
|
654
654
|
const h = p();
|
|
655
655
|
e.push({
|
|
@@ -660,7 +660,7 @@ function X(t) {
|
|
|
660
660
|
message: `${c.name} must be "true" or "false", got "${d}".`
|
|
661
661
|
});
|
|
662
662
|
}
|
|
663
|
-
} else if (
|
|
663
|
+
} else if (te.has(c.name)) {
|
|
664
664
|
if (d !== "true" && d !== "false" && d !== "mixed") {
|
|
665
665
|
const h = p();
|
|
666
666
|
e.push({
|
|
@@ -671,7 +671,7 @@ function X(t) {
|
|
|
671
671
|
message: `${c.name} must be "true", "false", or "mixed", got "${d}".`
|
|
672
672
|
});
|
|
673
673
|
}
|
|
674
|
-
} else if (
|
|
674
|
+
} else if (Fe.has(c.name)) {
|
|
675
675
|
if (d === "" || !/^-?\d+$/.test(d)) {
|
|
676
676
|
const h = p();
|
|
677
677
|
e.push({
|
|
@@ -693,10 +693,10 @@ function X(t) {
|
|
|
693
693
|
message: `${c.name} must be a number, got "${d}".`
|
|
694
694
|
});
|
|
695
695
|
}
|
|
696
|
-
} else if (
|
|
696
|
+
} else if (ae[c.name]) {
|
|
697
697
|
const h = d.split(/\s+/);
|
|
698
698
|
for (const g of h)
|
|
699
|
-
if (!
|
|
699
|
+
if (!ae[c.name].has(g)) {
|
|
700
700
|
const f = p();
|
|
701
701
|
e.push({
|
|
702
702
|
ruleId: "aria-valid-attr-value",
|
|
@@ -710,10 +710,10 @@ function X(t) {
|
|
|
710
710
|
}
|
|
711
711
|
}
|
|
712
712
|
}
|
|
713
|
-
if (!b(
|
|
714
|
-
const c = (i =
|
|
713
|
+
if (!b(o)) {
|
|
714
|
+
const c = (i = o.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase(), d = o.tagName.toLowerCase();
|
|
715
715
|
if (!c && We[d]) {
|
|
716
|
-
const h =
|
|
716
|
+
const h = o.hasAttribute("aria-label"), g = o.hasAttribute("aria-labelledby");
|
|
717
717
|
if (h || g) {
|
|
718
718
|
const f = p();
|
|
719
719
|
n.push({
|
|
@@ -725,8 +725,8 @@ function X(t) {
|
|
|
725
725
|
});
|
|
726
726
|
}
|
|
727
727
|
} else if (c) {
|
|
728
|
-
if (
|
|
729
|
-
const g =
|
|
728
|
+
if (ne.has(c)) {
|
|
729
|
+
const g = o.hasAttribute("aria-label"), f = o.hasAttribute("aria-labelledby");
|
|
730
730
|
if (g || f) {
|
|
731
731
|
const y = p();
|
|
732
732
|
n.push({
|
|
@@ -738,11 +738,11 @@ function X(t) {
|
|
|
738
738
|
});
|
|
739
739
|
}
|
|
740
740
|
}
|
|
741
|
-
const h =
|
|
741
|
+
const h = Ue[c];
|
|
742
742
|
if (h) {
|
|
743
|
-
for (const g of
|
|
743
|
+
for (const g of o.attributes)
|
|
744
744
|
if (g.name.startsWith("aria-") && h.has(g.name)) {
|
|
745
|
-
if ((g.name === "aria-label" || g.name === "aria-labelledby") &&
|
|
745
|
+
if ((g.name === "aria-label" || g.name === "aria-labelledby") && ne.has(c))
|
|
746
746
|
continue;
|
|
747
747
|
const f = p();
|
|
748
748
|
n.push({
|
|
@@ -757,28 +757,28 @@ function X(t) {
|
|
|
757
757
|
}
|
|
758
758
|
}
|
|
759
759
|
}
|
|
760
|
-
return
|
|
760
|
+
return T = new WeakRef(t), R = { validAttr: a, validAttrValue: e, prohibitedAttr: n }, R;
|
|
761
761
|
}
|
|
762
|
-
let _ = /* @__PURE__ */ new WeakMap(),
|
|
762
|
+
let _ = /* @__PURE__ */ new WeakMap(), G = /* @__PURE__ */ new WeakMap(), Y = /* @__PURE__ */ new WeakMap();
|
|
763
763
|
function Oe() {
|
|
764
|
-
_ = /* @__PURE__ */ new WeakMap(),
|
|
764
|
+
_ = /* @__PURE__ */ new WeakMap(), G = /* @__PURE__ */ new WeakMap(), Y = /* @__PURE__ */ new WeakMap();
|
|
765
765
|
}
|
|
766
766
|
function w(t) {
|
|
767
767
|
let a = _.get(t);
|
|
768
768
|
return a || (a = getComputedStyle(t), _.set(t, a), a);
|
|
769
769
|
}
|
|
770
|
-
function
|
|
771
|
-
const [n, i,
|
|
772
|
-
const s =
|
|
770
|
+
function M(t, a, e) {
|
|
771
|
+
const [n, i, o] = [t, a, e].map((r) => {
|
|
772
|
+
const s = r / 255;
|
|
773
773
|
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
774
774
|
});
|
|
775
|
-
return 0.2126 * n + 0.7152 * i + 0.0722 *
|
|
775
|
+
return 0.2126 * n + 0.7152 * i + 0.0722 * o;
|
|
776
776
|
}
|
|
777
|
-
function
|
|
777
|
+
function we(t, a) {
|
|
778
778
|
const e = Math.max(t, a), n = Math.min(t, a);
|
|
779
779
|
return (e + 0.05) / (n + 0.05);
|
|
780
780
|
}
|
|
781
|
-
const
|
|
781
|
+
const ie = {
|
|
782
782
|
black: [0, 0, 0],
|
|
783
783
|
white: [255, 255, 255],
|
|
784
784
|
red: [255, 0, 0],
|
|
@@ -798,9 +798,9 @@ const ae = {
|
|
|
798
798
|
lime: [0, 255, 0],
|
|
799
799
|
olive: [128, 128, 0]
|
|
800
800
|
};
|
|
801
|
-
function
|
|
801
|
+
function D(t) {
|
|
802
802
|
const a = t.trim().toLowerCase();
|
|
803
|
-
if (
|
|
803
|
+
if (ie[a]) return ie[a];
|
|
804
804
|
const e = a.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);
|
|
805
805
|
if (e)
|
|
806
806
|
return [parseInt(e[1] + e[1], 16), parseInt(e[2] + e[2], 16), parseInt(e[3] + e[3], 16)];
|
|
@@ -812,18 +812,18 @@ function $(t) {
|
|
|
812
812
|
);
|
|
813
813
|
if (i)
|
|
814
814
|
return [parseInt(i[1]), parseInt(i[2]), parseInt(i[3])];
|
|
815
|
-
const
|
|
815
|
+
const o = t.match(
|
|
816
816
|
/rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)\s*(?:\/\s*[\d.]+%?)?\s*\)/
|
|
817
817
|
);
|
|
818
|
-
return
|
|
818
|
+
return o ? [parseInt(o[1]), parseInt(o[2]), parseInt(o[3])] : null;
|
|
819
819
|
}
|
|
820
|
-
function
|
|
821
|
-
const a =
|
|
820
|
+
function Ve(t) {
|
|
821
|
+
const a = G.get(t);
|
|
822
822
|
if (a !== void 0) return a;
|
|
823
|
-
const e =
|
|
824
|
-
return
|
|
823
|
+
const e = _e(t);
|
|
824
|
+
return G.set(t, e), e;
|
|
825
825
|
}
|
|
826
|
-
function
|
|
826
|
+
function _e(t) {
|
|
827
827
|
let a = t;
|
|
828
828
|
for (; a; ) {
|
|
829
829
|
const e = w(a), n = e.backgroundImage;
|
|
@@ -833,32 +833,32 @@ function Ue(t) {
|
|
|
833
833
|
a = a.parentElement;
|
|
834
834
|
continue;
|
|
835
835
|
}
|
|
836
|
-
const
|
|
837
|
-
if (
|
|
836
|
+
const o = i.match(/rgba\(.+?,\s*([\d.]+)\s*\)/) || i.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);
|
|
837
|
+
if (o && (o[1].endsWith("%") ? parseFloat(o[1]) / 100 : parseFloat(o[1])) < 0.1) {
|
|
838
838
|
a = a.parentElement;
|
|
839
839
|
continue;
|
|
840
840
|
}
|
|
841
|
-
return
|
|
841
|
+
return D(i);
|
|
842
842
|
}
|
|
843
843
|
return [255, 255, 255];
|
|
844
844
|
}
|
|
845
|
-
const
|
|
846
|
-
function
|
|
847
|
-
const a =
|
|
845
|
+
const Ge = /* @__PURE__ */ new Set(["IMG", "PICTURE", "VIDEO", "SVG"]);
|
|
846
|
+
function Ye(t) {
|
|
847
|
+
const a = Y.get(t);
|
|
848
848
|
if (a !== void 0) return a;
|
|
849
|
-
const e =
|
|
850
|
-
return
|
|
849
|
+
const e = Xe(t);
|
|
850
|
+
return Y.set(t, e), e;
|
|
851
851
|
}
|
|
852
|
-
function
|
|
852
|
+
function Xe(t) {
|
|
853
853
|
let a = t, e = !1;
|
|
854
854
|
for (; a; ) {
|
|
855
855
|
const n = w(a).position;
|
|
856
856
|
if ((n === "absolute" || n === "fixed") && (e = !0), a !== t && n !== "static") {
|
|
857
857
|
for (const i of a.children)
|
|
858
|
-
if (!(i === t || i.contains(t)) &&
|
|
858
|
+
if (!(i === t || i.contains(t)) && Ge.has(i.tagName)) {
|
|
859
859
|
if (e) return !0;
|
|
860
|
-
const
|
|
861
|
-
if (
|
|
860
|
+
const o = w(i).position;
|
|
861
|
+
if (o === "absolute" || o === "fixed") return !0;
|
|
862
862
|
}
|
|
863
863
|
if (e) break;
|
|
864
864
|
}
|
|
@@ -866,20 +866,20 @@ function Ge(t) {
|
|
|
866
866
|
}
|
|
867
867
|
return !1;
|
|
868
868
|
}
|
|
869
|
-
function
|
|
869
|
+
function Ke(t) {
|
|
870
870
|
const a = parseFloat(t);
|
|
871
871
|
return t.endsWith("pt") ? a * (4 / 3) : a;
|
|
872
872
|
}
|
|
873
|
-
function
|
|
874
|
-
const a = w(t), e =
|
|
873
|
+
function Je(t) {
|
|
874
|
+
const a = w(t), e = Ke(a.fontSize), n = parseInt(a.fontWeight) || (a.fontWeight === "bold" ? 700 : 400);
|
|
875
875
|
return e >= 23.5 || e >= 18.5 && n >= 700;
|
|
876
876
|
}
|
|
877
|
-
const
|
|
878
|
-
function
|
|
879
|
-
|
|
877
|
+
const $ = /* @__PURE__ */ new Map();
|
|
878
|
+
function Bn(t, a) {
|
|
879
|
+
$.set(t, a), X.delete(t);
|
|
880
880
|
}
|
|
881
|
-
function
|
|
882
|
-
const e =
|
|
881
|
+
function Qe(t, a) {
|
|
882
|
+
const e = $.get(a);
|
|
883
883
|
return e ? t.map((n) => {
|
|
884
884
|
const i = e[n.id];
|
|
885
885
|
return i ? {
|
|
@@ -889,8 +889,54 @@ function Ke(t, a) {
|
|
|
889
889
|
} : n;
|
|
890
890
|
}) : t;
|
|
891
891
|
}
|
|
892
|
-
|
|
893
|
-
|
|
892
|
+
const X = /* @__PURE__ */ new Map();
|
|
893
|
+
function Ze(t) {
|
|
894
|
+
return t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
895
|
+
}
|
|
896
|
+
function et(t) {
|
|
897
|
+
const a = t.split(/\{(\d+)\}/);
|
|
898
|
+
let e = "^";
|
|
899
|
+
for (let n = 0; n < a.length; n++)
|
|
900
|
+
n % 2 === 0 ? e += Ze(a[n]) : e += "(.+?)";
|
|
901
|
+
return e += "$", new RegExp(e);
|
|
902
|
+
}
|
|
903
|
+
function tt(t, a) {
|
|
904
|
+
let e = X.get(t);
|
|
905
|
+
if (e || (e = /* @__PURE__ */ new Map(), X.set(t, e)), e.has(a)) return e.get(a);
|
|
906
|
+
const n = $.get(t);
|
|
907
|
+
if (!n) return;
|
|
908
|
+
const i = n[a];
|
|
909
|
+
if (!(i != null && i.messages))
|
|
910
|
+
return e.set(a, []), [];
|
|
911
|
+
const o = [];
|
|
912
|
+
for (const [r, s] of Object.entries(i.messages))
|
|
913
|
+
o.push({
|
|
914
|
+
regex: et(r),
|
|
915
|
+
translated: s
|
|
916
|
+
});
|
|
917
|
+
return e.set(a, o), o;
|
|
918
|
+
}
|
|
919
|
+
function at(t, a, e) {
|
|
920
|
+
const n = tt(e, t);
|
|
921
|
+
if (!n) return a;
|
|
922
|
+
for (const { regex: i, translated: o } of n) {
|
|
923
|
+
const r = a.match(i);
|
|
924
|
+
if (r)
|
|
925
|
+
return o.replace(/\{(\d+)\}/g, (s, l) => {
|
|
926
|
+
const p = parseInt(l, 10);
|
|
927
|
+
return p + 1 < r.length ? r[p + 1] : `{${l}}`;
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
return a;
|
|
931
|
+
}
|
|
932
|
+
function Ae(t, a) {
|
|
933
|
+
return $.has(a) ? t.map((e) => {
|
|
934
|
+
const n = at(e.ruleId, e.message, a);
|
|
935
|
+
return n === e.message ? e : { ...e, message: n };
|
|
936
|
+
}) : t;
|
|
937
|
+
}
|
|
938
|
+
function F(t) {
|
|
939
|
+
var o, r;
|
|
894
940
|
const a = [], e = t.closest("a");
|
|
895
941
|
if (e) {
|
|
896
942
|
const s = e.getAttribute("href");
|
|
@@ -899,17 +945,17 @@ function j(t) {
|
|
|
899
945
|
const n = t.closest("figure");
|
|
900
946
|
if (n) {
|
|
901
947
|
const s = n.querySelector("figcaption");
|
|
902
|
-
(
|
|
948
|
+
(o = s == null ? void 0 : s.textContent) != null && o.trim() && a.push(`Figcaption: ${s.textContent.trim().slice(0, 100)}`);
|
|
903
949
|
}
|
|
904
950
|
const i = t.parentElement;
|
|
905
951
|
if (i && i !== e) {
|
|
906
|
-
const s = t instanceof HTMLImageElement && t.alt || "", l = (
|
|
952
|
+
const s = t instanceof HTMLImageElement && t.alt || "", l = (r = i.textContent) == null ? void 0 : r.replace(s, "").trim().slice(0, 100);
|
|
907
953
|
l && a.push(`Adjacent text: ${l}`);
|
|
908
954
|
}
|
|
909
955
|
return a.length > 0 ? a.join(`
|
|
910
956
|
`) : void 0;
|
|
911
957
|
}
|
|
912
|
-
function
|
|
958
|
+
function oe(t) {
|
|
913
959
|
let a = t;
|
|
914
960
|
for (; a; ) {
|
|
915
961
|
if (a instanceof HTMLElement && a.style.visibility === "hidden") return !0;
|
|
@@ -917,7 +963,7 @@ function ne(t) {
|
|
|
917
963
|
}
|
|
918
964
|
return !1;
|
|
919
965
|
}
|
|
920
|
-
const
|
|
966
|
+
const nt = {
|
|
921
967
|
id: "img-alt",
|
|
922
968
|
wcag: ["1.1.1"],
|
|
923
969
|
level: "A",
|
|
@@ -927,11 +973,11 @@ const Je = {
|
|
|
927
973
|
run(t) {
|
|
928
974
|
const a = [];
|
|
929
975
|
for (const e of t.querySelectorAll("img")) {
|
|
930
|
-
if (b(e) ||
|
|
976
|
+
if (b(e) || oe(e)) continue;
|
|
931
977
|
const n = e.getAttribute("role");
|
|
932
978
|
if (n === "presentation" || n === "none") {
|
|
933
|
-
const
|
|
934
|
-
if (!
|
|
979
|
+
const o = e.getAttribute("tabindex");
|
|
980
|
+
if (!o || o === "-1") continue;
|
|
935
981
|
}
|
|
936
982
|
const i = e.getAttribute("alt");
|
|
937
983
|
if (i !== null && i.trim() === "" && i !== "") {
|
|
@@ -941,7 +987,7 @@ const Je = {
|
|
|
941
987
|
html: u(e),
|
|
942
988
|
impact: "critical",
|
|
943
989
|
message: 'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',
|
|
944
|
-
context:
|
|
990
|
+
context: F(e)
|
|
945
991
|
});
|
|
946
992
|
continue;
|
|
947
993
|
}
|
|
@@ -951,23 +997,23 @@ const Je = {
|
|
|
951
997
|
html: u(e),
|
|
952
998
|
impact: "critical",
|
|
953
999
|
message: "Image element missing alt attribute.",
|
|
954
|
-
context:
|
|
1000
|
+
context: F(e)
|
|
955
1001
|
});
|
|
956
1002
|
}
|
|
957
1003
|
for (const e of t.querySelectorAll('[role="img"]:not(img):not(svg)'))
|
|
958
|
-
b(e) ||
|
|
1004
|
+
b(e) || oe(e) || v(e) || a.push({
|
|
959
1005
|
ruleId: "img-alt",
|
|
960
1006
|
selector: m(e),
|
|
961
1007
|
html: u(e),
|
|
962
1008
|
impact: "critical",
|
|
963
1009
|
message: 'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.',
|
|
964
|
-
context:
|
|
1010
|
+
context: F(e)
|
|
965
1011
|
});
|
|
966
1012
|
return a;
|
|
967
1013
|
}
|
|
968
1014
|
};
|
|
969
|
-
function
|
|
970
|
-
var
|
|
1015
|
+
function it(t) {
|
|
1016
|
+
var o, r, s;
|
|
971
1017
|
const a = t.getAttribute("aria-labelledby");
|
|
972
1018
|
if (a) {
|
|
973
1019
|
const l = a.split(/\s+/).map((p) => {
|
|
@@ -976,14 +1022,14 @@ function Qe(t) {
|
|
|
976
1022
|
}).filter(Boolean);
|
|
977
1023
|
if (l.length) return l.join(" ");
|
|
978
1024
|
}
|
|
979
|
-
const e = (
|
|
1025
|
+
const e = (o = t.getAttribute("aria-label")) == null ? void 0 : o.trim();
|
|
980
1026
|
if (e) return e;
|
|
981
1027
|
const n = t.querySelector("title");
|
|
982
|
-
if ((
|
|
1028
|
+
if ((r = n == null ? void 0 : n.textContent) != null && r.trim()) return n.textContent.trim();
|
|
983
1029
|
const i = (s = t.getAttribute("title")) == null ? void 0 : s.trim();
|
|
984
1030
|
return i || "";
|
|
985
1031
|
}
|
|
986
|
-
const
|
|
1032
|
+
const ot = {
|
|
987
1033
|
id: "svg-img-alt",
|
|
988
1034
|
wcag: ["1.1.1"],
|
|
989
1035
|
level: "A",
|
|
@@ -994,20 +1040,20 @@ const Ze = {
|
|
|
994
1040
|
const a = [], e = 'svg[role="img"], [role="graphics-document"], [role="graphics-symbol"]';
|
|
995
1041
|
for (const n of t.querySelectorAll(e)) {
|
|
996
1042
|
if (b(n)) continue;
|
|
997
|
-
if (!
|
|
998
|
-
const
|
|
1043
|
+
if (!it(n)) {
|
|
1044
|
+
const o = n.getAttribute("role");
|
|
999
1045
|
a.push({
|
|
1000
1046
|
ruleId: "svg-img-alt",
|
|
1001
1047
|
selector: m(n),
|
|
1002
1048
|
html: u(n),
|
|
1003
1049
|
impact: "serious",
|
|
1004
|
-
message: `${n.tagName.toLowerCase()} with role='${
|
|
1050
|
+
message: `${n.tagName.toLowerCase()} with role='${o}' has no accessible name.`
|
|
1005
1051
|
});
|
|
1006
1052
|
}
|
|
1007
1053
|
}
|
|
1008
1054
|
return a;
|
|
1009
1055
|
}
|
|
1010
|
-
},
|
|
1056
|
+
}, rt = {
|
|
1011
1057
|
id: "input-image-alt",
|
|
1012
1058
|
wcag: ["1.1.1", "4.1.2"],
|
|
1013
1059
|
level: "A",
|
|
@@ -1026,7 +1072,7 @@ const Ze = {
|
|
|
1026
1072
|
});
|
|
1027
1073
|
return a;
|
|
1028
1074
|
}
|
|
1029
|
-
},
|
|
1075
|
+
}, st = {
|
|
1030
1076
|
id: "image-redundant-alt",
|
|
1031
1077
|
wcag: [],
|
|
1032
1078
|
level: "A",
|
|
@@ -1040,11 +1086,11 @@ const Ze = {
|
|
|
1040
1086
|
for (const n of t.querySelectorAll("img[alt]")) {
|
|
1041
1087
|
const i = n.getAttribute("alt").trim().toLowerCase();
|
|
1042
1088
|
if (!i) continue;
|
|
1043
|
-
const
|
|
1044
|
-
if (
|
|
1045
|
-
const
|
|
1046
|
-
if (
|
|
1047
|
-
const s =
|
|
1089
|
+
const o = n.closest("a, button");
|
|
1090
|
+
if (o) {
|
|
1091
|
+
const r = ((e = o.textContent) == null ? void 0 : e.trim().toLowerCase()) || "";
|
|
1092
|
+
if (r && r === i) {
|
|
1093
|
+
const s = o.tagName.toLowerCase(), l = o.getAttribute("href");
|
|
1048
1094
|
a.push({
|
|
1049
1095
|
ruleId: "image-redundant-alt",
|
|
1050
1096
|
selector: m(n),
|
|
@@ -1058,7 +1104,7 @@ const Ze = {
|
|
|
1058
1104
|
}
|
|
1059
1105
|
return a;
|
|
1060
1106
|
}
|
|
1061
|
-
},
|
|
1107
|
+
}, lt = ["image", "picture", "photo", "graphic", "icon", "img"], ct = {
|
|
1062
1108
|
id: "image-alt-redundant-words",
|
|
1063
1109
|
wcag: [],
|
|
1064
1110
|
level: "A",
|
|
@@ -1071,7 +1117,7 @@ const Ze = {
|
|
|
1071
1117
|
for (const e of t.querySelectorAll("img[alt]")) {
|
|
1072
1118
|
const n = e.getAttribute("alt").toLowerCase();
|
|
1073
1119
|
if (!n) continue;
|
|
1074
|
-
const i =
|
|
1120
|
+
const i = lt.filter((o) => n.split(/\s+/).includes(o));
|
|
1075
1121
|
i.length > 0 && a.push({
|
|
1076
1122
|
ruleId: "image-alt-redundant-words",
|
|
1077
1123
|
selector: m(e),
|
|
@@ -1083,7 +1129,7 @@ const Ze = {
|
|
|
1083
1129
|
}
|
|
1084
1130
|
return a;
|
|
1085
1131
|
}
|
|
1086
|
-
},
|
|
1132
|
+
}, dt = {
|
|
1087
1133
|
id: "area-alt",
|
|
1088
1134
|
wcag: ["1.1.1", "4.1.2"],
|
|
1089
1135
|
level: "A",
|
|
@@ -1105,22 +1151,22 @@ const Ze = {
|
|
|
1105
1151
|
return a;
|
|
1106
1152
|
}
|
|
1107
1153
|
};
|
|
1108
|
-
function
|
|
1109
|
-
var i,
|
|
1154
|
+
function ut(t) {
|
|
1155
|
+
var i, o;
|
|
1110
1156
|
const a = t.getAttribute("aria-labelledby");
|
|
1111
1157
|
if (a) {
|
|
1112
|
-
const
|
|
1158
|
+
const r = a.split(/\s+/).map((s) => {
|
|
1113
1159
|
var l, p;
|
|
1114
1160
|
return ((p = (l = t.ownerDocument.getElementById(s)) == null ? void 0 : l.textContent) == null ? void 0 : p.trim()) ?? "";
|
|
1115
1161
|
}).filter(Boolean);
|
|
1116
|
-
if (
|
|
1162
|
+
if (r.length) return r.join(" ");
|
|
1117
1163
|
}
|
|
1118
1164
|
const e = (i = t.getAttribute("aria-label")) == null ? void 0 : i.trim();
|
|
1119
1165
|
if (e) return e;
|
|
1120
|
-
const n = (
|
|
1166
|
+
const n = (o = t.getAttribute("title")) == null ? void 0 : o.trim();
|
|
1121
1167
|
return n || "";
|
|
1122
1168
|
}
|
|
1123
|
-
const
|
|
1169
|
+
const mt = {
|
|
1124
1170
|
id: "object-alt",
|
|
1125
1171
|
wcag: ["1.1.1"],
|
|
1126
1172
|
level: "A",
|
|
@@ -1132,17 +1178,17 @@ const ot = {
|
|
|
1132
1178
|
const a = [];
|
|
1133
1179
|
for (const n of t.querySelectorAll("object")) {
|
|
1134
1180
|
if (b(n) || n instanceof HTMLElement && n.style.visibility === "hidden") continue;
|
|
1135
|
-
let i = n.parentElement,
|
|
1181
|
+
let i = n.parentElement, o = !1;
|
|
1136
1182
|
for (; i; ) {
|
|
1137
1183
|
if (i instanceof HTMLElement && i.style.visibility === "hidden") {
|
|
1138
|
-
|
|
1184
|
+
o = !0;
|
|
1139
1185
|
break;
|
|
1140
1186
|
}
|
|
1141
1187
|
i = i.parentElement;
|
|
1142
1188
|
}
|
|
1143
|
-
if (
|
|
1144
|
-
const
|
|
1145
|
-
if (!((n.getAttribute("type") || "").startsWith("image/") || /\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(
|
|
1189
|
+
if (o || n.getAttribute("role") === "presentation" || n.getAttribute("role") === "none" || ut(n)) continue;
|
|
1190
|
+
const r = n.getAttribute("data") || "";
|
|
1191
|
+
if (!((n.getAttribute("type") || "").startsWith("image/") || /\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(r))) {
|
|
1146
1192
|
const p = n.querySelector("img[alt]");
|
|
1147
1193
|
if (p && ((e = p.getAttribute("alt")) != null && e.trim())) continue;
|
|
1148
1194
|
}
|
|
@@ -1156,7 +1202,7 @@ const ot = {
|
|
|
1156
1202
|
}
|
|
1157
1203
|
return a;
|
|
1158
1204
|
}
|
|
1159
|
-
},
|
|
1205
|
+
}, pt = {
|
|
1160
1206
|
id: "role-img-alt",
|
|
1161
1207
|
wcag: ["1.1.1"],
|
|
1162
1208
|
level: "A",
|
|
@@ -1178,7 +1224,7 @@ const ot = {
|
|
|
1178
1224
|
return a;
|
|
1179
1225
|
}
|
|
1180
1226
|
};
|
|
1181
|
-
function
|
|
1227
|
+
function On(t) {
|
|
1182
1228
|
if (typeof t != "object" || t === null)
|
|
1183
1229
|
return "Rule spec must be an object";
|
|
1184
1230
|
const a = t;
|
|
@@ -1208,10 +1254,10 @@ function jn(t) {
|
|
|
1208
1254
|
return "Rule must have a wcag array";
|
|
1209
1255
|
if (typeof a.level != "string" || !["A", "AA"].includes(a.level))
|
|
1210
1256
|
return "Rule must have level A or AA";
|
|
1211
|
-
const i =
|
|
1257
|
+
const i = ht(e);
|
|
1212
1258
|
return i || null;
|
|
1213
1259
|
}
|
|
1214
|
-
function
|
|
1260
|
+
function ht(t) {
|
|
1215
1261
|
switch (t.type) {
|
|
1216
1262
|
case "selector-exists":
|
|
1217
1263
|
return null;
|
|
@@ -1237,7 +1283,7 @@ function S(t, a, e) {
|
|
|
1237
1283
|
}
|
|
1238
1284
|
return n;
|
|
1239
1285
|
}
|
|
1240
|
-
function
|
|
1286
|
+
function E(t) {
|
|
1241
1287
|
const a = t.skipAriaHidden !== !1;
|
|
1242
1288
|
return {
|
|
1243
1289
|
id: t.id,
|
|
@@ -1248,27 +1294,27 @@ function I(t) {
|
|
|
1248
1294
|
guidance: t.guidance,
|
|
1249
1295
|
prompt: t.prompt,
|
|
1250
1296
|
run(e) {
|
|
1251
|
-
var i,
|
|
1297
|
+
var i, o;
|
|
1252
1298
|
const n = [];
|
|
1253
1299
|
switch (t.check.type) {
|
|
1254
1300
|
case "selector-exists": {
|
|
1255
|
-
for (const
|
|
1256
|
-
a && b(
|
|
1301
|
+
for (const r of e.querySelectorAll(t.selector))
|
|
1302
|
+
a && b(r) || n.push({
|
|
1257
1303
|
ruleId: t.id,
|
|
1258
|
-
selector: m(
|
|
1259
|
-
html: u(
|
|
1304
|
+
selector: m(r),
|
|
1305
|
+
html: u(r),
|
|
1260
1306
|
impact: t.impact,
|
|
1261
|
-
message: S(t.message,
|
|
1262
|
-
element:
|
|
1307
|
+
message: S(t.message, r, t.check),
|
|
1308
|
+
element: r
|
|
1263
1309
|
});
|
|
1264
1310
|
break;
|
|
1265
1311
|
}
|
|
1266
1312
|
case "attribute-value": {
|
|
1267
|
-
const { attribute:
|
|
1313
|
+
const { attribute: r, operator: s, value: l } = t.check;
|
|
1268
1314
|
for (const p of e.querySelectorAll(t.selector)) {
|
|
1269
1315
|
if (a && b(p)) continue;
|
|
1270
|
-
const c = p.getAttribute(
|
|
1271
|
-
c !== null &&
|
|
1316
|
+
const c = p.getAttribute(r);
|
|
1317
|
+
c !== null && bt(c, s, l) && n.push({
|
|
1272
1318
|
ruleId: t.id,
|
|
1273
1319
|
selector: m(p),
|
|
1274
1320
|
html: u(p),
|
|
@@ -1280,9 +1326,9 @@ function I(t) {
|
|
|
1280
1326
|
break;
|
|
1281
1327
|
}
|
|
1282
1328
|
case "attribute-missing": {
|
|
1283
|
-
const { attribute:
|
|
1329
|
+
const { attribute: r } = t.check;
|
|
1284
1330
|
for (const s of e.querySelectorAll(t.selector))
|
|
1285
|
-
a && b(s) || s.hasAttribute(
|
|
1331
|
+
a && b(s) || s.hasAttribute(r) || n.push({
|
|
1286
1332
|
ruleId: t.id,
|
|
1287
1333
|
selector: m(s),
|
|
1288
1334
|
html: u(s),
|
|
@@ -1293,7 +1339,7 @@ function I(t) {
|
|
|
1293
1339
|
break;
|
|
1294
1340
|
}
|
|
1295
1341
|
case "attribute-regex": {
|
|
1296
|
-
const { attribute:
|
|
1342
|
+
const { attribute: r, pattern: s, flags: l, shouldMatch: p } = t.check;
|
|
1297
1343
|
let c;
|
|
1298
1344
|
try {
|
|
1299
1345
|
c = new RegExp(s, l);
|
|
@@ -1302,7 +1348,7 @@ function I(t) {
|
|
|
1302
1348
|
}
|
|
1303
1349
|
for (const d of e.querySelectorAll(t.selector)) {
|
|
1304
1350
|
if (a && b(d)) continue;
|
|
1305
|
-
const h = d.getAttribute(
|
|
1351
|
+
const h = d.getAttribute(r);
|
|
1306
1352
|
if (h === null) continue;
|
|
1307
1353
|
const g = c.test(h);
|
|
1308
1354
|
p && !g ? n.push({
|
|
@@ -1324,9 +1370,9 @@ function I(t) {
|
|
|
1324
1370
|
break;
|
|
1325
1371
|
}
|
|
1326
1372
|
case "child-required": {
|
|
1327
|
-
const { childSelector:
|
|
1373
|
+
const { childSelector: r } = t.check;
|
|
1328
1374
|
for (const s of e.querySelectorAll(t.selector))
|
|
1329
|
-
a && b(s) || s.querySelector(
|
|
1375
|
+
a && b(s) || s.querySelector(r) || n.push({
|
|
1330
1376
|
ruleId: t.id,
|
|
1331
1377
|
selector: m(s),
|
|
1332
1378
|
html: u(s),
|
|
@@ -1337,7 +1383,7 @@ function I(t) {
|
|
|
1337
1383
|
break;
|
|
1338
1384
|
}
|
|
1339
1385
|
case "child-invalid": {
|
|
1340
|
-
const
|
|
1386
|
+
const r = new Set(
|
|
1341
1387
|
t.check.allowedChildren.map((l) => l.toLowerCase())
|
|
1342
1388
|
), s = t.check.allowedChildRoles ? new Set(t.check.allowedChildRoles.map((l) => l.toLowerCase())) : null;
|
|
1343
1389
|
for (const l of e.querySelectorAll(t.selector)) {
|
|
@@ -1363,8 +1409,8 @@ function I(t) {
|
|
|
1363
1409
|
}
|
|
1364
1410
|
if (!c)
|
|
1365
1411
|
for (const h of l.children) {
|
|
1366
|
-
if (
|
|
1367
|
-
const g = (
|
|
1412
|
+
if (r.has(h.tagName.toLowerCase())) continue;
|
|
1413
|
+
const g = (o = h.getAttribute("role")) == null ? void 0 : o.trim().toLowerCase();
|
|
1368
1414
|
if (!(g && (s != null && s.has(g))) && !(g === "presentation" || g === "none")) {
|
|
1369
1415
|
n.push({
|
|
1370
1416
|
ruleId: t.id,
|
|
@@ -1385,7 +1431,7 @@ function I(t) {
|
|
|
1385
1431
|
}
|
|
1386
1432
|
};
|
|
1387
1433
|
}
|
|
1388
|
-
function
|
|
1434
|
+
function bt(t, a, e) {
|
|
1389
1435
|
switch (a) {
|
|
1390
1436
|
case ">":
|
|
1391
1437
|
return parseFloat(t) > e;
|
|
@@ -1403,7 +1449,7 @@ function ct(t, a, e) {
|
|
|
1403
1449
|
return !1;
|
|
1404
1450
|
}
|
|
1405
1451
|
}
|
|
1406
|
-
const
|
|
1452
|
+
const gt = {
|
|
1407
1453
|
id: "server-side-image-map",
|
|
1408
1454
|
selector: "img[ismap], input[type='image'][ismap]",
|
|
1409
1455
|
check: { type: "selector-exists" },
|
|
@@ -1414,7 +1460,7 @@ const dt = {
|
|
|
1414
1460
|
level: "A",
|
|
1415
1461
|
guidance: "Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead.",
|
|
1416
1462
|
prompt: "Explain that the ismap attribute should be removed and the functionality replaced with a client-side <map> element with <area> children, or separate linked images/buttons."
|
|
1417
|
-
},
|
|
1463
|
+
}, ft = E(gt), vt = [
|
|
1418
1464
|
'[role="checkbox"]',
|
|
1419
1465
|
'[role="combobox"]',
|
|
1420
1466
|
'[role="listbox"]',
|
|
@@ -1426,13 +1472,13 @@ const dt = {
|
|
|
1426
1472
|
'[role="spinbutton"]',
|
|
1427
1473
|
'[role="switch"]',
|
|
1428
1474
|
'[role="textbox"]'
|
|
1429
|
-
].join(", "),
|
|
1475
|
+
].join(", "), yt = /* @__PURE__ */ new Set([
|
|
1430
1476
|
"checkbox",
|
|
1431
1477
|
"menuitemcheckbox",
|
|
1432
1478
|
"menuitemradio",
|
|
1433
1479
|
"radio",
|
|
1434
1480
|
"switch"
|
|
1435
|
-
]),
|
|
1481
|
+
]), wt = /* @__PURE__ */ new Set([
|
|
1436
1482
|
"combobox",
|
|
1437
1483
|
"listbox",
|
|
1438
1484
|
"searchbox",
|
|
@@ -1440,10 +1486,10 @@ const dt = {
|
|
|
1440
1486
|
"spinbutton",
|
|
1441
1487
|
"textbox"
|
|
1442
1488
|
]);
|
|
1443
|
-
function
|
|
1444
|
-
var
|
|
1445
|
-
const a = (
|
|
1446
|
-
if (a &&
|
|
1489
|
+
function At(t) {
|
|
1490
|
+
var r, s, l, p;
|
|
1491
|
+
const a = (r = t.getAttribute("role")) == null ? void 0 : r.trim().toLowerCase();
|
|
1492
|
+
if (a && yt.has(a) || (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) && !(a && wt.has(a)))
|
|
1447
1493
|
return v(t);
|
|
1448
1494
|
const n = t.getAttribute("aria-labelledby");
|
|
1449
1495
|
if (n) {
|
|
@@ -1469,15 +1515,15 @@ function bt(t) {
|
|
|
1469
1515
|
if (d) return d;
|
|
1470
1516
|
}
|
|
1471
1517
|
}
|
|
1472
|
-
const
|
|
1473
|
-
if (
|
|
1518
|
+
const o = (l = t.getAttribute("title")) == null ? void 0 : l.trim();
|
|
1519
|
+
if (o) return o;
|
|
1474
1520
|
if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) {
|
|
1475
1521
|
const c = (p = t.getAttribute("placeholder")) == null ? void 0 : p.trim();
|
|
1476
1522
|
if (c) return c;
|
|
1477
1523
|
}
|
|
1478
1524
|
return "";
|
|
1479
1525
|
}
|
|
1480
|
-
const
|
|
1526
|
+
const xt = {
|
|
1481
1527
|
id: "label",
|
|
1482
1528
|
wcag: ["4.1.2"],
|
|
1483
1529
|
level: "A",
|
|
@@ -1486,23 +1532,23 @@ const gt = {
|
|
|
1486
1532
|
prompt: `This form field has no accessible label. Based on the context (input type, name attribute, placeholder, or surrounding elements), suggest adding a <label for="id"> element with descriptive text, or an aria-label attribute. The label should describe what information the user should enter, not the field type. For example: 'Email address', 'Search', 'Phone number'.`,
|
|
1487
1533
|
run(t) {
|
|
1488
1534
|
var i;
|
|
1489
|
-
const a = [], n = t.querySelectorAll(`input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select, ${
|
|
1490
|
-
for (const
|
|
1491
|
-
if (b(
|
|
1492
|
-
const
|
|
1493
|
-
if (
|
|
1494
|
-
if (!
|
|
1495
|
-
const l = [], p =
|
|
1535
|
+
const a = [], n = t.querySelectorAll(`input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select, ${vt}`);
|
|
1536
|
+
for (const o of n) {
|
|
1537
|
+
if (b(o) || q(o) || (o instanceof HTMLInputElement || o instanceof HTMLTextAreaElement || o instanceof HTMLSelectElement || o instanceof HTMLButtonElement) && o.disabled || o.closest("fieldset[disabled]") || o.getAttribute("aria-disabled") === "true") continue;
|
|
1538
|
+
const r = (i = o.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase();
|
|
1539
|
+
if (r === "presentation" || r === "none") continue;
|
|
1540
|
+
if (!At(o)) {
|
|
1541
|
+
const l = [], p = o.tagName.toLowerCase(), c = o.getAttribute("type");
|
|
1496
1542
|
c && p === "input" && l.push(`type: ${c}`);
|
|
1497
|
-
const d =
|
|
1543
|
+
const d = o.getAttribute("name");
|
|
1498
1544
|
d && l.push(`name: "${d}"`);
|
|
1499
|
-
const h =
|
|
1500
|
-
h && l.push(`placeholder: "${h}"`),
|
|
1501
|
-
const g =
|
|
1545
|
+
const h = o.getAttribute("placeholder");
|
|
1546
|
+
h && l.push(`placeholder: "${h}"`), r && l.push(`role: ${r}`);
|
|
1547
|
+
const g = o.getAttribute("id");
|
|
1502
1548
|
g && l.push(`id: "${g}"`), a.push({
|
|
1503
1549
|
ruleId: "label",
|
|
1504
|
-
selector: m(
|
|
1505
|
-
html: u(
|
|
1550
|
+
selector: m(o),
|
|
1551
|
+
html: u(o),
|
|
1506
1552
|
impact: "critical",
|
|
1507
1553
|
message: "Form element has no accessible label.",
|
|
1508
1554
|
context: l.length > 0 ? l.join(", ") : void 0
|
|
@@ -1511,7 +1557,7 @@ const gt = {
|
|
|
1511
1557
|
}
|
|
1512
1558
|
return a;
|
|
1513
1559
|
}
|
|
1514
|
-
},
|
|
1560
|
+
}, kt = {
|
|
1515
1561
|
id: "form-field-multiple-labels",
|
|
1516
1562
|
wcag: [],
|
|
1517
1563
|
level: "A",
|
|
@@ -1524,15 +1570,15 @@ const gt = {
|
|
|
1524
1570
|
for (const n of e) {
|
|
1525
1571
|
if (b(n) || !n.id) continue;
|
|
1526
1572
|
const i = t.querySelectorAll(`label[for="${CSS.escape(n.id)}"]`);
|
|
1527
|
-
let
|
|
1528
|
-
for (;
|
|
1529
|
-
if (
|
|
1530
|
-
|
|
1573
|
+
let o = 0, r = n.parentElement;
|
|
1574
|
+
for (; r; ) {
|
|
1575
|
+
if (r.tagName.toLowerCase() === "label" && !r.hasAttribute("for")) {
|
|
1576
|
+
o++;
|
|
1531
1577
|
break;
|
|
1532
1578
|
}
|
|
1533
|
-
|
|
1579
|
+
r = r.parentElement;
|
|
1534
1580
|
}
|
|
1535
|
-
const s = i.length +
|
|
1581
|
+
const s = i.length + o;
|
|
1536
1582
|
s > 1 && a.push({
|
|
1537
1583
|
ruleId: "form-field-multiple-labels",
|
|
1538
1584
|
selector: m(n),
|
|
@@ -1543,7 +1589,7 @@ const gt = {
|
|
|
1543
1589
|
}
|
|
1544
1590
|
return a;
|
|
1545
1591
|
}
|
|
1546
|
-
},
|
|
1592
|
+
}, St = {
|
|
1547
1593
|
id: "select-name",
|
|
1548
1594
|
wcag: ["4.1.2"],
|
|
1549
1595
|
level: "A",
|
|
@@ -1562,7 +1608,7 @@ const gt = {
|
|
|
1562
1608
|
});
|
|
1563
1609
|
return a;
|
|
1564
1610
|
}
|
|
1565
|
-
},
|
|
1611
|
+
}, It = {
|
|
1566
1612
|
id: "input-button-name",
|
|
1567
1613
|
wcag: ["4.1.2"],
|
|
1568
1614
|
level: "A",
|
|
@@ -1576,8 +1622,8 @@ const gt = {
|
|
|
1576
1622
|
'input[type="submit"], input[type="button"], input[type="reset"]'
|
|
1577
1623
|
)) {
|
|
1578
1624
|
if (b(i)) continue;
|
|
1579
|
-
const
|
|
1580
|
-
!
|
|
1625
|
+
const o = (e = i.getAttribute("value")) == null ? void 0 : e.trim(), r = (n = i.getAttribute("type")) == null ? void 0 : n.toLowerCase(), s = (r === "submit" || r === "reset") && !i.hasAttribute("value");
|
|
1626
|
+
!o && !s && !v(i) && a.push({
|
|
1581
1627
|
ruleId: "input-button-name",
|
|
1582
1628
|
selector: m(i),
|
|
1583
1629
|
html: u(i),
|
|
@@ -1587,7 +1633,7 @@ const gt = {
|
|
|
1587
1633
|
}
|
|
1588
1634
|
return a;
|
|
1589
1635
|
}
|
|
1590
|
-
},
|
|
1636
|
+
}, Et = /* @__PURE__ */ new Set([
|
|
1591
1637
|
"off",
|
|
1592
1638
|
"on",
|
|
1593
1639
|
"name",
|
|
@@ -1642,7 +1688,7 @@ const gt = {
|
|
|
1642
1688
|
"impp",
|
|
1643
1689
|
"url",
|
|
1644
1690
|
"photo"
|
|
1645
|
-
]),
|
|
1691
|
+
]), qt = /* @__PURE__ */ new Set([
|
|
1646
1692
|
"tel",
|
|
1647
1693
|
"tel-country-code",
|
|
1648
1694
|
"tel-national",
|
|
@@ -1651,18 +1697,18 @@ const gt = {
|
|
|
1651
1697
|
"tel-extension",
|
|
1652
1698
|
"email",
|
|
1653
1699
|
"impp"
|
|
1654
|
-
]),
|
|
1655
|
-
function
|
|
1700
|
+
]), Lt = /* @__PURE__ */ new Set(["home", "work", "mobile", "fax", "pager"]), Tt = /* @__PURE__ */ new Set(["shipping", "billing"]), Rt = /* @__PURE__ */ new Set(["webauthn"]);
|
|
1701
|
+
function Ct(t) {
|
|
1656
1702
|
const a = t.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1657
1703
|
if (a.length === 0) return !0;
|
|
1658
1704
|
let e = 0;
|
|
1659
|
-
a[e].startsWith("section-") && e++, e < a.length &&
|
|
1705
|
+
a[e].startsWith("section-") && e++, e < a.length && Tt.has(a[e]) && e++;
|
|
1660
1706
|
let n = !1;
|
|
1661
|
-
if (e < a.length &&
|
|
1707
|
+
if (e < a.length && Lt.has(a[e]) && (n = !0, e++), e >= a.length) return !1;
|
|
1662
1708
|
const i = a[e];
|
|
1663
|
-
return !
|
|
1709
|
+
return !Et.has(i) || n && !qt.has(i) ? !1 : (e++, e < a.length && Rt.has(a[e]) && e++, e === a.length);
|
|
1664
1710
|
}
|
|
1665
|
-
const
|
|
1711
|
+
const Nt = {
|
|
1666
1712
|
id: "autocomplete-valid",
|
|
1667
1713
|
wcag: ["1.3.5"],
|
|
1668
1714
|
level: "AA",
|
|
@@ -1674,7 +1720,7 @@ const qt = {
|
|
|
1674
1720
|
for (const e of t.querySelectorAll("[autocomplete]")) {
|
|
1675
1721
|
if (b(e) || e instanceof HTMLElement && e.style.display === "none" || e.disabled || e.getAttribute("aria-disabled") === "true") continue;
|
|
1676
1722
|
const n = e.getAttribute("autocomplete").trim();
|
|
1677
|
-
n && (
|
|
1723
|
+
n && (Ct(n) || a.push({
|
|
1678
1724
|
ruleId: "autocomplete-valid",
|
|
1679
1725
|
selector: m(e),
|
|
1680
1726
|
html: u(e),
|
|
@@ -1685,16 +1731,16 @@ const qt = {
|
|
|
1685
1731
|
return a;
|
|
1686
1732
|
}
|
|
1687
1733
|
};
|
|
1688
|
-
function
|
|
1734
|
+
function re(t) {
|
|
1689
1735
|
return t.toLowerCase().replace(/\s+/g, " ").trim();
|
|
1690
1736
|
}
|
|
1691
|
-
function
|
|
1692
|
-
const e =
|
|
1737
|
+
function se(t, a) {
|
|
1738
|
+
const e = re(t), n = re(a);
|
|
1693
1739
|
if (!e || !n || e.includes(n) || n.includes(e)) return !0;
|
|
1694
|
-
const i = n.split(/\s+/).map((
|
|
1695
|
-
return i.length >= 2 && i.filter((
|
|
1740
|
+
const i = n.split(/\s+/).map((o) => o.replace(/[.,;:!?\u2026]+$/g, "")).filter((o) => o.length > 2);
|
|
1741
|
+
return i.length >= 2 && i.filter((r) => e.includes(r)).length / i.length > 0.5;
|
|
1696
1742
|
}
|
|
1697
|
-
function
|
|
1743
|
+
function K(t) {
|
|
1698
1744
|
let a = "";
|
|
1699
1745
|
for (const e of t.childNodes)
|
|
1700
1746
|
if (e.nodeType === 3)
|
|
@@ -1702,13 +1748,13 @@ function Y(t) {
|
|
|
1702
1748
|
else if (e.nodeType === 1) {
|
|
1703
1749
|
const n = e, i = n.tagName.toLowerCase();
|
|
1704
1750
|
if (i === "style" || i === "script" || i === "svg" || n.getAttribute("aria-hidden") === "true" || n instanceof HTMLElement && n.style.display === "none") continue;
|
|
1705
|
-
const
|
|
1706
|
-
if (
|
|
1707
|
-
a +=
|
|
1751
|
+
const o = n.getAttribute("role");
|
|
1752
|
+
if (o === "img" || o === "presentation" || o === "none") continue;
|
|
1753
|
+
a += K(n);
|
|
1708
1754
|
}
|
|
1709
1755
|
return a;
|
|
1710
1756
|
}
|
|
1711
|
-
const
|
|
1757
|
+
const Mt = {
|
|
1712
1758
|
id: "label-content-name-mismatch",
|
|
1713
1759
|
wcag: [],
|
|
1714
1760
|
level: "A",
|
|
@@ -1723,11 +1769,11 @@ const Lt = {
|
|
|
1723
1769
|
const n = v(e);
|
|
1724
1770
|
if (!n) continue;
|
|
1725
1771
|
let i = "";
|
|
1726
|
-
e instanceof HTMLInputElement ? i = e.value || "" : i =
|
|
1727
|
-
const
|
|
1728
|
-
if (!
|
|
1729
|
-
const
|
|
1730
|
-
!
|
|
1772
|
+
e instanceof HTMLInputElement ? i = e.value || "" : i = K(e);
|
|
1773
|
+
const o = i.trim();
|
|
1774
|
+
if (!o || o.length <= 2) continue;
|
|
1775
|
+
const r = e.hasAttribute("aria-label"), s = e.hasAttribute("aria-labelledby");
|
|
1776
|
+
!r && !s || se(n, i) || a.push({
|
|
1731
1777
|
ruleId: "label-content-name-mismatch",
|
|
1732
1778
|
selector: m(e),
|
|
1733
1779
|
html: u(e),
|
|
@@ -1739,23 +1785,23 @@ const Lt = {
|
|
|
1739
1785
|
if (b(e) || e instanceof HTMLInputElement && ["hidden", "submit", "button", "image"].includes(e.type)) continue;
|
|
1740
1786
|
const n = v(e);
|
|
1741
1787
|
if (!n || !e.hasAttribute("aria-label")) continue;
|
|
1742
|
-
const
|
|
1743
|
-
let
|
|
1744
|
-
if (
|
|
1745
|
-
const s = t.querySelector(`label[for="${CSS.escape(
|
|
1746
|
-
s && (
|
|
1788
|
+
const o = e.id;
|
|
1789
|
+
let r = "";
|
|
1790
|
+
if (o) {
|
|
1791
|
+
const s = t.querySelector(`label[for="${CSS.escape(o)}"]`);
|
|
1792
|
+
s && (r = K(s));
|
|
1747
1793
|
}
|
|
1748
|
-
|
|
1794
|
+
r.trim() && (se(n, r) || a.push({
|
|
1749
1795
|
ruleId: "label-content-name-mismatch",
|
|
1750
1796
|
selector: m(e),
|
|
1751
1797
|
html: u(e),
|
|
1752
1798
|
impact: "serious",
|
|
1753
|
-
message: `Accessible name "${n}" does not contain visible label "${
|
|
1799
|
+
message: `Accessible name "${n}" does not contain visible label "${r.trim()}".`
|
|
1754
1800
|
}));
|
|
1755
1801
|
}
|
|
1756
1802
|
return a;
|
|
1757
1803
|
}
|
|
1758
|
-
},
|
|
1804
|
+
}, Dt = {
|
|
1759
1805
|
id: "label-title-only",
|
|
1760
1806
|
wcag: [],
|
|
1761
1807
|
level: "A",
|
|
@@ -1764,7 +1810,7 @@ const Lt = {
|
|
|
1764
1810
|
guidance: "The title attribute is unreliable as a label because it only appears on hover/focus (not visible to touch users) and is often ignored by assistive technologies. Use a visible <label> element, aria-label, or aria-labelledby instead. Title can supplement a label but should not replace it.",
|
|
1765
1811
|
prompt: "The title attribute text should be moved to a visible <label> element or aria-label. Show what text to use based on the current title.",
|
|
1766
1812
|
run(t) {
|
|
1767
|
-
var n, i,
|
|
1813
|
+
var n, i, o, r;
|
|
1768
1814
|
const a = [], e = t.querySelectorAll(
|
|
1769
1815
|
'input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select'
|
|
1770
1816
|
);
|
|
@@ -1775,10 +1821,10 @@ const Lt = {
|
|
|
1775
1821
|
const h = s.id;
|
|
1776
1822
|
if (h) {
|
|
1777
1823
|
const f = s.ownerDocument.querySelector(`label[for="${CSS.escape(h)}"]`);
|
|
1778
|
-
(
|
|
1824
|
+
(o = f == null ? void 0 : f.textContent) != null && o.trim() && (d = !0);
|
|
1779
1825
|
}
|
|
1780
1826
|
const g = s.closest("label");
|
|
1781
|
-
(
|
|
1827
|
+
(r = g == null ? void 0 : g.textContent) != null && r.trim() && (d = !0), l && !p && !c && !d && a.push({
|
|
1782
1828
|
ruleId: "label-title-only",
|
|
1783
1829
|
selector: m(s),
|
|
1784
1830
|
html: u(s),
|
|
@@ -1788,7 +1834,7 @@ const Lt = {
|
|
|
1788
1834
|
}
|
|
1789
1835
|
return a;
|
|
1790
1836
|
}
|
|
1791
|
-
},
|
|
1837
|
+
}, $t = {
|
|
1792
1838
|
id: "tabindex",
|
|
1793
1839
|
selector: "[tabindex]",
|
|
1794
1840
|
check: { type: "attribute-value", attribute: "tabindex", operator: ">", value: 0 },
|
|
@@ -1800,7 +1846,7 @@ const Lt = {
|
|
|
1800
1846
|
tags: ["best-practice"],
|
|
1801
1847
|
guidance: "Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence.",
|
|
1802
1848
|
prompt: "Change the positive tabindex value to tabindex='0' and rely on DOM order for tab sequence instead."
|
|
1803
|
-
},
|
|
1849
|
+
}, Ht = E($t), Pt = /* @__PURE__ */ new Set([
|
|
1804
1850
|
"div",
|
|
1805
1851
|
"span",
|
|
1806
1852
|
"p",
|
|
@@ -1827,7 +1873,7 @@ const Lt = {
|
|
|
1827
1873
|
"tr",
|
|
1828
1874
|
"td",
|
|
1829
1875
|
"th"
|
|
1830
|
-
]),
|
|
1876
|
+
]), zt = {
|
|
1831
1877
|
id: "focus-order-semantics",
|
|
1832
1878
|
wcag: [],
|
|
1833
1879
|
tags: ["best-practice"],
|
|
@@ -1839,7 +1885,7 @@ const Lt = {
|
|
|
1839
1885
|
const a = [];
|
|
1840
1886
|
for (const e of t.querySelectorAll('[tabindex="0"]')) {
|
|
1841
1887
|
const n = e.tagName.toLowerCase();
|
|
1842
|
-
if (!
|
|
1888
|
+
if (!Pt.has(n)) continue;
|
|
1843
1889
|
e.getAttribute("role") || a.push({
|
|
1844
1890
|
ruleId: "focus-order-semantics",
|
|
1845
1891
|
selector: m(e),
|
|
@@ -1850,7 +1896,7 @@ const Lt = {
|
|
|
1850
1896
|
}
|
|
1851
1897
|
return a;
|
|
1852
1898
|
}
|
|
1853
|
-
},
|
|
1899
|
+
}, Ft = /* @__PURE__ */ new Set([
|
|
1854
1900
|
"a",
|
|
1855
1901
|
"audio",
|
|
1856
1902
|
"button",
|
|
@@ -1859,7 +1905,7 @@ const Lt = {
|
|
|
1859
1905
|
"select",
|
|
1860
1906
|
"textarea",
|
|
1861
1907
|
"video"
|
|
1862
|
-
]),
|
|
1908
|
+
]), jt = /* @__PURE__ */ new Set([
|
|
1863
1909
|
"button",
|
|
1864
1910
|
"checkbox",
|
|
1865
1911
|
"combobox",
|
|
@@ -1883,7 +1929,7 @@ const Lt = {
|
|
|
1883
1929
|
"tabpanel",
|
|
1884
1930
|
"textbox",
|
|
1885
1931
|
"treeitem"
|
|
1886
|
-
]),
|
|
1932
|
+
]), Wt = {
|
|
1887
1933
|
grid: /* @__PURE__ */ new Set(["gridcell", "row", "columnheader", "rowheader"]),
|
|
1888
1934
|
listbox: /* @__PURE__ */ new Set(["option"]),
|
|
1889
1935
|
menu: /* @__PURE__ */ new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]),
|
|
@@ -1893,26 +1939,26 @@ const Lt = {
|
|
|
1893
1939
|
tree: /* @__PURE__ */ new Set(["treeitem"]),
|
|
1894
1940
|
treegrid: /* @__PURE__ */ new Set(["gridcell", "row", "columnheader", "rowheader", "treeitem"])
|
|
1895
1941
|
};
|
|
1896
|
-
function
|
|
1897
|
-
var i,
|
|
1898
|
-
const e = (i = t.getAttribute("role")) == null ? void 0 : i.toLowerCase(), n = (
|
|
1899
|
-
return !e || !n ? !1 : ((
|
|
1942
|
+
function Ut(t, a) {
|
|
1943
|
+
var i, o, r;
|
|
1944
|
+
const e = (i = t.getAttribute("role")) == null ? void 0 : i.toLowerCase(), n = (o = a.getAttribute("role")) == null ? void 0 : o.toLowerCase();
|
|
1945
|
+
return !e || !n ? !1 : ((r = Wt[e]) == null ? void 0 : r.has(n)) ?? !1;
|
|
1900
1946
|
}
|
|
1901
|
-
function
|
|
1947
|
+
function Bt(t) {
|
|
1902
1948
|
var i;
|
|
1903
1949
|
const a = t.tagName.toLowerCase();
|
|
1904
|
-
if (
|
|
1950
|
+
if (Ft.has(a))
|
|
1905
1951
|
return a === "a" && !t.hasAttribute("href") ? !1 : a === "audio" || a === "video" ? t.hasAttribute("controls") : !(a === "img" && !t.hasAttribute("usemap") || a === "input" && t.type === "hidden" || t.disabled);
|
|
1906
1952
|
const e = (i = t.getAttribute("role")) == null ? void 0 : i.toLowerCase();
|
|
1907
|
-
if (e &&
|
|
1953
|
+
if (e && jt.has(e)) return !0;
|
|
1908
1954
|
const n = t.getAttribute("tabindex");
|
|
1909
1955
|
return n !== null && n !== "-1" || t.getAttribute("contenteditable") === "true";
|
|
1910
1956
|
}
|
|
1911
|
-
function
|
|
1957
|
+
function Ot(t) {
|
|
1912
1958
|
const a = t.tagName.toLowerCase();
|
|
1913
1959
|
return !!(a === "a" && t.hasAttribute("href") || a === "button" && !t.disabled);
|
|
1914
1960
|
}
|
|
1915
|
-
const
|
|
1961
|
+
const Vt = {
|
|
1916
1962
|
id: "nested-interactive",
|
|
1917
1963
|
wcag: ["4.1.2"],
|
|
1918
1964
|
level: "A",
|
|
@@ -1922,29 +1968,29 @@ const Wt = {
|
|
|
1922
1968
|
run(t) {
|
|
1923
1969
|
const a = [], e = t.body ?? t;
|
|
1924
1970
|
if (!e) return a;
|
|
1925
|
-
const i = (t.body ? t : t.ownerDocument).createTreeWalker(e, NodeFilter.SHOW_ELEMENT),
|
|
1926
|
-
let
|
|
1927
|
-
for (;
|
|
1928
|
-
for (;
|
|
1929
|
-
|
|
1930
|
-
if (!b(
|
|
1931
|
-
if (
|
|
1932
|
-
const s =
|
|
1933
|
-
|
|
1971
|
+
const i = (t.body ? t : t.ownerDocument).createTreeWalker(e, NodeFilter.SHOW_ELEMENT), o = [];
|
|
1972
|
+
let r = i.currentNode;
|
|
1973
|
+
for (; r; ) {
|
|
1974
|
+
for (; o.length > 0 && !o[o.length - 1].contains(r); )
|
|
1975
|
+
o.pop();
|
|
1976
|
+
if (!b(r) && Bt(r)) {
|
|
1977
|
+
if (o.length > 0) {
|
|
1978
|
+
const s = o[o.length - 1];
|
|
1979
|
+
Ut(s, r) || a.push({
|
|
1934
1980
|
ruleId: "nested-interactive",
|
|
1935
|
-
selector: m(
|
|
1936
|
-
html: u(
|
|
1981
|
+
selector: m(r),
|
|
1982
|
+
html: u(r),
|
|
1937
1983
|
impact: "serious",
|
|
1938
|
-
message: `Interactive element <${
|
|
1984
|
+
message: `Interactive element <${r.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`
|
|
1939
1985
|
});
|
|
1940
1986
|
}
|
|
1941
|
-
|
|
1987
|
+
Ot(r) && o.push(r);
|
|
1942
1988
|
}
|
|
1943
|
-
|
|
1989
|
+
r = i.nextNode();
|
|
1944
1990
|
}
|
|
1945
1991
|
return a;
|
|
1946
1992
|
}
|
|
1947
|
-
},
|
|
1993
|
+
}, _t = {
|
|
1948
1994
|
id: "scrollable-region-focusable",
|
|
1949
1995
|
wcag: ["2.1.1"],
|
|
1950
1996
|
level: "A",
|
|
@@ -1958,15 +2004,15 @@ const Wt = {
|
|
|
1958
2004
|
if (b(n) || !(n instanceof HTMLElement)) continue;
|
|
1959
2005
|
const i = n.tagName.toLowerCase();
|
|
1960
2006
|
if (i === "body" || i === "html") continue;
|
|
1961
|
-
const
|
|
1962
|
-
if (
|
|
1963
|
-
const
|
|
2007
|
+
const o = n.getAttribute("role");
|
|
2008
|
+
if (o === "presentation" || o === "none") continue;
|
|
2009
|
+
const r = w(n), s = r.overflowX, l = r.overflowY;
|
|
1964
2010
|
if (!(s === "scroll" || s === "auto" || l === "scroll" || l === "auto")) continue;
|
|
1965
2011
|
if (n.scrollHeight > 0 || n.clientHeight > 0) {
|
|
1966
2012
|
const g = n.scrollHeight - n.clientHeight, f = n.scrollWidth - n.clientWidth;
|
|
1967
2013
|
if (g <= 0 && f <= 0 || g < 14 && f < 14 || n.clientWidth < 64 && n.clientHeight < 64) continue;
|
|
1968
2014
|
} else {
|
|
1969
|
-
const g =
|
|
2015
|
+
const g = r.height !== "" || r.maxHeight !== "", f = ((e = n.textContent) == null ? void 0 : e.trim().length) ?? 0;
|
|
1970
2016
|
if (!g || f <= 50) continue;
|
|
1971
2017
|
}
|
|
1972
2018
|
const d = n.getAttribute("tabindex");
|
|
@@ -1982,7 +2028,7 @@ const Wt = {
|
|
|
1982
2028
|
}
|
|
1983
2029
|
return a;
|
|
1984
2030
|
}
|
|
1985
|
-
},
|
|
2031
|
+
}, Gt = {
|
|
1986
2032
|
id: "accesskeys",
|
|
1987
2033
|
wcag: [],
|
|
1988
2034
|
level: "A",
|
|
@@ -1995,24 +2041,24 @@ const Wt = {
|
|
|
1995
2041
|
const a = [], e = /* @__PURE__ */ new Map();
|
|
1996
2042
|
for (const i of t.querySelectorAll("[accesskey]")) {
|
|
1997
2043
|
if (b(i)) continue;
|
|
1998
|
-
const
|
|
1999
|
-
if (!
|
|
2000
|
-
const
|
|
2001
|
-
|
|
2044
|
+
const o = (n = i.getAttribute("accesskey")) == null ? void 0 : n.trim().toLowerCase();
|
|
2045
|
+
if (!o) continue;
|
|
2046
|
+
const r = e.get(o) || [];
|
|
2047
|
+
r.push(i), e.set(o, r);
|
|
2002
2048
|
}
|
|
2003
|
-
for (const [i,
|
|
2004
|
-
if (
|
|
2005
|
-
for (const
|
|
2049
|
+
for (const [i, o] of e)
|
|
2050
|
+
if (o.length > 1)
|
|
2051
|
+
for (const r of o.slice(1))
|
|
2006
2052
|
a.push({
|
|
2007
2053
|
ruleId: "accesskeys",
|
|
2008
|
-
selector: m(
|
|
2009
|
-
html: u(
|
|
2054
|
+
selector: m(r),
|
|
2055
|
+
html: u(r),
|
|
2010
2056
|
impact: "serious",
|
|
2011
2057
|
message: `Duplicate accesskey "${i}". Each accesskey must be unique.`
|
|
2012
2058
|
});
|
|
2013
2059
|
return a;
|
|
2014
2060
|
}
|
|
2015
|
-
},
|
|
2061
|
+
}, Yt = {
|
|
2016
2062
|
id: "heading-order",
|
|
2017
2063
|
wcag: [],
|
|
2018
2064
|
level: "A",
|
|
@@ -2023,21 +2069,21 @@ const Wt = {
|
|
|
2023
2069
|
run(t) {
|
|
2024
2070
|
const a = [], e = t.querySelectorAll("h1, h2, h3, h4, h5, h6, [role='heading']");
|
|
2025
2071
|
let n = 0, i = null;
|
|
2026
|
-
for (const
|
|
2027
|
-
if (b(
|
|
2028
|
-
let
|
|
2029
|
-
|
|
2072
|
+
for (const o of e) {
|
|
2073
|
+
if (b(o)) continue;
|
|
2074
|
+
let r;
|
|
2075
|
+
o.hasAttribute("aria-level") ? r = parseInt(o.getAttribute("aria-level"), 10) : r = parseInt(o.tagName[1], 10), n > 0 && r > n + 1 && a.push({
|
|
2030
2076
|
ruleId: "heading-order",
|
|
2031
|
-
selector: m(
|
|
2032
|
-
html: u(
|
|
2077
|
+
selector: m(o),
|
|
2078
|
+
html: u(o),
|
|
2033
2079
|
impact: "moderate",
|
|
2034
|
-
message: `Heading level ${
|
|
2080
|
+
message: `Heading level ${r} skipped from level ${n}.`,
|
|
2035
2081
|
context: i ? `Previous heading: ${u(i)}` : void 0
|
|
2036
|
-
}), n =
|
|
2082
|
+
}), n = r, i = o;
|
|
2037
2083
|
}
|
|
2038
2084
|
return a;
|
|
2039
2085
|
}
|
|
2040
|
-
}, H = 'article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]',
|
|
2086
|
+
}, H = 'article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]', le = 'main, [role="main"], header, [role="banner"], footer, [role="contentinfo"], nav, [role="navigation"], aside, [role="complementary"], section[aria-label], section[aria-labelledby], [role="region"][aria-label], [role="region"][aria-labelledby], form[aria-label], form[aria-labelledby], [role="form"][aria-label], [role="form"][aria-labelledby], [role="search"]', Xt = {
|
|
2041
2087
|
id: "landmark-one-main",
|
|
2042
2088
|
wcag: [],
|
|
2043
2089
|
level: "A",
|
|
@@ -2061,7 +2107,7 @@ const Wt = {
|
|
|
2061
2107
|
message: "Page has multiple main landmarks."
|
|
2062
2108
|
})) : [];
|
|
2063
2109
|
}
|
|
2064
|
-
},
|
|
2110
|
+
}, Kt = {
|
|
2065
2111
|
id: "landmark-no-duplicate-banner",
|
|
2066
2112
|
wcag: [],
|
|
2067
2113
|
level: "A",
|
|
@@ -2081,7 +2127,7 @@ const Wt = {
|
|
|
2081
2127
|
})
|
|
2082
2128
|
), a;
|
|
2083
2129
|
}
|
|
2084
|
-
},
|
|
2130
|
+
}, Jt = {
|
|
2085
2131
|
id: "landmark-no-duplicate-contentinfo",
|
|
2086
2132
|
wcag: [],
|
|
2087
2133
|
level: "A",
|
|
@@ -2101,7 +2147,7 @@ const Wt = {
|
|
|
2101
2147
|
})
|
|
2102
2148
|
), a;
|
|
2103
2149
|
}
|
|
2104
|
-
},
|
|
2150
|
+
}, Qt = {
|
|
2105
2151
|
id: "landmark-no-duplicate-main",
|
|
2106
2152
|
wcag: [],
|
|
2107
2153
|
level: "A",
|
|
@@ -2121,7 +2167,7 @@ const Wt = {
|
|
|
2121
2167
|
})
|
|
2122
2168
|
), a;
|
|
2123
2169
|
}
|
|
2124
|
-
},
|
|
2170
|
+
}, Zt = {
|
|
2125
2171
|
id: "landmark-banner-is-top-level",
|
|
2126
2172
|
wcag: [],
|
|
2127
2173
|
level: "A",
|
|
@@ -2141,7 +2187,7 @@ const Wt = {
|
|
|
2141
2187
|
});
|
|
2142
2188
|
return a;
|
|
2143
2189
|
}
|
|
2144
|
-
},
|
|
2190
|
+
}, ea = {
|
|
2145
2191
|
id: "landmark-contentinfo-is-top-level",
|
|
2146
2192
|
wcag: [],
|
|
2147
2193
|
level: "A",
|
|
@@ -2161,7 +2207,7 @@ const Wt = {
|
|
|
2161
2207
|
});
|
|
2162
2208
|
return a;
|
|
2163
2209
|
}
|
|
2164
|
-
},
|
|
2210
|
+
}, ta = {
|
|
2165
2211
|
id: "landmark-main-is-top-level",
|
|
2166
2212
|
wcag: [],
|
|
2167
2213
|
level: "A",
|
|
@@ -2183,7 +2229,7 @@ const Wt = {
|
|
|
2183
2229
|
}
|
|
2184
2230
|
return a;
|
|
2185
2231
|
}
|
|
2186
|
-
},
|
|
2232
|
+
}, aa = {
|
|
2187
2233
|
id: "landmark-complementary-is-top-level",
|
|
2188
2234
|
wcag: [],
|
|
2189
2235
|
level: "A",
|
|
@@ -2205,7 +2251,7 @@ const Wt = {
|
|
|
2205
2251
|
}
|
|
2206
2252
|
return a;
|
|
2207
2253
|
}
|
|
2208
|
-
},
|
|
2254
|
+
}, na = {
|
|
2209
2255
|
id: "landmark-unique",
|
|
2210
2256
|
wcag: [],
|
|
2211
2257
|
level: "A",
|
|
@@ -2221,14 +2267,14 @@ const Wt = {
|
|
|
2221
2267
|
{ selector: 'form[aria-label], form[aria-labelledby], [role="form"], [role="search"]', type: "form" }
|
|
2222
2268
|
];
|
|
2223
2269
|
for (const { selector: n, type: i } of e) {
|
|
2224
|
-
const
|
|
2225
|
-
if (
|
|
2226
|
-
const
|
|
2227
|
-
for (const s of
|
|
2228
|
-
const l = v(s).toLowerCase() || "", p =
|
|
2229
|
-
p.push(s),
|
|
2270
|
+
const o = Array.from(t.querySelectorAll(n)).filter((s) => !b(s));
|
|
2271
|
+
if (o.length <= 1) continue;
|
|
2272
|
+
const r = /* @__PURE__ */ new Map();
|
|
2273
|
+
for (const s of o) {
|
|
2274
|
+
const l = v(s).toLowerCase() || "", p = r.get(l) || [];
|
|
2275
|
+
p.push(s), r.set(l, p);
|
|
2230
2276
|
}
|
|
2231
|
-
for (const [s, l] of
|
|
2277
|
+
for (const [s, l] of r)
|
|
2232
2278
|
if (l.length > 1)
|
|
2233
2279
|
for (const p of l.slice(1))
|
|
2234
2280
|
a.push({
|
|
@@ -2241,7 +2287,7 @@ const Wt = {
|
|
|
2241
2287
|
}
|
|
2242
2288
|
return a;
|
|
2243
2289
|
}
|
|
2244
|
-
},
|
|
2290
|
+
}, ia = {
|
|
2245
2291
|
id: "region",
|
|
2246
2292
|
wcag: [],
|
|
2247
2293
|
level: "A",
|
|
@@ -2255,8 +2301,8 @@ const Wt = {
|
|
|
2255
2301
|
if (!e) return [];
|
|
2256
2302
|
for (const i of e.children) {
|
|
2257
2303
|
if (b(i) || i instanceof HTMLScriptElement || i instanceof HTMLStyleElement || i.tagName === "NOSCRIPT" || i instanceof HTMLElement && i.hidden || i.matches('a[href^="#"]')) continue;
|
|
2258
|
-
const
|
|
2259
|
-
!
|
|
2304
|
+
const o = i.matches(le), r = (n = i.textContent) == null ? void 0 : n.trim();
|
|
2305
|
+
!o && r && (i.querySelector(le) || a.push({
|
|
2260
2306
|
ruleId: "region",
|
|
2261
2307
|
selector: m(i),
|
|
2262
2308
|
html: u(i),
|
|
@@ -2266,7 +2312,7 @@ const Wt = {
|
|
|
2266
2312
|
}
|
|
2267
2313
|
return a;
|
|
2268
2314
|
}
|
|
2269
|
-
},
|
|
2315
|
+
}, oa = {
|
|
2270
2316
|
id: "list",
|
|
2271
2317
|
selector: "ul, ol",
|
|
2272
2318
|
check: { type: "child-invalid", allowedChildren: ["li", "script", "template"], allowedChildRoles: ["listitem"] },
|
|
@@ -2277,7 +2323,7 @@ const Wt = {
|
|
|
2277
2323
|
level: "A",
|
|
2278
2324
|
guidance: "Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, restructure your CSS to style the <li> elements directly.",
|
|
2279
2325
|
prompt: "Explain how to restructure this element within the list properly."
|
|
2280
|
-
},
|
|
2326
|
+
}, ra = E(oa), sa = {
|
|
2281
2327
|
id: "dlitem",
|
|
2282
2328
|
wcag: ["1.3.1"],
|
|
2283
2329
|
level: "A",
|
|
@@ -2296,7 +2342,7 @@ const Wt = {
|
|
|
2296
2342
|
});
|
|
2297
2343
|
return a;
|
|
2298
2344
|
}
|
|
2299
|
-
},
|
|
2345
|
+
}, la = {
|
|
2300
2346
|
id: "definition-list",
|
|
2301
2347
|
selector: "dl",
|
|
2302
2348
|
check: { type: "child-invalid", allowedChildren: ["dt", "dd", "div", "script", "template"] },
|
|
@@ -2307,7 +2353,7 @@ const Wt = {
|
|
|
2307
2353
|
level: "A",
|
|
2308
2354
|
guidance: "Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or restructure using proper definition list markup.",
|
|
2309
2355
|
prompt: "Explain whether to move this element outside the <dl> or convert it to dt/dd."
|
|
2310
|
-
},
|
|
2356
|
+
}, ca = E(la), da = {
|
|
2311
2357
|
id: "listitem",
|
|
2312
2358
|
wcag: ["1.3.1"],
|
|
2313
2359
|
level: "A",
|
|
@@ -2321,8 +2367,8 @@ const Wt = {
|
|
|
2321
2367
|
if (b(n)) continue;
|
|
2322
2368
|
const i = n.parentElement;
|
|
2323
2369
|
if (!i) continue;
|
|
2324
|
-
const
|
|
2325
|
-
|
|
2370
|
+
const o = i.tagName.toLowerCase();
|
|
2371
|
+
o === "ul" || o === "ol" || o === "menu" || ((e = i.getAttribute("role")) == null ? void 0 : e.trim().toLowerCase()) === "list" || a.push({
|
|
2326
2372
|
ruleId: "listitem",
|
|
2327
2373
|
selector: m(n),
|
|
2328
2374
|
html: u(n),
|
|
@@ -2332,7 +2378,7 @@ const Wt = {
|
|
|
2332
2378
|
}
|
|
2333
2379
|
return a;
|
|
2334
2380
|
}
|
|
2335
|
-
},
|
|
2381
|
+
}, ua = {
|
|
2336
2382
|
id: "document-title",
|
|
2337
2383
|
wcag: ["2.4.2"],
|
|
2338
2384
|
level: "A",
|
|
@@ -2343,13 +2389,13 @@ const Wt = {
|
|
|
2343
2389
|
var e, n, i;
|
|
2344
2390
|
const a = t.querySelector("title");
|
|
2345
2391
|
if (!a || !((e = a.textContent) != null && e.trim())) {
|
|
2346
|
-
let
|
|
2347
|
-
const
|
|
2348
|
-
if ((n =
|
|
2349
|
-
|
|
2392
|
+
let o;
|
|
2393
|
+
const r = t.querySelector("h1");
|
|
2394
|
+
if ((n = r == null ? void 0 : r.textContent) != null && n.trim())
|
|
2395
|
+
o = `h1: "${r.textContent.trim().slice(0, 100)}"`;
|
|
2350
2396
|
else if (t.body) {
|
|
2351
2397
|
const s = ((i = t.body.textContent) == null ? void 0 : i.trim().replace(/\s+/g, " ")) || "";
|
|
2352
|
-
s && (
|
|
2398
|
+
s && (o = `Page text: "${s.slice(0, 150)}"`);
|
|
2353
2399
|
}
|
|
2354
2400
|
return [{
|
|
2355
2401
|
ruleId: "document-title",
|
|
@@ -2357,12 +2403,12 @@ const Wt = {
|
|
|
2357
2403
|
html: "<html>",
|
|
2358
2404
|
impact: "serious",
|
|
2359
2405
|
message: a ? "Document <title> element is empty." : "Document is missing a <title> element.",
|
|
2360
|
-
context:
|
|
2406
|
+
context: o
|
|
2361
2407
|
}];
|
|
2362
2408
|
}
|
|
2363
2409
|
return [];
|
|
2364
2410
|
}
|
|
2365
|
-
},
|
|
2411
|
+
}, ma = {
|
|
2366
2412
|
id: "bypass",
|
|
2367
2413
|
wcag: [],
|
|
2368
2414
|
level: "A",
|
|
@@ -2376,10 +2422,10 @@ const Wt = {
|
|
|
2376
2422
|
)) return [];
|
|
2377
2423
|
const e = t.querySelector('a[href^="#"]');
|
|
2378
2424
|
if (e) {
|
|
2379
|
-
const
|
|
2380
|
-
if (
|
|
2381
|
-
const
|
|
2382
|
-
if (t.getElementById(
|
|
2425
|
+
const o = e.getAttribute("href");
|
|
2426
|
+
if (o && o.length > 1) {
|
|
2427
|
+
const r = o.slice(1);
|
|
2428
|
+
if (t.getElementById(r)) return [];
|
|
2383
2429
|
}
|
|
2384
2430
|
}
|
|
2385
2431
|
if (t.querySelector("h1, h2, h3, [role='heading']")) return [];
|
|
@@ -2393,7 +2439,7 @@ const Wt = {
|
|
|
2393
2439
|
context: `Missing: ${i.join(", ")}`
|
|
2394
2440
|
}];
|
|
2395
2441
|
}
|
|
2396
|
-
},
|
|
2442
|
+
}, pa = {
|
|
2397
2443
|
id: "page-has-heading-one",
|
|
2398
2444
|
wcag: [],
|
|
2399
2445
|
level: "A",
|
|
@@ -2402,17 +2448,17 @@ const Wt = {
|
|
|
2402
2448
|
guidance: "A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have exactly one h1 that describes the main content, typically matching or similar to the page title.",
|
|
2403
2449
|
prompt: "The page has no <h1> heading. Suggest appropriate h1 text based on the page title or content sample in context. The h1 should describe the page's main topic and typically be placed at the start of the main content area.",
|
|
2404
2450
|
run(t) {
|
|
2405
|
-
var
|
|
2451
|
+
var r, s, l;
|
|
2406
2452
|
const a = t.querySelector("h1");
|
|
2407
2453
|
if (a && v(a)) return [];
|
|
2408
2454
|
const e = t.querySelectorAll('[role="heading"][aria-level="1"]');
|
|
2409
2455
|
for (const p of e)
|
|
2410
2456
|
if (v(p)) return [];
|
|
2411
|
-
const n = [], i = (s = (
|
|
2457
|
+
const n = [], i = (s = (r = t.querySelector("title")) == null ? void 0 : r.textContent) == null ? void 0 : s.trim();
|
|
2412
2458
|
i && n.push(`Page title: "${i}"`);
|
|
2413
|
-
const
|
|
2414
|
-
if (
|
|
2415
|
-
const p = ((l =
|
|
2459
|
+
const o = t.querySelector("main");
|
|
2460
|
+
if (o) {
|
|
2461
|
+
const p = ((l = o.textContent) == null ? void 0 : l.trim().replace(/\s+/g, " ")) || "";
|
|
2416
2462
|
p && n.push(`Main content: "${p.slice(0, 100)}"`);
|
|
2417
2463
|
}
|
|
2418
2464
|
return [{
|
|
@@ -2425,13 +2471,13 @@ const Wt = {
|
|
|
2425
2471
|
}];
|
|
2426
2472
|
}
|
|
2427
2473
|
};
|
|
2428
|
-
function
|
|
2474
|
+
function xe(t) {
|
|
2429
2475
|
if (!(t instanceof HTMLElement)) return !1;
|
|
2430
2476
|
if (t.style.display === "none" || t.style.visibility === "hidden") return !0;
|
|
2431
2477
|
const a = t.getAttribute("width"), e = t.getAttribute("height");
|
|
2432
2478
|
return (a === "0" || a === "1") && (e === "0" || e === "1");
|
|
2433
2479
|
}
|
|
2434
|
-
const
|
|
2480
|
+
const ha = {
|
|
2435
2481
|
id: "frame-title",
|
|
2436
2482
|
wcag: ["4.1.2"],
|
|
2437
2483
|
level: "A",
|
|
@@ -2441,7 +2487,7 @@ const la = {
|
|
|
2441
2487
|
run(t) {
|
|
2442
2488
|
const a = [];
|
|
2443
2489
|
for (const e of t.querySelectorAll("iframe, frame")) {
|
|
2444
|
-
if (b(e) ||
|
|
2490
|
+
if (b(e) || xe(e)) continue;
|
|
2445
2491
|
if (!v(e)) {
|
|
2446
2492
|
const i = e.getAttribute("src");
|
|
2447
2493
|
a.push({
|
|
@@ -2456,7 +2502,7 @@ const la = {
|
|
|
2456
2502
|
}
|
|
2457
2503
|
return a;
|
|
2458
2504
|
}
|
|
2459
|
-
},
|
|
2505
|
+
}, ba = {
|
|
2460
2506
|
id: "frame-title-unique",
|
|
2461
2507
|
wcag: ["4.1.2"],
|
|
2462
2508
|
level: "A",
|
|
@@ -2467,27 +2513,27 @@ const la = {
|
|
|
2467
2513
|
run(t) {
|
|
2468
2514
|
var i;
|
|
2469
2515
|
const a = [], e = Array.from(t.querySelectorAll("iframe[title], frame[title]")), n = /* @__PURE__ */ new Map();
|
|
2470
|
-
for (const
|
|
2471
|
-
if (b(
|
|
2472
|
-
const
|
|
2473
|
-
if (
|
|
2474
|
-
const s = n.get(
|
|
2475
|
-
s.push(
|
|
2516
|
+
for (const o of e) {
|
|
2517
|
+
if (b(o) || xe(o)) continue;
|
|
2518
|
+
const r = (i = o.getAttribute("title")) == null ? void 0 : i.trim().toLowerCase();
|
|
2519
|
+
if (r) {
|
|
2520
|
+
const s = n.get(r) || [];
|
|
2521
|
+
s.push(o), n.set(r, s);
|
|
2476
2522
|
}
|
|
2477
2523
|
}
|
|
2478
|
-
for (const [,
|
|
2479
|
-
if (
|
|
2480
|
-
for (const
|
|
2524
|
+
for (const [, o] of n)
|
|
2525
|
+
if (o.length > 1)
|
|
2526
|
+
for (const r of o.slice(1))
|
|
2481
2527
|
a.push({
|
|
2482
2528
|
ruleId: "frame-title-unique",
|
|
2483
|
-
selector: m(
|
|
2484
|
-
html: u(
|
|
2529
|
+
selector: m(r),
|
|
2530
|
+
html: u(r),
|
|
2485
2531
|
impact: "moderate",
|
|
2486
2532
|
message: "Frame title is not unique. Use a distinct title for each frame."
|
|
2487
2533
|
});
|
|
2488
2534
|
return a;
|
|
2489
2535
|
}
|
|
2490
|
-
},
|
|
2536
|
+
}, ga = {
|
|
2491
2537
|
id: "empty-heading",
|
|
2492
2538
|
wcag: [],
|
|
2493
2539
|
level: "A",
|
|
@@ -2500,11 +2546,11 @@ const la = {
|
|
|
2500
2546
|
const a = [], e = t.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');
|
|
2501
2547
|
for (const i of e)
|
|
2502
2548
|
if (!b(i) && !v(i)) {
|
|
2503
|
-
let
|
|
2504
|
-
const
|
|
2505
|
-
if (
|
|
2506
|
-
const s = ((n =
|
|
2507
|
-
s && (
|
|
2549
|
+
let o;
|
|
2550
|
+
const r = i.nextElementSibling;
|
|
2551
|
+
if (r) {
|
|
2552
|
+
const s = ((n = r.textContent) == null ? void 0 : n.trim().replace(/\s+/g, " ")) || "";
|
|
2553
|
+
s && (o = s.slice(0, 100));
|
|
2508
2554
|
}
|
|
2509
2555
|
a.push({
|
|
2510
2556
|
ruleId: "empty-heading",
|
|
@@ -2512,12 +2558,12 @@ const la = {
|
|
|
2512
2558
|
html: u(i),
|
|
2513
2559
|
impact: "minor",
|
|
2514
2560
|
message: "Heading is empty. Add text content or remove the heading element.",
|
|
2515
|
-
context:
|
|
2561
|
+
context: o ? `Following content: "${o}"` : void 0
|
|
2516
2562
|
});
|
|
2517
2563
|
}
|
|
2518
2564
|
return a;
|
|
2519
2565
|
}
|
|
2520
|
-
},
|
|
2566
|
+
}, fa = {
|
|
2521
2567
|
id: "meta-viewport",
|
|
2522
2568
|
wcag: ["1.4.4"],
|
|
2523
2569
|
level: "AA",
|
|
@@ -2527,9 +2573,9 @@ const la = {
|
|
|
2527
2573
|
run(t) {
|
|
2528
2574
|
const a = [], e = t.querySelector('meta[name="viewport"]');
|
|
2529
2575
|
if (!e) return [];
|
|
2530
|
-
const n = e.getAttribute("content") || "", i = n.toLowerCase(),
|
|
2531
|
-
if (
|
|
2532
|
-
const s =
|
|
2576
|
+
const n = e.getAttribute("content") || "", i = n.toLowerCase(), o = i.match(/user-scalable\s*=\s*([^\s,;]+)/i);
|
|
2577
|
+
if (o) {
|
|
2578
|
+
const s = o[1], l = parseFloat(s);
|
|
2533
2579
|
(s === "no" || !isNaN(l) && l > -1 && l < 1) && a.push({
|
|
2534
2580
|
ruleId: "meta-viewport",
|
|
2535
2581
|
selector: m(e),
|
|
@@ -2539,9 +2585,9 @@ const la = {
|
|
|
2539
2585
|
context: `content: "${n}"`
|
|
2540
2586
|
});
|
|
2541
2587
|
}
|
|
2542
|
-
const
|
|
2543
|
-
if (
|
|
2544
|
-
const s =
|
|
2588
|
+
const r = i.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);
|
|
2589
|
+
if (r) {
|
|
2590
|
+
const s = r[1], l = s.toLowerCase() === "yes" ? 1 : parseFloat(s);
|
|
2545
2591
|
l < 2 && a.push({
|
|
2546
2592
|
ruleId: "meta-viewport",
|
|
2547
2593
|
selector: m(e),
|
|
@@ -2553,7 +2599,7 @@ const la = {
|
|
|
2553
2599
|
}
|
|
2554
2600
|
return a;
|
|
2555
2601
|
}
|
|
2556
|
-
},
|
|
2602
|
+
}, va = {
|
|
2557
2603
|
id: "meta-refresh",
|
|
2558
2604
|
wcag: ["2.2.1", "2.2.4", "3.2.5"],
|
|
2559
2605
|
level: "A",
|
|
@@ -2584,7 +2630,7 @@ const la = {
|
|
|
2584
2630
|
}
|
|
2585
2631
|
return [];
|
|
2586
2632
|
}
|
|
2587
|
-
},
|
|
2633
|
+
}, ya = {
|
|
2588
2634
|
id: "blink",
|
|
2589
2635
|
selector: "blink",
|
|
2590
2636
|
check: { type: "selector-exists" },
|
|
@@ -2595,7 +2641,7 @@ const la = {
|
|
|
2595
2641
|
level: "A",
|
|
2596
2642
|
guidance: "Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons.",
|
|
2597
2643
|
prompt: "Suggest static alternatives to the blinking effect."
|
|
2598
|
-
},
|
|
2644
|
+
}, wa = E(ya), Aa = {
|
|
2599
2645
|
id: "marquee",
|
|
2600
2646
|
selector: "marquee",
|
|
2601
2647
|
check: { type: "selector-exists" },
|
|
@@ -2606,7 +2652,7 @@ const la = {
|
|
|
2606
2652
|
level: "A",
|
|
2607
2653
|
guidance: "Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds.",
|
|
2608
2654
|
prompt: "Suggest static alternatives or accessible carousel patterns."
|
|
2609
|
-
},
|
|
2655
|
+
}, xa = E(Aa), ka = {
|
|
2610
2656
|
id: "p-as-heading",
|
|
2611
2657
|
wcag: [],
|
|
2612
2658
|
level: "A",
|
|
@@ -2619,8 +2665,8 @@ const la = {
|
|
|
2619
2665
|
const a = [];
|
|
2620
2666
|
for (const i of t.querySelectorAll("p")) {
|
|
2621
2667
|
if (b(i)) continue;
|
|
2622
|
-
const
|
|
2623
|
-
if ((
|
|
2668
|
+
const o = i.getAttribute("style") || "", r = /font-weight\s*:\s*(bold|[6-9]00)/i.test(o), s = /font-size\s*:\s*(\d+)\s*(px|em|rem)/i.test(o), l = ((e = i.className) == null ? void 0 : e.toLowerCase()) || "", p = /\bh[1-6]\b|\bheading\b/.test(l), c = ((n = i.textContent) == null ? void 0 : n.trim()) || "", d = c.length > 0 && c.length < 50, h = !c.match(/[.!?,;:]$/);
|
|
2669
|
+
if ((r && s || r && p) && d && h) {
|
|
2624
2670
|
const y = i.nextElementSibling;
|
|
2625
2671
|
y && (y.tagName === "P" || y.tagName === "DIV" || y.tagName === "UL") && a.push({
|
|
2626
2672
|
ruleId: "p-as-heading",
|
|
@@ -2633,7 +2679,7 @@ const la = {
|
|
|
2633
2679
|
}
|
|
2634
2680
|
return a;
|
|
2635
2681
|
}
|
|
2636
|
-
},
|
|
2682
|
+
}, Sa = {
|
|
2637
2683
|
id: "aria-roles",
|
|
2638
2684
|
wcag: ["4.1.2"],
|
|
2639
2685
|
level: "A",
|
|
@@ -2643,18 +2689,18 @@ const la = {
|
|
|
2643
2689
|
run(t) {
|
|
2644
2690
|
const a = [];
|
|
2645
2691
|
for (const e of t.querySelectorAll("[role]")) {
|
|
2646
|
-
const
|
|
2647
|
-
!
|
|
2692
|
+
const o = e.getAttribute("role").replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g, "").split(/\s+/).filter(Boolean);
|
|
2693
|
+
!o.some((s) => Ne(s)) && o.length > 0 && a.push({
|
|
2648
2694
|
ruleId: "aria-roles",
|
|
2649
2695
|
selector: m(e),
|
|
2650
2696
|
html: u(e),
|
|
2651
2697
|
impact: "critical",
|
|
2652
|
-
message: `Invalid ARIA role "${
|
|
2698
|
+
message: `Invalid ARIA role "${o[0]}".`
|
|
2653
2699
|
});
|
|
2654
2700
|
}
|
|
2655
2701
|
return a;
|
|
2656
2702
|
}
|
|
2657
|
-
},
|
|
2703
|
+
}, Ia = {
|
|
2658
2704
|
id: "aria-valid-attr",
|
|
2659
2705
|
wcag: ["4.1.2"],
|
|
2660
2706
|
level: "A",
|
|
@@ -2662,9 +2708,9 @@ const la = {
|
|
|
2662
2708
|
guidance: "Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+).",
|
|
2663
2709
|
prompt: "Identify the misspelled attribute and provide the correct spelling.",
|
|
2664
2710
|
run(t) {
|
|
2665
|
-
return
|
|
2711
|
+
return J(t).validAttr;
|
|
2666
2712
|
}
|
|
2667
|
-
},
|
|
2713
|
+
}, Ea = {
|
|
2668
2714
|
id: "aria-valid-attr-value",
|
|
2669
2715
|
wcag: ["4.1.2"],
|
|
2670
2716
|
level: "A",
|
|
@@ -2672,9 +2718,9 @@ const la = {
|
|
|
2672
2718
|
guidance: "Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs.",
|
|
2673
2719
|
prompt: "Show the invalid value and list the valid values for this specific attribute.",
|
|
2674
2720
|
run(t) {
|
|
2675
|
-
return
|
|
2721
|
+
return J(t).validAttrValue;
|
|
2676
2722
|
}
|
|
2677
|
-
},
|
|
2723
|
+
}, qa = {
|
|
2678
2724
|
checkbox: ["aria-checked"],
|
|
2679
2725
|
combobox: ["aria-expanded"],
|
|
2680
2726
|
heading: ["aria-level"],
|
|
@@ -2688,7 +2734,7 @@ const la = {
|
|
|
2688
2734
|
slider: ["aria-valuenow"],
|
|
2689
2735
|
spinbutton: ["aria-valuenow"],
|
|
2690
2736
|
switch: ["aria-checked"]
|
|
2691
|
-
},
|
|
2737
|
+
}, La = {
|
|
2692
2738
|
id: "aria-required-attr",
|
|
2693
2739
|
wcag: ["4.1.2"],
|
|
2694
2740
|
level: "A",
|
|
@@ -2699,21 +2745,21 @@ const la = {
|
|
|
2699
2745
|
const a = [];
|
|
2700
2746
|
for (const e of t.querySelectorAll("[role]")) {
|
|
2701
2747
|
if (b(e) || e instanceof HTMLElement && e.style.display === "none") continue;
|
|
2702
|
-
const n = e.getAttribute("role").trim().toLowerCase(), i =
|
|
2748
|
+
const n = e.getAttribute("role").trim().toLowerCase(), i = qa[n];
|
|
2703
2749
|
if (i && !(n === "checkbox" && e instanceof HTMLInputElement && e.type === "checkbox") && !(n === "radio" && e instanceof HTMLInputElement && e.type === "radio") && !(n === "option" && e instanceof HTMLOptionElement) && !(n === "heading" && /^h[1-6]$/i.test(e.tagName))) {
|
|
2704
2750
|
if (n === "separator") {
|
|
2705
|
-
const
|
|
2706
|
-
if (!
|
|
2751
|
+
const o = e.getAttribute("tabindex");
|
|
2752
|
+
if (!o || o === "-1") continue;
|
|
2707
2753
|
}
|
|
2708
2754
|
if (!(e.tagName.toLowerCase() === "hr" && !e.hasAttribute("role"))) {
|
|
2709
|
-
for (const
|
|
2710
|
-
if (!e.hasAttribute(
|
|
2755
|
+
for (const o of i)
|
|
2756
|
+
if (!e.hasAttribute(o)) {
|
|
2711
2757
|
a.push({
|
|
2712
2758
|
ruleId: "aria-required-attr",
|
|
2713
2759
|
selector: m(e),
|
|
2714
2760
|
html: u(e),
|
|
2715
2761
|
impact: "critical",
|
|
2716
|
-
message: `Role "${n}" requires attribute "${
|
|
2762
|
+
message: `Role "${n}" requires attribute "${o}".`
|
|
2717
2763
|
});
|
|
2718
2764
|
break;
|
|
2719
2765
|
}
|
|
@@ -2723,13 +2769,13 @@ const la = {
|
|
|
2723
2769
|
return a;
|
|
2724
2770
|
}
|
|
2725
2771
|
};
|
|
2726
|
-
function
|
|
2727
|
-
var
|
|
2772
|
+
function Ta(t) {
|
|
2773
|
+
var o, r, s;
|
|
2728
2774
|
const a = [], e = t.className;
|
|
2729
2775
|
e && typeof e == "string" && e.trim() && a.push(`Classes: ${e.trim().slice(0, 100)}`);
|
|
2730
2776
|
const n = t.closest("form");
|
|
2731
2777
|
if (n) {
|
|
2732
|
-
const l = n.getAttribute("aria-label") || ((
|
|
2778
|
+
const l = n.getAttribute("aria-label") || ((r = (o = n.querySelector("legend")) == null ? void 0 : o.textContent) == null ? void 0 : r.trim());
|
|
2733
2779
|
l && a.push(`Form: ${l.slice(0, 60)}`);
|
|
2734
2780
|
}
|
|
2735
2781
|
const i = t.parentElement;
|
|
@@ -2740,7 +2786,7 @@ function ka(t) {
|
|
|
2740
2786
|
return a.length > 0 ? a.join(`
|
|
2741
2787
|
`) : void 0;
|
|
2742
2788
|
}
|
|
2743
|
-
const
|
|
2789
|
+
const Ra = {
|
|
2744
2790
|
id: "button-name",
|
|
2745
2791
|
wcag: ["4.1.2"],
|
|
2746
2792
|
level: "A",
|
|
@@ -2759,12 +2805,12 @@ const Sa = {
|
|
|
2759
2805
|
html: u(e),
|
|
2760
2806
|
impact: "critical",
|
|
2761
2807
|
message: "Button has no discernible text.",
|
|
2762
|
-
context:
|
|
2808
|
+
context: Ta(e)
|
|
2763
2809
|
});
|
|
2764
2810
|
}
|
|
2765
2811
|
return a;
|
|
2766
2812
|
}
|
|
2767
|
-
},
|
|
2813
|
+
}, Ca = {
|
|
2768
2814
|
alert: /* @__PURE__ */ new Set(["aria-atomic", "aria-busy", "aria-live", "aria-relevant"]),
|
|
2769
2815
|
alertdialog: /* @__PURE__ */ new Set(["aria-describedby", "aria-modal"]),
|
|
2770
2816
|
application: /* @__PURE__ */ new Set(["aria-activedescendant", "aria-disabled", "aria-errormessage", "aria-expanded", "aria-haspopup", "aria-invalid"]),
|
|
@@ -2836,7 +2882,7 @@ const Sa = {
|
|
|
2836
2882
|
tree: /* @__PURE__ */ new Set(["aria-activedescendant", "aria-disabled", "aria-errormessage", "aria-invalid", "aria-multiselectable", "aria-orientation", "aria-required"]),
|
|
2837
2883
|
treegrid: /* @__PURE__ */ new Set(["aria-activedescendant", "aria-colcount", "aria-disabled", "aria-errormessage", "aria-invalid", "aria-multiselectable", "aria-orientation", "aria-readonly", "aria-required", "aria-rowcount"]),
|
|
2838
2884
|
treeitem: /* @__PURE__ */ new Set(["aria-checked", "aria-disabled", "aria-expanded", "aria-haspopup", "aria-level", "aria-posinset", "aria-selected", "aria-setsize"])
|
|
2839
|
-
},
|
|
2885
|
+
}, Na = /* @__PURE__ */ new Set([
|
|
2840
2886
|
"aria-atomic",
|
|
2841
2887
|
"aria-busy",
|
|
2842
2888
|
"aria-controls",
|
|
@@ -2860,7 +2906,7 @@ const Sa = {
|
|
|
2860
2906
|
"aria-roledescription",
|
|
2861
2907
|
"aria-braillelabel",
|
|
2862
2908
|
"aria-brailleroledescription"
|
|
2863
|
-
]),
|
|
2909
|
+
]), Ma = {
|
|
2864
2910
|
id: "aria-allowed-attr",
|
|
2865
2911
|
wcag: ["4.1.2"],
|
|
2866
2912
|
level: "A",
|
|
@@ -2871,26 +2917,26 @@ const Sa = {
|
|
|
2871
2917
|
const a = [];
|
|
2872
2918
|
for (const e of t.querySelectorAll("[role], [aria-*]")) {
|
|
2873
2919
|
if (b(e)) continue;
|
|
2874
|
-
const n =
|
|
2920
|
+
const n = N(e);
|
|
2875
2921
|
if (!n) continue;
|
|
2876
|
-
const i =
|
|
2922
|
+
const i = Ca[n];
|
|
2877
2923
|
if (i)
|
|
2878
|
-
for (const
|
|
2879
|
-
if (!
|
|
2880
|
-
const
|
|
2924
|
+
for (const o of e.attributes) {
|
|
2925
|
+
if (!o.name.startsWith("aria-") || Na.has(o.name) || i.has(o.name)) continue;
|
|
2926
|
+
const r = i.size > 0 ? [...i].join(", ") : "none (only global ARIA attributes)";
|
|
2881
2927
|
a.push({
|
|
2882
2928
|
ruleId: "aria-allowed-attr",
|
|
2883
2929
|
selector: m(e),
|
|
2884
2930
|
html: u(e),
|
|
2885
2931
|
impact: "critical",
|
|
2886
|
-
message: `ARIA attribute "${
|
|
2887
|
-
context: `Attribute: ${
|
|
2932
|
+
message: `ARIA attribute "${o.name}" is not allowed on role "${n}".`,
|
|
2933
|
+
context: `Attribute: ${o.name}="${o.value}", role: ${n}, allowed role-specific attributes: ${r}`
|
|
2888
2934
|
});
|
|
2889
2935
|
}
|
|
2890
2936
|
}
|
|
2891
2937
|
return a;
|
|
2892
2938
|
}
|
|
2893
|
-
},
|
|
2939
|
+
}, Da = /* @__PURE__ */ new Set([
|
|
2894
2940
|
"base",
|
|
2895
2941
|
"col",
|
|
2896
2942
|
"colgroup",
|
|
@@ -3012,10 +3058,10 @@ const Sa = {
|
|
|
3012
3058
|
video: /* @__PURE__ */ new Set(["application"]),
|
|
3013
3059
|
wbr: /* @__PURE__ */ new Set(["none", "presentation"])
|
|
3014
3060
|
};
|
|
3015
|
-
function
|
|
3061
|
+
function $a(t) {
|
|
3016
3062
|
var e;
|
|
3017
3063
|
const a = t.tagName.toLowerCase();
|
|
3018
|
-
if (
|
|
3064
|
+
if (Da.has(a))
|
|
3019
3065
|
return "none";
|
|
3020
3066
|
if (a === "a" && t.hasAttribute("href"))
|
|
3021
3067
|
return L["a[href]"];
|
|
@@ -3027,7 +3073,7 @@ function Ta(t) {
|
|
|
3027
3073
|
}
|
|
3028
3074
|
return L[a] || "any";
|
|
3029
3075
|
}
|
|
3030
|
-
const
|
|
3076
|
+
const Ha = {
|
|
3031
3077
|
id: "aria-allowed-role",
|
|
3032
3078
|
wcag: ["4.1.2"],
|
|
3033
3079
|
level: "A",
|
|
@@ -3041,16 +3087,16 @@ const Ca = {
|
|
|
3041
3087
|
if (b(n)) continue;
|
|
3042
3088
|
const i = (e = n.getAttribute("role")) == null ? void 0 : e.trim().toLowerCase();
|
|
3043
3089
|
if (!i) continue;
|
|
3044
|
-
const
|
|
3045
|
-
if (
|
|
3046
|
-
const
|
|
3047
|
-
|
|
3090
|
+
const o = ve(n);
|
|
3091
|
+
if (o && i === o) continue;
|
|
3092
|
+
const r = $a(n);
|
|
3093
|
+
r === "none" ? a.push({
|
|
3048
3094
|
ruleId: "aria-allowed-role",
|
|
3049
3095
|
selector: m(n),
|
|
3050
3096
|
html: u(n),
|
|
3051
3097
|
impact: "minor",
|
|
3052
3098
|
message: `Element <${n.tagName.toLowerCase()}> should not have an explicit role.`
|
|
3053
|
-
}) :
|
|
3099
|
+
}) : r !== "any" && !r.has(i) && a.push({
|
|
3054
3100
|
ruleId: "aria-allowed-role",
|
|
3055
3101
|
selector: m(n),
|
|
3056
3102
|
html: u(n),
|
|
@@ -3060,7 +3106,7 @@ const Ca = {
|
|
|
3060
3106
|
}
|
|
3061
3107
|
return a;
|
|
3062
3108
|
}
|
|
3063
|
-
},
|
|
3109
|
+
}, ce = {
|
|
3064
3110
|
// Each array is an OR group - at least one of each inner array must be present
|
|
3065
3111
|
combobox: [["listbox", "tree", "grid", "dialog", "textbox"]],
|
|
3066
3112
|
// Must own/contain one of these
|
|
@@ -3077,7 +3123,7 @@ const Ca = {
|
|
|
3077
3123
|
tablist: [["tab"]],
|
|
3078
3124
|
tree: [["treeitem", "group"]],
|
|
3079
3125
|
treegrid: [["row", "rowgroup"]]
|
|
3080
|
-
},
|
|
3126
|
+
}, Pa = /* @__PURE__ */ new Set([
|
|
3081
3127
|
"doc-bibliography",
|
|
3082
3128
|
"doc-endnotes",
|
|
3083
3129
|
"grid",
|
|
@@ -3089,7 +3135,7 @@ const Ca = {
|
|
|
3089
3135
|
"tablist",
|
|
3090
3136
|
"tree",
|
|
3091
3137
|
"treegrid"
|
|
3092
|
-
]),
|
|
3138
|
+
]), de = {
|
|
3093
3139
|
caption: ["figure", "table", "grid", "treegrid"],
|
|
3094
3140
|
cell: ["row"],
|
|
3095
3141
|
columnheader: ["row"],
|
|
@@ -3105,30 +3151,30 @@ const Ca = {
|
|
|
3105
3151
|
tab: ["tablist"],
|
|
3106
3152
|
treeitem: ["tree", "group"]
|
|
3107
3153
|
};
|
|
3108
|
-
function
|
|
3109
|
-
var
|
|
3110
|
-
const e = ((
|
|
3111
|
-
let
|
|
3154
|
+
function za(t, a) {
|
|
3155
|
+
var r;
|
|
3156
|
+
const e = ((r = t.getAttribute("aria-owns")) == null ? void 0 : r.split(/\s+/)) || [], n = t.ownerDocument, i = /* @__PURE__ */ new Set();
|
|
3157
|
+
let o = !1;
|
|
3112
3158
|
for (const s of t.querySelectorAll("*")) {
|
|
3113
3159
|
if (b(s)) continue;
|
|
3114
|
-
|
|
3115
|
-
const l =
|
|
3160
|
+
o = !0;
|
|
3161
|
+
const l = N(s);
|
|
3116
3162
|
l && i.add(l);
|
|
3117
3163
|
}
|
|
3118
3164
|
for (const s of e) {
|
|
3119
3165
|
const l = n.getElementById(s);
|
|
3120
3166
|
if (l && !b(l)) {
|
|
3121
|
-
|
|
3122
|
-
const p =
|
|
3167
|
+
o = !0;
|
|
3168
|
+
const p = N(l);
|
|
3123
3169
|
p && i.add(p);
|
|
3124
3170
|
}
|
|
3125
3171
|
}
|
|
3126
|
-
if (!
|
|
3172
|
+
if (!o) return "empty";
|
|
3127
3173
|
for (const s of a)
|
|
3128
3174
|
if (!s.some((l) => i.has(l))) return "fail";
|
|
3129
3175
|
return "pass";
|
|
3130
3176
|
}
|
|
3131
|
-
const
|
|
3177
|
+
const Fa = {
|
|
3132
3178
|
id: "aria-required-children",
|
|
3133
3179
|
wcag: ["1.3.1"],
|
|
3134
3180
|
level: "A",
|
|
@@ -3141,15 +3187,15 @@ const Ma = {
|
|
|
3141
3187
|
for (const n of t.querySelectorAll("[role]")) {
|
|
3142
3188
|
if (b(n)) continue;
|
|
3143
3189
|
const i = (e = n.getAttribute("role")) == null ? void 0 : e.trim().toLowerCase();
|
|
3144
|
-
if (!i || !(i in
|
|
3190
|
+
if (!i || !(i in ce) || n.getAttribute("aria-busy") === "true") continue;
|
|
3145
3191
|
if (i === "combobox") {
|
|
3146
3192
|
if (n.getAttribute("aria-expanded") !== "true") continue;
|
|
3147
3193
|
const l = n.tagName.toLowerCase();
|
|
3148
3194
|
if (l === "input" || l === "textarea") continue;
|
|
3149
3195
|
}
|
|
3150
|
-
const
|
|
3151
|
-
if (
|
|
3152
|
-
const s =
|
|
3196
|
+
const o = ce[i], r = za(n, o);
|
|
3197
|
+
if (r === "pass" || r === "empty" && Pa.has(i)) continue;
|
|
3198
|
+
const s = o.map((l) => l.join(" or ")).join(", ");
|
|
3153
3199
|
a.push({
|
|
3154
3200
|
ruleId: "aria-required-children",
|
|
3155
3201
|
selector: m(n),
|
|
@@ -3160,7 +3206,7 @@ const Ma = {
|
|
|
3160
3206
|
}
|
|
3161
3207
|
return a;
|
|
3162
3208
|
}
|
|
3163
|
-
},
|
|
3209
|
+
}, ja = {
|
|
3164
3210
|
id: "aria-required-parent",
|
|
3165
3211
|
wcag: ["1.3.1"],
|
|
3166
3212
|
level: "A",
|
|
@@ -3173,28 +3219,28 @@ const Ma = {
|
|
|
3173
3219
|
for (const n of t.querySelectorAll("[role]")) {
|
|
3174
3220
|
if (b(n)) continue;
|
|
3175
3221
|
const i = (e = n.getAttribute("role")) == null ? void 0 : e.trim().toLowerCase();
|
|
3176
|
-
if (!i || !(i in
|
|
3177
|
-
const
|
|
3178
|
-
let
|
|
3179
|
-
for (;
|
|
3180
|
-
const l =
|
|
3181
|
-
if (l &&
|
|
3222
|
+
if (!i || !(i in de)) continue;
|
|
3223
|
+
const o = de[i];
|
|
3224
|
+
let r = n.parentElement, s = !1;
|
|
3225
|
+
for (; r && r !== t.documentElement; ) {
|
|
3226
|
+
const l = N(r);
|
|
3227
|
+
if (l && o.includes(l)) {
|
|
3182
3228
|
s = !0;
|
|
3183
3229
|
break;
|
|
3184
3230
|
}
|
|
3185
|
-
|
|
3231
|
+
r = r.parentElement;
|
|
3186
3232
|
}
|
|
3187
3233
|
s || a.push({
|
|
3188
3234
|
ruleId: "aria-required-parent",
|
|
3189
3235
|
selector: m(n),
|
|
3190
3236
|
html: u(n),
|
|
3191
3237
|
impact: "critical",
|
|
3192
|
-
message: `Role "${i}" must be contained within: ${
|
|
3238
|
+
message: `Role "${i}" must be contained within: ${o.join(", ")}.`
|
|
3193
3239
|
});
|
|
3194
3240
|
}
|
|
3195
3241
|
return a;
|
|
3196
3242
|
}
|
|
3197
|
-
},
|
|
3243
|
+
}, ue = [
|
|
3198
3244
|
"a[href]",
|
|
3199
3245
|
"button:not([disabled])",
|
|
3200
3246
|
'input:not([disabled]):not([type="hidden"])',
|
|
@@ -3210,7 +3256,7 @@ const Ma = {
|
|
|
3210
3256
|
"embed",
|
|
3211
3257
|
"area[href]"
|
|
3212
3258
|
].join(", ");
|
|
3213
|
-
function
|
|
3259
|
+
function Wa(t) {
|
|
3214
3260
|
let a = t;
|
|
3215
3261
|
const e = t.ownerDocument, n = e.defaultView;
|
|
3216
3262
|
for (; a && a !== e.body; ) {
|
|
@@ -3223,7 +3269,7 @@ function Ha(t) {
|
|
|
3223
3269
|
}
|
|
3224
3270
|
return !0;
|
|
3225
3271
|
}
|
|
3226
|
-
const
|
|
3272
|
+
const Ua = {
|
|
3227
3273
|
id: "aria-hidden-body",
|
|
3228
3274
|
selector: 'body[aria-hidden="true"]',
|
|
3229
3275
|
check: { type: "selector-exists" },
|
|
@@ -3235,7 +3281,7 @@ const Da = {
|
|
|
3235
3281
|
guidance: "Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead.",
|
|
3236
3282
|
prompt: "Instruct to remove aria-hidden='true' from the body element.",
|
|
3237
3283
|
skipAriaHidden: !1
|
|
3238
|
-
},
|
|
3284
|
+
}, Ba = E(Ua), Oa = {
|
|
3239
3285
|
id: "aria-hidden-focus",
|
|
3240
3286
|
wcag: ["4.1.2"],
|
|
3241
3287
|
level: "A",
|
|
@@ -3246,15 +3292,15 @@ const Da = {
|
|
|
3246
3292
|
const a = [];
|
|
3247
3293
|
for (const e of t.querySelectorAll('[aria-hidden="true"]')) {
|
|
3248
3294
|
if (e === t.body) continue;
|
|
3249
|
-
const n = [...e.querySelectorAll(
|
|
3250
|
-
e.matches(
|
|
3295
|
+
const n = [...e.querySelectorAll(ue)];
|
|
3296
|
+
e.matches(ue) && n.push(e);
|
|
3251
3297
|
for (const i of n)
|
|
3252
3298
|
if (i instanceof HTMLElement) {
|
|
3253
|
-
const
|
|
3254
|
-
if (
|
|
3255
|
-
const
|
|
3299
|
+
const o = i.getAttribute("tabindex");
|
|
3300
|
+
if (o === "-1" || i.disabled || i instanceof HTMLInputElement && i.type === "hidden" || !Wa(i)) continue;
|
|
3301
|
+
const r = i.tagName.toLowerCase();
|
|
3256
3302
|
let s;
|
|
3257
|
-
|
|
3303
|
+
o !== null ? s = `has tabindex="${o}"` : r === "a" && i.hasAttribute("href") ? s = "is a link with href" : r === "button" ? s = "is a <button>" : r === "input" ? s = `is an <input type="${i.type}">` : r === "select" ? s = "is a <select>" : r === "textarea" ? s = "is a <textarea>" : r === "iframe" ? s = "is an <iframe>" : s = `is a natively focusable <${r}>`;
|
|
3258
3304
|
const l = i === e ? i : i.closest('[aria-hidden="true"]');
|
|
3259
3305
|
a.push({
|
|
3260
3306
|
ruleId: "aria-hidden-focus",
|
|
@@ -3268,7 +3314,7 @@ const Da = {
|
|
|
3268
3314
|
}
|
|
3269
3315
|
return a;
|
|
3270
3316
|
}
|
|
3271
|
-
},
|
|
3317
|
+
}, Va = {
|
|
3272
3318
|
id: "aria-command-name",
|
|
3273
3319
|
wcag: ["4.1.2"],
|
|
3274
3320
|
level: "A",
|
|
@@ -3281,8 +3327,8 @@ const Da = {
|
|
|
3281
3327
|
for (const n of t.querySelectorAll('[role="button"], [role="link"], [role="menuitem"]')) {
|
|
3282
3328
|
if (b(n) || q(n) || n.getRootNode() instanceof ShadowRoot || n.tagName.toLowerCase() === "button" || n.tagName.toLowerCase() === "a") continue;
|
|
3283
3329
|
if (!v(n)) {
|
|
3284
|
-
const
|
|
3285
|
-
if ((e =
|
|
3330
|
+
const o = n.querySelector("img[alt]");
|
|
3331
|
+
if ((e = o == null ? void 0 : o.getAttribute("alt")) != null && e.trim()) continue;
|
|
3286
3332
|
a.push({
|
|
3287
3333
|
ruleId: "aria-command-name",
|
|
3288
3334
|
selector: m(n),
|
|
@@ -3294,7 +3340,7 @@ const Da = {
|
|
|
3294
3340
|
}
|
|
3295
3341
|
return a;
|
|
3296
3342
|
}
|
|
3297
|
-
},
|
|
3343
|
+
}, _a = {
|
|
3298
3344
|
id: "aria-input-field-name",
|
|
3299
3345
|
wcag: ["4.1.2"],
|
|
3300
3346
|
level: "A",
|
|
@@ -3315,7 +3361,7 @@ const Da = {
|
|
|
3315
3361
|
}
|
|
3316
3362
|
return a;
|
|
3317
3363
|
}
|
|
3318
|
-
},
|
|
3364
|
+
}, Ga = {
|
|
3319
3365
|
id: "aria-toggle-field-name",
|
|
3320
3366
|
wcag: ["4.1.2"],
|
|
3321
3367
|
level: "A",
|
|
@@ -3336,7 +3382,7 @@ const Da = {
|
|
|
3336
3382
|
}
|
|
3337
3383
|
return a;
|
|
3338
3384
|
}
|
|
3339
|
-
},
|
|
3385
|
+
}, Ya = {
|
|
3340
3386
|
id: "aria-meter-name",
|
|
3341
3387
|
wcag: ["4.1.2"],
|
|
3342
3388
|
level: "A",
|
|
@@ -3357,7 +3403,7 @@ const Da = {
|
|
|
3357
3403
|
}
|
|
3358
3404
|
return a;
|
|
3359
3405
|
}
|
|
3360
|
-
},
|
|
3406
|
+
}, Xa = {
|
|
3361
3407
|
id: "aria-progressbar-name",
|
|
3362
3408
|
wcag: ["4.1.2"],
|
|
3363
3409
|
level: "A",
|
|
@@ -3378,7 +3424,7 @@ const Da = {
|
|
|
3378
3424
|
}
|
|
3379
3425
|
return a;
|
|
3380
3426
|
}
|
|
3381
|
-
},
|
|
3427
|
+
}, Ka = {
|
|
3382
3428
|
id: "aria-dialog-name",
|
|
3383
3429
|
wcag: ["4.1.2"],
|
|
3384
3430
|
level: "A",
|
|
@@ -3399,7 +3445,7 @@ const Da = {
|
|
|
3399
3445
|
}
|
|
3400
3446
|
return a;
|
|
3401
3447
|
}
|
|
3402
|
-
},
|
|
3448
|
+
}, Ja = {
|
|
3403
3449
|
id: "aria-tooltip-name",
|
|
3404
3450
|
wcag: ["4.1.2"],
|
|
3405
3451
|
level: "A",
|
|
@@ -3420,7 +3466,7 @@ const Da = {
|
|
|
3420
3466
|
}
|
|
3421
3467
|
return a;
|
|
3422
3468
|
}
|
|
3423
|
-
},
|
|
3469
|
+
}, Qa = {
|
|
3424
3470
|
id: "aria-treeitem-name",
|
|
3425
3471
|
wcag: ["4.1.2"],
|
|
3426
3472
|
level: "A",
|
|
@@ -3441,7 +3487,7 @@ const Da = {
|
|
|
3441
3487
|
}
|
|
3442
3488
|
return a;
|
|
3443
3489
|
}
|
|
3444
|
-
},
|
|
3490
|
+
}, Za = {
|
|
3445
3491
|
id: "aria-prohibited-attr",
|
|
3446
3492
|
wcag: ["4.1.2"],
|
|
3447
3493
|
level: "A",
|
|
@@ -3449,16 +3495,16 @@ const Da = {
|
|
|
3449
3495
|
guidance: "Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role.",
|
|
3450
3496
|
prompt: "Identify the prohibited attribute and recommend removing it from this element.",
|
|
3451
3497
|
run(t) {
|
|
3452
|
-
return
|
|
3498
|
+
return J(t).prohibitedAttr;
|
|
3453
3499
|
}
|
|
3454
|
-
},
|
|
3500
|
+
}, en = [
|
|
3455
3501
|
"a[href]",
|
|
3456
3502
|
"button:not([disabled])",
|
|
3457
3503
|
'input:not([disabled]):not([type="hidden"])',
|
|
3458
3504
|
"select:not([disabled])",
|
|
3459
3505
|
"textarea:not([disabled])",
|
|
3460
3506
|
'[tabindex]:not([tabindex="-1"])'
|
|
3461
|
-
].join(", "),
|
|
3507
|
+
].join(", "), tn = [
|
|
3462
3508
|
"aria-atomic",
|
|
3463
3509
|
"aria-busy",
|
|
3464
3510
|
"aria-controls",
|
|
@@ -3473,17 +3519,17 @@ const Da = {
|
|
|
3473
3519
|
"aria-owns",
|
|
3474
3520
|
"aria-relevant"
|
|
3475
3521
|
];
|
|
3476
|
-
function
|
|
3522
|
+
function me(t) {
|
|
3477
3523
|
const a = [];
|
|
3478
|
-
t.matches(
|
|
3479
|
-
for (const e of
|
|
3524
|
+
t.matches(en) && a.push("element is focusable");
|
|
3525
|
+
for (const e of tn)
|
|
3480
3526
|
if (t.hasAttribute(e)) {
|
|
3481
3527
|
a.push(`has ${e}`);
|
|
3482
3528
|
break;
|
|
3483
3529
|
}
|
|
3484
3530
|
return (t.hasAttribute("aria-label") || t.hasAttribute("aria-labelledby")) && a.push("has accessible name"), a;
|
|
3485
3531
|
}
|
|
3486
|
-
const
|
|
3532
|
+
const an = {
|
|
3487
3533
|
id: "presentation-role-conflict",
|
|
3488
3534
|
wcag: ["4.1.2"],
|
|
3489
3535
|
level: "A",
|
|
@@ -3494,7 +3540,7 @@ const Ka = {
|
|
|
3494
3540
|
const a = [];
|
|
3495
3541
|
for (const e of t.querySelectorAll('[role="presentation"], [role="none"]')) {
|
|
3496
3542
|
if (b(e)) continue;
|
|
3497
|
-
const n =
|
|
3543
|
+
const n = me(e);
|
|
3498
3544
|
n.length > 0 && a.push({
|
|
3499
3545
|
ruleId: "presentation-role-conflict",
|
|
3500
3546
|
selector: m(e),
|
|
@@ -3505,7 +3551,7 @@ const Ka = {
|
|
|
3505
3551
|
}
|
|
3506
3552
|
for (const e of t.querySelectorAll('img[alt=""]')) {
|
|
3507
3553
|
if (b(e) || e.hasAttribute("role")) continue;
|
|
3508
|
-
const n =
|
|
3554
|
+
const n = me(e);
|
|
3509
3555
|
n.length > 0 && a.push({
|
|
3510
3556
|
ruleId: "presentation-role-conflict",
|
|
3511
3557
|
selector: m(e),
|
|
@@ -3516,7 +3562,7 @@ const Ka = {
|
|
|
3516
3562
|
}
|
|
3517
3563
|
return a;
|
|
3518
3564
|
}
|
|
3519
|
-
},
|
|
3565
|
+
}, nn = {
|
|
3520
3566
|
id: "summary-name",
|
|
3521
3567
|
wcag: ["4.1.2"],
|
|
3522
3568
|
level: "A",
|
|
@@ -3538,24 +3584,24 @@ const Ka = {
|
|
|
3538
3584
|
return a;
|
|
3539
3585
|
}
|
|
3540
3586
|
};
|
|
3541
|
-
function
|
|
3542
|
-
var i,
|
|
3587
|
+
function on(t) {
|
|
3588
|
+
var i, o;
|
|
3543
3589
|
const a = [], e = t.getAttribute("href");
|
|
3544
3590
|
e && a.push(`href: ${e}`);
|
|
3545
3591
|
const n = t.parentElement;
|
|
3546
3592
|
if (n) {
|
|
3547
|
-
const
|
|
3548
|
-
if ((i =
|
|
3549
|
-
a.push(`Nearby heading: ${
|
|
3593
|
+
const r = n.closest("h1, h2, h3, h4, h5, h6");
|
|
3594
|
+
if ((i = r == null ? void 0 : r.textContent) != null && i.trim())
|
|
3595
|
+
a.push(`Nearby heading: ${r.textContent.trim().slice(0, 80)}`);
|
|
3550
3596
|
else {
|
|
3551
|
-
const s = (
|
|
3597
|
+
const s = (o = n.textContent) == null ? void 0 : o.trim().slice(0, 100);
|
|
3552
3598
|
s && a.push(`Parent text: ${s}`);
|
|
3553
3599
|
}
|
|
3554
3600
|
}
|
|
3555
3601
|
return a.length > 0 ? a.join(`
|
|
3556
3602
|
`) : void 0;
|
|
3557
3603
|
}
|
|
3558
|
-
const
|
|
3604
|
+
const rn = {
|
|
3559
3605
|
id: "link-name",
|
|
3560
3606
|
wcag: ["2.4.4", "4.1.2"],
|
|
3561
3607
|
level: "A",
|
|
@@ -3572,12 +3618,12 @@ const Za = {
|
|
|
3572
3618
|
html: u(e),
|
|
3573
3619
|
impact: "serious",
|
|
3574
3620
|
message: "Link has no discernible text.",
|
|
3575
|
-
context:
|
|
3621
|
+
context: on(e)
|
|
3576
3622
|
});
|
|
3577
3623
|
}
|
|
3578
3624
|
return a;
|
|
3579
3625
|
}
|
|
3580
|
-
},
|
|
3626
|
+
}, sn = {
|
|
3581
3627
|
id: "skip-link",
|
|
3582
3628
|
wcag: ["2.4.1"],
|
|
3583
3629
|
level: "A",
|
|
@@ -3590,8 +3636,8 @@ const Za = {
|
|
|
3590
3636
|
for (const n of e) {
|
|
3591
3637
|
const i = n.getAttribute("href");
|
|
3592
3638
|
if (!i || i === "#") continue;
|
|
3593
|
-
const
|
|
3594
|
-
if (!(
|
|
3639
|
+
const o = A(n).toLowerCase();
|
|
3640
|
+
if (!(o.includes("skip") || o.includes("jump") || o.includes("main content") || o.includes("navigation"))) continue;
|
|
3595
3641
|
const s = i.slice(1);
|
|
3596
3642
|
t.getElementById(s) || a.push({
|
|
3597
3643
|
ruleId: "skip-link",
|
|
@@ -3603,7 +3649,7 @@ const Za = {
|
|
|
3603
3649
|
}
|
|
3604
3650
|
return a;
|
|
3605
3651
|
}
|
|
3606
|
-
},
|
|
3652
|
+
}, ln = /* @__PURE__ */ new Set([
|
|
3607
3653
|
"block",
|
|
3608
3654
|
"flex",
|
|
3609
3655
|
"grid",
|
|
@@ -3611,23 +3657,23 @@ const Za = {
|
|
|
3611
3657
|
"table-cell",
|
|
3612
3658
|
"list-item",
|
|
3613
3659
|
"flow-root"
|
|
3614
|
-
]),
|
|
3660
|
+
]), cn = /* @__PURE__ */ new Set([
|
|
3615
3661
|
"inline",
|
|
3616
3662
|
"inline-block",
|
|
3617
3663
|
"inline-flex",
|
|
3618
3664
|
"inline-grid"
|
|
3619
3665
|
]);
|
|
3620
|
-
function
|
|
3666
|
+
function dn(t) {
|
|
3621
3667
|
let a = t.parentElement;
|
|
3622
3668
|
for (; a; ) {
|
|
3623
3669
|
const e = w(a).display;
|
|
3624
|
-
if (
|
|
3625
|
-
return
|
|
3670
|
+
if (ln.has(e))
|
|
3671
|
+
return un(a) ? a : null;
|
|
3626
3672
|
a = a.parentElement;
|
|
3627
3673
|
}
|
|
3628
3674
|
return null;
|
|
3629
3675
|
}
|
|
3630
|
-
function
|
|
3676
|
+
function un(t) {
|
|
3631
3677
|
const a = t.ownerDocument.createTreeWalker(
|
|
3632
3678
|
t,
|
|
3633
3679
|
NodeFilter.SHOW_TEXT
|
|
@@ -3635,19 +3681,19 @@ function rn(t) {
|
|
|
3635
3681
|
let e = "", n;
|
|
3636
3682
|
for (; n = a.nextNode(); ) {
|
|
3637
3683
|
if (!n.data.trim()) continue;
|
|
3638
|
-
let i = n.parentElement,
|
|
3684
|
+
let i = n.parentElement, o = !1;
|
|
3639
3685
|
for (; i && i !== t; ) {
|
|
3640
3686
|
if (i.tagName === "A") {
|
|
3641
|
-
|
|
3687
|
+
o = !0;
|
|
3642
3688
|
break;
|
|
3643
3689
|
}
|
|
3644
3690
|
i = i.parentElement;
|
|
3645
3691
|
}
|
|
3646
|
-
|
|
3692
|
+
o || (e += n.data);
|
|
3647
3693
|
}
|
|
3648
3694
|
return new RegExp("\\p{L}{2,}", "u").test(e);
|
|
3649
3695
|
}
|
|
3650
|
-
function
|
|
3696
|
+
function mn(t, a) {
|
|
3651
3697
|
const e = t.ownerDocument.createTreeWalker(
|
|
3652
3698
|
t,
|
|
3653
3699
|
NodeFilter.SHOW_TEXT
|
|
@@ -3655,25 +3701,25 @@ function on(t, a) {
|
|
|
3655
3701
|
let n;
|
|
3656
3702
|
for (; n = e.nextNode(); ) {
|
|
3657
3703
|
if (!n.data.trim()) continue;
|
|
3658
|
-
let i = n.parentElement,
|
|
3659
|
-
for (;
|
|
3660
|
-
if (
|
|
3661
|
-
|
|
3704
|
+
let i = n.parentElement, o = !1, r = i;
|
|
3705
|
+
for (; r && r !== t; ) {
|
|
3706
|
+
if (r.tagName === "A") {
|
|
3707
|
+
o = !0;
|
|
3662
3708
|
break;
|
|
3663
3709
|
}
|
|
3664
|
-
|
|
3710
|
+
r = r.parentElement;
|
|
3665
3711
|
}
|
|
3666
|
-
if (!
|
|
3667
|
-
return
|
|
3712
|
+
if (!o && i)
|
|
3713
|
+
return D(w(i).color);
|
|
3668
3714
|
}
|
|
3669
3715
|
return null;
|
|
3670
3716
|
}
|
|
3671
|
-
function
|
|
3717
|
+
function pn(t, a, e) {
|
|
3672
3718
|
const n = e.textDecorationLine || e.textDecoration || "", i = a.textDecorationLine || a.textDecoration || "";
|
|
3673
3719
|
if ((i.includes("underline") || i.includes("line-through")) && i !== n)
|
|
3674
3720
|
return !0;
|
|
3675
|
-
const
|
|
3676
|
-
if (
|
|
3721
|
+
const o = parseFloat(a.borderBottomWidth) || 0, r = a.borderBottomStyle || "";
|
|
3722
|
+
if (o > 0 && r !== "none" && r !== "hidden")
|
|
3677
3723
|
return !0;
|
|
3678
3724
|
const s = parseFloat(a.outlineWidth) || 0, l = a.outlineStyle || "";
|
|
3679
3725
|
if (s > 0 && l !== "none")
|
|
@@ -3681,7 +3727,7 @@ function sn(t, a, e) {
|
|
|
3681
3727
|
const p = a.backgroundImage || "";
|
|
3682
3728
|
if (p && p !== "none" && p !== "initial")
|
|
3683
3729
|
return !0;
|
|
3684
|
-
const c =
|
|
3730
|
+
const c = j(e.fontWeight), d = j(a.fontWeight);
|
|
3685
3731
|
if (Math.abs(d - c) >= 300 || a.fontStyle !== e.fontStyle)
|
|
3686
3732
|
return !0;
|
|
3687
3733
|
const h = parseFloat(a.fontSize) || 16, g = parseFloat(e.fontSize) || 16;
|
|
@@ -3689,18 +3735,18 @@ function sn(t, a, e) {
|
|
|
3689
3735
|
return !0;
|
|
3690
3736
|
for (const f of t.querySelectorAll("*")) {
|
|
3691
3737
|
const y = w(f), x = y.textDecorationLine || y.textDecoration || "";
|
|
3692
|
-
if ((x.includes("underline") || x.includes("line-through")) && x !== n || Math.abs(
|
|
3738
|
+
if ((x.includes("underline") || x.includes("line-through")) && x !== n || Math.abs(j(y.fontWeight) - c) >= 300)
|
|
3693
3739
|
return !0;
|
|
3694
3740
|
}
|
|
3695
3741
|
return !1;
|
|
3696
3742
|
}
|
|
3697
|
-
function
|
|
3743
|
+
function j(t) {
|
|
3698
3744
|
return t === "bold" ? 700 : t === "normal" ? 400 : parseInt(t) || 400;
|
|
3699
3745
|
}
|
|
3700
|
-
function
|
|
3746
|
+
function pe(t, a, e) {
|
|
3701
3747
|
return "#" + [t, a, e].map((n) => n.toString(16).padStart(2, "0")).join("");
|
|
3702
3748
|
}
|
|
3703
|
-
const
|
|
3749
|
+
const hn = {
|
|
3704
3750
|
id: "link-in-text-block",
|
|
3705
3751
|
wcag: ["1.4.1"],
|
|
3706
3752
|
level: "A",
|
|
@@ -3712,16 +3758,16 @@ const ln = {
|
|
|
3712
3758
|
for (const e of t.querySelectorAll("a[href]")) {
|
|
3713
3759
|
if (b(e) || !A(e).trim() || e.closest('nav, header, footer, [role="navigation"], [role="banner"], [role="contentinfo"]')) continue;
|
|
3714
3760
|
const n = w(e), i = n.display || "inline";
|
|
3715
|
-
if (!
|
|
3716
|
-
const
|
|
3717
|
-
if (!
|
|
3718
|
-
const
|
|
3719
|
-
if (
|
|
3720
|
-
const s =
|
|
3761
|
+
if (!cn.has(i)) continue;
|
|
3762
|
+
const o = dn(e);
|
|
3763
|
+
if (!o) continue;
|
|
3764
|
+
const r = w(o);
|
|
3765
|
+
if (pn(e, n, r)) continue;
|
|
3766
|
+
const s = D(n.color), l = mn(o);
|
|
3721
3767
|
if (!s || !l) continue;
|
|
3722
|
-
const p =
|
|
3768
|
+
const p = M(...s), c = M(...l), d = we(p, c);
|
|
3723
3769
|
if (d < 1.1 || d >= 3) continue;
|
|
3724
|
-
const h =
|
|
3770
|
+
const h = pe(...s), g = pe(...l), f = `link color: ${h} rgb(${s.join(", ")}), surrounding text: ${g} rgb(${l.join(", ")}), ratio: ${d.toFixed(2)}:1`;
|
|
3725
3771
|
a.push({
|
|
3726
3772
|
ruleId: "link-in-text-block",
|
|
3727
3773
|
selector: m(e),
|
|
@@ -3733,7 +3779,7 @@ const ln = {
|
|
|
3733
3779
|
}
|
|
3734
3780
|
return a;
|
|
3735
3781
|
}
|
|
3736
|
-
},
|
|
3782
|
+
}, bn = {
|
|
3737
3783
|
id: "html-has-lang",
|
|
3738
3784
|
wcag: ["3.1.1"],
|
|
3739
3785
|
level: "A",
|
|
@@ -3747,14 +3793,14 @@ const ln = {
|
|
|
3747
3793
|
if (!t.doctype && t.body) {
|
|
3748
3794
|
const i = t.body.children;
|
|
3749
3795
|
if (i.length > 0 && Array.from(i).every(
|
|
3750
|
-
(
|
|
3796
|
+
(o) => o.tagName.toLowerCase() === "svg" || o.tagName.toLowerCase() === "math"
|
|
3751
3797
|
)) return [];
|
|
3752
3798
|
}
|
|
3753
3799
|
if (!((e = a.getAttribute("lang")) != null && e.trim())) {
|
|
3754
3800
|
let i;
|
|
3755
3801
|
if (t.body) {
|
|
3756
|
-
const
|
|
3757
|
-
|
|
3802
|
+
const o = ((n = t.body.textContent) == null ? void 0 : n.trim().replace(/\s+/g, " ")) || "";
|
|
3803
|
+
o && (i = o.slice(0, 200));
|
|
3758
3804
|
}
|
|
3759
3805
|
return [{
|
|
3760
3806
|
ruleId: "html-has-lang",
|
|
@@ -3767,17 +3813,17 @@ const ln = {
|
|
|
3767
3813
|
}
|
|
3768
3814
|
return [];
|
|
3769
3815
|
}
|
|
3770
|
-
},
|
|
3816
|
+
}, gn = new Set(
|
|
3771
3817
|
"aa ab ae af ak am an ar as av ay az ba be bg bh bi bm bn bo br bs ca ce ch co cr cs cu cv cy da de dv dz ee el en eo es et eu fa ff fi fj fo fr fy ga gd gl gn gu gv ha he hi ho hr ht hu hy hz ia id ie ig ii ik io is it iu ja jv ka kg ki kj kk kl km kn ko kr ks ku kv kw ky la lb lg li ln lo lt lu lv mg mh mi mk ml mn mr ms mt my na nb nd ne ng nl nn no nr nv ny oc oj om or os pa pi pl ps pt qu rm rn ro ru rw sa sc sd se sg si sk sl sm sn so sq sr ss st su sv sw ta te tg th ti tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa wo xh yi yo za zh zu".split(" ")
|
|
3772
|
-
),
|
|
3818
|
+
), fn = new Set(
|
|
3773
3819
|
"aar abk afr aka amh ara arg asm ava ave aym aze bak bam bel ben bih bis bod bos bre bul cat ces cha che chu chv cor cos cre cym dan deu div dzo ell eng epo est eus ewe fao fas fij fin fra fry ful gla gle glg glv grn guj hat hau hbs heb her hin hmo hrv hun hye ibo iii iku ile ina ind ipk isl ita jav jpn kal kan kas kat kau kaz khm kik kin kir kom kon kor kua kur lao lat lav lim lin lit ltz lub lug mah mal mar mkd mlg mlt mon mri msa mya nau nav nbl nde ndo nep nld nno nob nor nya oci oji ori orm oss pan pli pol por pus que roh ron run rus sag san sin slk slv sme smo sna snd som sot spa sqi srd srp ssw sun swa swe tah tam tat tel tgk tgl tha tir ton tsn tso tuk tur twi uig ukr urd uzb ven vie vol wln wol xho yid yor zha zho zul".split(" ")
|
|
3774
|
-
),
|
|
3775
|
-
function
|
|
3776
|
-
if (!
|
|
3820
|
+
), vn = /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;
|
|
3821
|
+
function ke(t) {
|
|
3822
|
+
if (!vn.test(t)) return !1;
|
|
3777
3823
|
const a = t.split("-")[0].toLowerCase();
|
|
3778
|
-
return a.length === 2 ?
|
|
3824
|
+
return a.length === 2 ? gn.has(a) : a.length === 3 ? !fn.has(a) : !1;
|
|
3779
3825
|
}
|
|
3780
|
-
const
|
|
3826
|
+
const yn = {
|
|
3781
3827
|
id: "html-lang-valid",
|
|
3782
3828
|
wcag: ["3.1.1"],
|
|
3783
3829
|
level: "A",
|
|
@@ -3787,7 +3833,7 @@ const pn = {
|
|
|
3787
3833
|
run(t) {
|
|
3788
3834
|
var e;
|
|
3789
3835
|
const a = (e = t.documentElement.getAttribute("lang")) == null ? void 0 : e.trim();
|
|
3790
|
-
return a && !
|
|
3836
|
+
return a && !ke(a) ? [{
|
|
3791
3837
|
ruleId: "html-lang-valid",
|
|
3792
3838
|
selector: "html",
|
|
3793
3839
|
html: u(t.documentElement),
|
|
@@ -3796,7 +3842,7 @@ const pn = {
|
|
|
3796
3842
|
}] : [];
|
|
3797
3843
|
}
|
|
3798
3844
|
};
|
|
3799
|
-
function
|
|
3845
|
+
function he(t) {
|
|
3800
3846
|
var n;
|
|
3801
3847
|
const a = t.ownerDocument.createTreeWalker(t, NodeFilter.SHOW_TEXT);
|
|
3802
3848
|
let e;
|
|
@@ -3804,31 +3850,31 @@ function me(t) {
|
|
|
3804
3850
|
if (!e.data.trim()) continue;
|
|
3805
3851
|
const i = e.parentElement;
|
|
3806
3852
|
if (!i || i instanceof HTMLElement && (i.hidden || i.style.display === "none")) continue;
|
|
3807
|
-
let
|
|
3808
|
-
for (;
|
|
3809
|
-
if (
|
|
3810
|
-
|
|
3853
|
+
let o = i, r = !1;
|
|
3854
|
+
for (; o && o !== t; ) {
|
|
3855
|
+
if (o.hasAttribute("lang")) {
|
|
3856
|
+
r = !0;
|
|
3811
3857
|
break;
|
|
3812
3858
|
}
|
|
3813
|
-
|
|
3859
|
+
o = o.parentElement;
|
|
3814
3860
|
}
|
|
3815
|
-
if (!
|
|
3861
|
+
if (!r) return !0;
|
|
3816
3862
|
}
|
|
3817
3863
|
for (const i of t.querySelectorAll("img[alt]")) {
|
|
3818
3864
|
if (!((n = i.getAttribute("alt")) == null ? void 0 : n.trim())) continue;
|
|
3819
|
-
let
|
|
3820
|
-
for (;
|
|
3821
|
-
if (
|
|
3865
|
+
let r = i.parentElement, s = !1;
|
|
3866
|
+
for (; r && r !== t; ) {
|
|
3867
|
+
if (r.hasAttribute("lang")) {
|
|
3822
3868
|
s = !0;
|
|
3823
3869
|
break;
|
|
3824
3870
|
}
|
|
3825
|
-
|
|
3871
|
+
r = r.parentElement;
|
|
3826
3872
|
}
|
|
3827
3873
|
if (!s) return !0;
|
|
3828
3874
|
}
|
|
3829
3875
|
return !1;
|
|
3830
3876
|
}
|
|
3831
|
-
const
|
|
3877
|
+
const wn = {
|
|
3832
3878
|
id: "valid-lang",
|
|
3833
3879
|
wcag: ["3.1.2"],
|
|
3834
3880
|
level: "AA",
|
|
@@ -3841,7 +3887,7 @@ const hn = {
|
|
|
3841
3887
|
if (b(e) || e === t.documentElement) continue;
|
|
3842
3888
|
const n = e.getAttribute("lang"), i = n == null ? void 0 : n.trim();
|
|
3843
3889
|
if (n && !i) {
|
|
3844
|
-
|
|
3890
|
+
he(e) && a.push({
|
|
3845
3891
|
ruleId: "valid-lang",
|
|
3846
3892
|
selector: m(e),
|
|
3847
3893
|
html: u(e),
|
|
@@ -3850,7 +3896,7 @@ const hn = {
|
|
|
3850
3896
|
});
|
|
3851
3897
|
continue;
|
|
3852
3898
|
}
|
|
3853
|
-
i &&
|
|
3899
|
+
i && he(e) && (ke(i) || a.push({
|
|
3854
3900
|
ruleId: "valid-lang",
|
|
3855
3901
|
selector: m(e),
|
|
3856
3902
|
html: u(e),
|
|
@@ -3860,7 +3906,7 @@ const hn = {
|
|
|
3860
3906
|
}
|
|
3861
3907
|
return a;
|
|
3862
3908
|
}
|
|
3863
|
-
},
|
|
3909
|
+
}, An = {
|
|
3864
3910
|
id: "html-xml-lang-mismatch",
|
|
3865
3911
|
wcag: ["3.1.1"],
|
|
3866
3912
|
level: "A",
|
|
@@ -3868,11 +3914,11 @@ const hn = {
|
|
|
3868
3914
|
guidance: "In XHTML documents, if both lang and xml:lang are present, they must specify the same base language. Mismatched values confuse assistive technologies. Either remove xml:lang (preferred for HTML5) or ensure both attributes have identical values.",
|
|
3869
3915
|
prompt: "Explain whether to remove xml:lang or align it with the lang value.",
|
|
3870
3916
|
run(t) {
|
|
3871
|
-
var i,
|
|
3872
|
-
const a = t.documentElement, e = (i = a.getAttribute("lang")) == null ? void 0 : i.trim().toLowerCase(), n = (
|
|
3917
|
+
var i, o;
|
|
3918
|
+
const a = t.documentElement, e = (i = a.getAttribute("lang")) == null ? void 0 : i.trim().toLowerCase(), n = (o = a.getAttribute("xml:lang")) == null ? void 0 : o.trim().toLowerCase();
|
|
3873
3919
|
if (e && n) {
|
|
3874
|
-
const
|
|
3875
|
-
if (
|
|
3920
|
+
const r = e.split("-")[0], s = n.split("-")[0];
|
|
3921
|
+
if (r !== s)
|
|
3876
3922
|
return [{
|
|
3877
3923
|
ruleId: "html-xml-lang-mismatch",
|
|
3878
3924
|
selector: "html",
|
|
@@ -3883,7 +3929,7 @@ const hn = {
|
|
|
3883
3929
|
}
|
|
3884
3930
|
return [];
|
|
3885
3931
|
}
|
|
3886
|
-
},
|
|
3932
|
+
}, xn = {
|
|
3887
3933
|
id: "td-headers-attr",
|
|
3888
3934
|
wcag: ["1.3.1"],
|
|
3889
3935
|
level: "A",
|
|
@@ -3896,25 +3942,25 @@ const hn = {
|
|
|
3896
3942
|
if (b(e)) continue;
|
|
3897
3943
|
const n = e.closest("table");
|
|
3898
3944
|
if (!n) continue;
|
|
3899
|
-
const i = e.getAttribute("id"),
|
|
3900
|
-
for (const
|
|
3901
|
-
if (
|
|
3945
|
+
const i = e.getAttribute("id"), o = e.getAttribute("headers").split(/\s+/);
|
|
3946
|
+
for (const r of o) {
|
|
3947
|
+
if (r === i) {
|
|
3902
3948
|
a.push({
|
|
3903
3949
|
ruleId: "td-headers-attr",
|
|
3904
3950
|
selector: m(e),
|
|
3905
3951
|
html: u(e),
|
|
3906
3952
|
impact: "serious",
|
|
3907
|
-
message: `Headers attribute references the cell itself ("${
|
|
3953
|
+
message: `Headers attribute references the cell itself ("${r}").`
|
|
3908
3954
|
});
|
|
3909
3955
|
break;
|
|
3910
3956
|
}
|
|
3911
|
-
if (!n.querySelector(`th#${CSS.escape(
|
|
3957
|
+
if (!n.querySelector(`th#${CSS.escape(r)}, td#${CSS.escape(r)}`)) {
|
|
3912
3958
|
a.push({
|
|
3913
3959
|
ruleId: "td-headers-attr",
|
|
3914
3960
|
selector: m(e),
|
|
3915
3961
|
html: u(e),
|
|
3916
3962
|
impact: "serious",
|
|
3917
|
-
message: `Headers attribute references non-existent ID "${
|
|
3963
|
+
message: `Headers attribute references non-existent ID "${r}".`
|
|
3918
3964
|
});
|
|
3919
3965
|
break;
|
|
3920
3966
|
}
|
|
@@ -3922,7 +3968,7 @@ const hn = {
|
|
|
3922
3968
|
}
|
|
3923
3969
|
return a;
|
|
3924
3970
|
}
|
|
3925
|
-
},
|
|
3971
|
+
}, kn = {
|
|
3926
3972
|
id: "th-has-data-cells",
|
|
3927
3973
|
wcag: ["1.3.1"],
|
|
3928
3974
|
level: "A",
|
|
@@ -3944,7 +3990,7 @@ const hn = {
|
|
|
3944
3990
|
}
|
|
3945
3991
|
return a;
|
|
3946
3992
|
}
|
|
3947
|
-
},
|
|
3993
|
+
}, Sn = {
|
|
3948
3994
|
id: "td-has-header",
|
|
3949
3995
|
wcag: ["1.3.1"],
|
|
3950
3996
|
level: "A",
|
|
@@ -3956,16 +4002,16 @@ const hn = {
|
|
|
3956
4002
|
const a = [];
|
|
3957
4003
|
for (const i of t.querySelectorAll("table")) {
|
|
3958
4004
|
if (b(i) || i.getAttribute("role") === "presentation" || i.getAttribute("role") === "none") continue;
|
|
3959
|
-
const
|
|
4005
|
+
const o = i.querySelectorAll("tr"), r = o.length;
|
|
3960
4006
|
let s = 0;
|
|
3961
|
-
for (const d of
|
|
4007
|
+
for (const d of o) {
|
|
3962
4008
|
const h = d.querySelectorAll("td, th");
|
|
3963
4009
|
let g = 0;
|
|
3964
4010
|
for (const f of h)
|
|
3965
4011
|
g += parseInt(f.getAttribute("colspan") || "1", 10);
|
|
3966
4012
|
s = Math.max(s, g);
|
|
3967
4013
|
}
|
|
3968
|
-
if (
|
|
4014
|
+
if (r <= 3 && s <= 3) continue;
|
|
3969
4015
|
const l = i.querySelector("th") !== null, p = i.querySelector("th[scope]") !== null, c = i.querySelector("td[headers]") !== null;
|
|
3970
4016
|
if (l)
|
|
3971
4017
|
for (const d of i.querySelectorAll("td")) {
|
|
@@ -3997,7 +4043,7 @@ const hn = {
|
|
|
3997
4043
|
}
|
|
3998
4044
|
return a;
|
|
3999
4045
|
}
|
|
4000
|
-
},
|
|
4046
|
+
}, In = {
|
|
4001
4047
|
id: "scope-attr-valid",
|
|
4002
4048
|
wcag: ["1.3.1"],
|
|
4003
4049
|
level: "A",
|
|
@@ -4009,18 +4055,18 @@ const hn = {
|
|
|
4009
4055
|
const a = [], e = /* @__PURE__ */ new Set(["row", "col", "rowgroup", "colgroup"]);
|
|
4010
4056
|
for (const i of t.querySelectorAll("th[scope]")) {
|
|
4011
4057
|
if (b(i)) continue;
|
|
4012
|
-
const
|
|
4013
|
-
|
|
4058
|
+
const o = (n = i.getAttribute("scope")) == null ? void 0 : n.toLowerCase();
|
|
4059
|
+
o && !e.has(o) && a.push({
|
|
4014
4060
|
ruleId: "scope-attr-valid",
|
|
4015
4061
|
selector: m(i),
|
|
4016
4062
|
html: u(i),
|
|
4017
4063
|
impact: "moderate",
|
|
4018
|
-
message: `Invalid scope value "${
|
|
4064
|
+
message: `Invalid scope value "${o}". Use row, col, rowgroup, or colgroup.`
|
|
4019
4065
|
});
|
|
4020
4066
|
}
|
|
4021
4067
|
return a;
|
|
4022
4068
|
}
|
|
4023
|
-
},
|
|
4069
|
+
}, En = {
|
|
4024
4070
|
id: "empty-table-header",
|
|
4025
4071
|
wcag: [],
|
|
4026
4072
|
level: "A",
|
|
@@ -4043,7 +4089,7 @@ const hn = {
|
|
|
4043
4089
|
}
|
|
4044
4090
|
return a;
|
|
4045
4091
|
}
|
|
4046
|
-
},
|
|
4092
|
+
}, W = ["aria-labelledby", "aria-describedby", "aria-controls", "aria-owns", "aria-flowto"], qn = {
|
|
4047
4093
|
id: "duplicate-id-aria",
|
|
4048
4094
|
wcag: ["4.1.2"],
|
|
4049
4095
|
level: "A",
|
|
@@ -4053,25 +4099,25 @@ const hn = {
|
|
|
4053
4099
|
run(t) {
|
|
4054
4100
|
const a = [], e = /* @__PURE__ */ new Set();
|
|
4055
4101
|
for (const i of t.querySelectorAll("[aria-labelledby], [aria-describedby], [aria-controls], [aria-owns], [aria-flowto]"))
|
|
4056
|
-
for (const
|
|
4057
|
-
const
|
|
4058
|
-
|
|
4102
|
+
for (const o of W) {
|
|
4103
|
+
const r = i.getAttribute(o);
|
|
4104
|
+
r && r.split(/\s+/).forEach((s) => e.add(s));
|
|
4059
4105
|
}
|
|
4060
4106
|
for (const i of t.querySelectorAll("label[for]")) {
|
|
4061
|
-
const
|
|
4062
|
-
|
|
4107
|
+
const o = i.getAttribute("for");
|
|
4108
|
+
o && e.add(o);
|
|
4063
4109
|
}
|
|
4064
4110
|
const n = /* @__PURE__ */ new Map();
|
|
4065
4111
|
for (const i of t.querySelectorAll("[id]"))
|
|
4066
4112
|
e.has(i.id) && (i instanceof HTMLElement && (i.style.display === "none" || i.style.visibility === "hidden" || i.hidden) || n.set(i.id, (n.get(i.id) ?? 0) + 1));
|
|
4067
|
-
for (const [i,
|
|
4068
|
-
if (
|
|
4069
|
-
const
|
|
4070
|
-
|
|
4113
|
+
for (const [i, o] of n) {
|
|
4114
|
+
if (o <= 1) continue;
|
|
4115
|
+
const r = t.querySelectorAll(`#${CSS.escape(i)}`), s = t.querySelector(
|
|
4116
|
+
W.map((c) => `[${c}~="${CSS.escape(i)}"]`).join(", ")
|
|
4071
4117
|
), l = t.querySelector(`label[for="${CSS.escape(i)}"]`);
|
|
4072
4118
|
let p;
|
|
4073
4119
|
if (s) {
|
|
4074
|
-
const c =
|
|
4120
|
+
const c = W.find(
|
|
4075
4121
|
(d) => {
|
|
4076
4122
|
var h;
|
|
4077
4123
|
return (h = s.getAttribute(d)) == null ? void 0 : h.split(/\s+/).includes(i);
|
|
@@ -4081,17 +4127,17 @@ const hn = {
|
|
|
4081
4127
|
} else l && (p = "label[for]");
|
|
4082
4128
|
a.push({
|
|
4083
4129
|
ruleId: "duplicate-id-aria",
|
|
4084
|
-
selector: m(
|
|
4085
|
-
html: u(
|
|
4130
|
+
selector: m(r[1]),
|
|
4131
|
+
html: u(r[1]),
|
|
4086
4132
|
impact: "critical",
|
|
4087
4133
|
message: `Duplicate ID "${i}" referenced by ${p ?? "an accessibility attribute"}.`,
|
|
4088
|
-
context: `First element: ${u(
|
|
4134
|
+
context: `First element: ${u(r[0])}${p ? `
|
|
4089
4135
|
Referenced by: ${p}` : ""}`
|
|
4090
4136
|
});
|
|
4091
4137
|
}
|
|
4092
4138
|
return a;
|
|
4093
4139
|
}
|
|
4094
|
-
},
|
|
4140
|
+
}, Ln = {
|
|
4095
4141
|
id: "video-caption",
|
|
4096
4142
|
wcag: ["1.2.2"],
|
|
4097
4143
|
level: "A",
|
|
@@ -4112,7 +4158,7 @@ Referenced by: ${p}` : ""}`
|
|
|
4112
4158
|
}
|
|
4113
4159
|
return a;
|
|
4114
4160
|
}
|
|
4115
|
-
},
|
|
4161
|
+
}, Tn = {
|
|
4116
4162
|
id: "audio-caption",
|
|
4117
4163
|
wcag: ["1.2.1"],
|
|
4118
4164
|
level: "A",
|
|
@@ -4134,7 +4180,7 @@ Referenced by: ${p}` : ""}`
|
|
|
4134
4180
|
}
|
|
4135
4181
|
return a;
|
|
4136
4182
|
}
|
|
4137
|
-
},
|
|
4183
|
+
}, Rn = /* @__PURE__ */ new Set([
|
|
4138
4184
|
"SCRIPT",
|
|
4139
4185
|
"STYLE",
|
|
4140
4186
|
"NOSCRIPT",
|
|
@@ -4150,25 +4196,25 @@ Referenced by: ${p}` : ""}`
|
|
|
4150
4196
|
"BR",
|
|
4151
4197
|
"HR"
|
|
4152
4198
|
]);
|
|
4153
|
-
function
|
|
4199
|
+
function be([t, a, e]) {
|
|
4154
4200
|
return "#" + [t, a, e].map((n) => n.toString(16).padStart(2, "0")).join("");
|
|
4155
4201
|
}
|
|
4156
|
-
function
|
|
4202
|
+
function Cn(t) {
|
|
4157
4203
|
return t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement || t instanceof HTMLButtonElement ? t.disabled : !!(t.closest("fieldset[disabled]") || t.getAttribute("aria-disabled") === "true");
|
|
4158
4204
|
}
|
|
4159
|
-
function
|
|
4205
|
+
function Nn(t, a) {
|
|
4160
4206
|
if (t.tagName !== "LABEL") return !1;
|
|
4161
4207
|
const e = t, n = e.htmlFor;
|
|
4162
4208
|
if (n) {
|
|
4163
|
-
const
|
|
4164
|
-
if (
|
|
4209
|
+
const r = a.getElementById(n);
|
|
4210
|
+
if (r && (r.disabled || r.getAttribute("aria-disabled") === "true")) return !0;
|
|
4165
4211
|
}
|
|
4166
4212
|
const i = e.querySelector("input, select, textarea, button");
|
|
4167
4213
|
if (i && (i.disabled || i.getAttribute("aria-disabled") === "true")) return !0;
|
|
4168
|
-
const
|
|
4169
|
-
return !!(
|
|
4214
|
+
const o = e.id;
|
|
4215
|
+
return !!(o && a.querySelector(`[aria-labelledby~="${o}"][aria-disabled="true"]`));
|
|
4170
4216
|
}
|
|
4171
|
-
function
|
|
4217
|
+
function Mn(t) {
|
|
4172
4218
|
const a = t.clip;
|
|
4173
4219
|
if (a && a.startsWith("rect(")) {
|
|
4174
4220
|
const n = a.match(/[\d.]+/g);
|
|
@@ -4182,17 +4228,17 @@ function Ln(t) {
|
|
|
4182
4228
|
}
|
|
4183
4229
|
return !1;
|
|
4184
4230
|
}
|
|
4185
|
-
function
|
|
4231
|
+
function Dn(t) {
|
|
4186
4232
|
if (b(t)) return !0;
|
|
4187
4233
|
let a = t;
|
|
4188
4234
|
for (; a; ) {
|
|
4189
4235
|
const e = w(a);
|
|
4190
|
-
if (e.display === "none" || e.visibility === "hidden" ||
|
|
4236
|
+
if (e.display === "none" || e.visibility === "hidden" || Mn(e)) return !0;
|
|
4191
4237
|
a = a.parentElement;
|
|
4192
4238
|
}
|
|
4193
4239
|
return !1;
|
|
4194
4240
|
}
|
|
4195
|
-
function
|
|
4241
|
+
function $n(t) {
|
|
4196
4242
|
let a = 1, e = t;
|
|
4197
4243
|
for (; e; ) {
|
|
4198
4244
|
const n = w(e), i = parseFloat(n.opacity);
|
|
@@ -4200,7 +4246,7 @@ function Tn(t) {
|
|
|
4200
4246
|
}
|
|
4201
4247
|
return a;
|
|
4202
4248
|
}
|
|
4203
|
-
const
|
|
4249
|
+
const Hn = {
|
|
4204
4250
|
grayscale: 0,
|
|
4205
4251
|
blur: 0,
|
|
4206
4252
|
"hue-rotate": 0,
|
|
@@ -4211,37 +4257,37 @@ const Cn = {
|
|
|
4211
4257
|
saturate: 1,
|
|
4212
4258
|
opacity: 1
|
|
4213
4259
|
};
|
|
4214
|
-
function
|
|
4260
|
+
function Pn(t) {
|
|
4215
4261
|
const a = parseFloat(t);
|
|
4216
4262
|
return isNaN(a) ? NaN : t.trim().endsWith("%") ? a / 100 : a;
|
|
4217
4263
|
}
|
|
4218
|
-
const
|
|
4219
|
-
function
|
|
4264
|
+
const ge = /([a-z-]+)\(([^)]*)\)/g;
|
|
4265
|
+
function fe(t) {
|
|
4220
4266
|
let a, e = !1;
|
|
4221
|
-
for (
|
|
4267
|
+
for (ge.lastIndex = 0; a = ge.exec(t); ) {
|
|
4222
4268
|
e = !0;
|
|
4223
|
-
const n =
|
|
4224
|
-
if (n === void 0 ||
|
|
4269
|
+
const n = Hn[a[1]];
|
|
4270
|
+
if (n === void 0 || Pn(a[2]) !== n) return !1;
|
|
4225
4271
|
}
|
|
4226
4272
|
return e;
|
|
4227
4273
|
}
|
|
4228
|
-
function
|
|
4274
|
+
function zn(t) {
|
|
4229
4275
|
let a = t;
|
|
4230
4276
|
for (; a; ) {
|
|
4231
4277
|
const e = w(a), n = e.filter;
|
|
4232
|
-
if (n && n !== "none" && n !== "initial" && !
|
|
4278
|
+
if (n && n !== "none" && n !== "initial" && !fe(n)) return !0;
|
|
4233
4279
|
const i = e.mixBlendMode;
|
|
4234
4280
|
if (i && i !== "normal" && i !== "initial") return !0;
|
|
4235
|
-
const
|
|
4236
|
-
if (
|
|
4281
|
+
const o = e.backdropFilter;
|
|
4282
|
+
if (o && o !== "none" && o !== "initial" && !fe(o)) return !0;
|
|
4237
4283
|
a = a.parentElement;
|
|
4238
4284
|
}
|
|
4239
4285
|
return !1;
|
|
4240
4286
|
}
|
|
4241
|
-
function
|
|
4287
|
+
function Fn(t) {
|
|
4242
4288
|
return t.closest("select") !== null;
|
|
4243
4289
|
}
|
|
4244
|
-
const
|
|
4290
|
+
const jn = {
|
|
4245
4291
|
id: "color-contrast",
|
|
4246
4292
|
wcag: ["1.4.3"],
|
|
4247
4293
|
level: "AA",
|
|
@@ -4252,174 +4298,174 @@ const $n = {
|
|
|
4252
4298
|
const a = [], e = t.body;
|
|
4253
4299
|
if (!e) return [];
|
|
4254
4300
|
const n = t.createTreeWalker(e, NodeFilter.SHOW_TEXT), i = /* @__PURE__ */ new Set();
|
|
4255
|
-
let
|
|
4256
|
-
for (;
|
|
4257
|
-
if (!
|
|
4258
|
-
const
|
|
4259
|
-
if (!
|
|
4260
|
-
const s =
|
|
4261
|
-
if (s === "BODY" || s === "HTML" ||
|
|
4262
|
-
const l = w(
|
|
4263
|
-
if (parseFloat(l.opacity) === 0 ||
|
|
4301
|
+
let o;
|
|
4302
|
+
for (; o = n.nextNode(); ) {
|
|
4303
|
+
if (!o.textContent || !o.textContent.trim()) continue;
|
|
4304
|
+
const r = o.parentElement;
|
|
4305
|
+
if (!r || i.has(r) || (i.add(r), Rn.has(r.tagName))) continue;
|
|
4306
|
+
const s = r.tagName;
|
|
4307
|
+
if (s === "BODY" || s === "HTML" || Fn(r) || Cn(r) || Nn(r, t) || Dn(r)) continue;
|
|
4308
|
+
const l = w(r);
|
|
4309
|
+
if (parseFloat(l.opacity) === 0 || $n(r) < 0.1) continue;
|
|
4264
4310
|
const p = l.textShadow;
|
|
4265
|
-
if (p && p !== "none" && p !== "initial" ||
|
|
4266
|
-
const c =
|
|
4311
|
+
if (p && p !== "none" && p !== "initial" || zn(r)) continue;
|
|
4312
|
+
const c = D(l.color);
|
|
4267
4313
|
if (!c) continue;
|
|
4268
4314
|
const d = l.color.match(/rgba\(.+?,\s*([\d.]+)\s*\)/) || l.color.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);
|
|
4269
|
-
if (d && (d[1].endsWith("%") ? parseFloat(d[1]) / 100 : parseFloat(d[1])) === 0 ||
|
|
4270
|
-
const h =
|
|
4315
|
+
if (d && (d[1].endsWith("%") ? parseFloat(d[1]) / 100 : parseFloat(d[1])) === 0 || Ye(r)) continue;
|
|
4316
|
+
const h = Ve(r);
|
|
4271
4317
|
if (!h) continue;
|
|
4272
|
-
const g =
|
|
4318
|
+
const g = M(c[0], c[1], c[2]), f = M(h[0], h[1], h[2]), y = we(g, f), x = Je(r) ? 3 : 4.5;
|
|
4273
4319
|
if (y < x) {
|
|
4274
|
-
const k = Math.round(y * 100) / 100,
|
|
4320
|
+
const k = Math.round(y * 100) / 100, P = be(c), qe = be(h);
|
|
4275
4321
|
a.push({
|
|
4276
4322
|
ruleId: "color-contrast",
|
|
4277
|
-
selector: m(
|
|
4278
|
-
html: u(
|
|
4323
|
+
selector: m(r),
|
|
4324
|
+
html: u(r),
|
|
4279
4325
|
impact: "serious",
|
|
4280
4326
|
message: `Insufficient color contrast ratio of ${k}:1 (required ${x}:1).`,
|
|
4281
|
-
context: `foreground: ${
|
|
4327
|
+
context: `foreground: ${P} rgb(${c.join(", ")}), background: ${qe} rgb(${h.join(", ")}), ratio: ${k}:1, required: ${x}:1`
|
|
4282
4328
|
});
|
|
4283
4329
|
}
|
|
4284
4330
|
}
|
|
4285
4331
|
return a;
|
|
4286
4332
|
}
|
|
4287
|
-
},
|
|
4333
|
+
}, Se = [
|
|
4288
4334
|
// Document Structure
|
|
4289
|
-
ra,
|
|
4290
|
-
oa,
|
|
4291
|
-
sa,
|
|
4292
|
-
la,
|
|
4293
|
-
ca,
|
|
4294
4335
|
ua,
|
|
4295
4336
|
ma,
|
|
4337
|
+
pa,
|
|
4296
4338
|
ha,
|
|
4297
|
-
|
|
4339
|
+
ba,
|
|
4340
|
+
fa,
|
|
4341
|
+
va,
|
|
4342
|
+
wa,
|
|
4343
|
+
xa,
|
|
4298
4344
|
// Images
|
|
4299
|
-
Je,
|
|
4300
|
-
Ze,
|
|
4301
|
-
et,
|
|
4302
|
-
tt,
|
|
4303
4345
|
nt,
|
|
4304
|
-
it,
|
|
4305
4346
|
ot,
|
|
4347
|
+
rt,
|
|
4306
4348
|
st,
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4349
|
+
ct,
|
|
4350
|
+
dt,
|
|
4351
|
+
mt,
|
|
4352
|
+
pt,
|
|
4310
4353
|
ft,
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
// Keyboard
|
|
4317
|
-
Ct,
|
|
4354
|
+
// Forms
|
|
4355
|
+
xt,
|
|
4356
|
+
kt,
|
|
4357
|
+
St,
|
|
4358
|
+
It,
|
|
4318
4359
|
Nt,
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
da,
|
|
4325
|
-
fa,
|
|
4326
|
-
Bt,
|
|
4327
|
-
Ut,
|
|
4328
|
-
_t,
|
|
4360
|
+
Mt,
|
|
4361
|
+
Dt,
|
|
4362
|
+
// Keyboard
|
|
4363
|
+
Ht,
|
|
4364
|
+
zt,
|
|
4329
4365
|
Vt,
|
|
4366
|
+
_t,
|
|
4330
4367
|
Gt,
|
|
4368
|
+
// Structure
|
|
4331
4369
|
Yt,
|
|
4370
|
+
ga,
|
|
4371
|
+
ka,
|
|
4332
4372
|
Xt,
|
|
4333
4373
|
Kt,
|
|
4334
4374
|
Jt,
|
|
4335
4375
|
Qt,
|
|
4376
|
+
Zt,
|
|
4336
4377
|
ea,
|
|
4337
|
-
ia,
|
|
4338
4378
|
ta,
|
|
4379
|
+
aa,
|
|
4339
4380
|
na,
|
|
4381
|
+
ia,
|
|
4382
|
+
ra,
|
|
4383
|
+
da,
|
|
4384
|
+
sa,
|
|
4385
|
+
ca,
|
|
4340
4386
|
// ARIA
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
xa,
|
|
4387
|
+
Sa,
|
|
4388
|
+
Ia,
|
|
4389
|
+
Ea,
|
|
4345
4390
|
La,
|
|
4346
|
-
Ca,
|
|
4347
4391
|
Ma,
|
|
4348
|
-
|
|
4349
|
-
za,
|
|
4350
|
-
ja,
|
|
4351
|
-
Wa,
|
|
4392
|
+
Ha,
|
|
4352
4393
|
Fa,
|
|
4353
|
-
|
|
4354
|
-
Oa,
|
|
4394
|
+
ja,
|
|
4355
4395
|
Ba,
|
|
4356
|
-
|
|
4357
|
-
_a,
|
|
4396
|
+
Oa,
|
|
4358
4397
|
Va,
|
|
4398
|
+
_a,
|
|
4359
4399
|
Ga,
|
|
4400
|
+
Ya,
|
|
4401
|
+
Xa,
|
|
4360
4402
|
Ka,
|
|
4361
|
-
Sa,
|
|
4362
4403
|
Ja,
|
|
4363
|
-
|
|
4404
|
+
Qa,
|
|
4364
4405
|
Za,
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4406
|
+
an,
|
|
4407
|
+
Ra,
|
|
4408
|
+
nn,
|
|
4409
|
+
// Links
|
|
4410
|
+
rn,
|
|
4411
|
+
sn,
|
|
4370
4412
|
hn,
|
|
4413
|
+
// Language
|
|
4371
4414
|
bn,
|
|
4372
|
-
// Tables
|
|
4373
|
-
gn,
|
|
4374
|
-
fn,
|
|
4375
|
-
vn,
|
|
4376
4415
|
yn,
|
|
4377
4416
|
wn,
|
|
4378
|
-
// Parsing
|
|
4379
4417
|
An,
|
|
4380
|
-
//
|
|
4418
|
+
// Tables
|
|
4381
4419
|
xn,
|
|
4382
4420
|
kn,
|
|
4421
|
+
Sn,
|
|
4422
|
+
In,
|
|
4423
|
+
En,
|
|
4424
|
+
// Parsing
|
|
4425
|
+
qn,
|
|
4426
|
+
// Media
|
|
4427
|
+
Ln,
|
|
4428
|
+
Tn,
|
|
4383
4429
|
// Color
|
|
4384
|
-
|
|
4430
|
+
jn
|
|
4385
4431
|
];
|
|
4386
|
-
let
|
|
4387
|
-
function
|
|
4388
|
-
t.additionalRules && (
|
|
4432
|
+
let Q = [], Ie = /* @__PURE__ */ new Set(), I, C;
|
|
4433
|
+
function Vn(t) {
|
|
4434
|
+
t.additionalRules && (Q = t.additionalRules), t.disabledRules && (Ie = new Set(t.disabledRules)), "locale" in t && (I = t.locale || void 0), C = void 0;
|
|
4389
4435
|
}
|
|
4390
|
-
function
|
|
4436
|
+
function Z() {
|
|
4391
4437
|
if (C) return C;
|
|
4392
|
-
const a =
|
|
4393
|
-
return
|
|
4438
|
+
const a = Se.filter((e) => !Ie.has(e.id)).concat(Q);
|
|
4439
|
+
return I ? (C = Qe(a, I), C) : a;
|
|
4394
4440
|
}
|
|
4395
|
-
function
|
|
4396
|
-
|
|
4397
|
-
const a =
|
|
4398
|
-
let
|
|
4441
|
+
function _n(t) {
|
|
4442
|
+
Ee();
|
|
4443
|
+
const a = Z(), e = I, n = [];
|
|
4444
|
+
let i = 0;
|
|
4399
4445
|
return {
|
|
4400
|
-
processChunk(
|
|
4446
|
+
processChunk(o) {
|
|
4401
4447
|
const r = performance.now();
|
|
4402
|
-
for (;
|
|
4448
|
+
for (; i < a.length; ) {
|
|
4403
4449
|
try {
|
|
4404
|
-
|
|
4450
|
+
n.push(...a[i].run(t));
|
|
4405
4451
|
} catch {
|
|
4406
4452
|
}
|
|
4407
|
-
if (
|
|
4453
|
+
if (i++, performance.now() - r >= o) break;
|
|
4408
4454
|
}
|
|
4409
|
-
return
|
|
4455
|
+
return i < a.length;
|
|
4410
4456
|
},
|
|
4411
4457
|
getViolations() {
|
|
4412
|
-
return e;
|
|
4458
|
+
return e ? Ae(n, e) : n;
|
|
4413
4459
|
}
|
|
4414
4460
|
};
|
|
4415
4461
|
}
|
|
4416
|
-
function
|
|
4417
|
-
|
|
4462
|
+
function Ee() {
|
|
4463
|
+
Me(), Le(), Te(), Oe(), Be(), De();
|
|
4418
4464
|
}
|
|
4419
|
-
function
|
|
4465
|
+
function Gn(t) {
|
|
4420
4466
|
var n;
|
|
4421
|
-
|
|
4422
|
-
const a =
|
|
4467
|
+
Ee();
|
|
4468
|
+
const a = Z(), e = [];
|
|
4423
4469
|
for (const i of a)
|
|
4424
4470
|
try {
|
|
4425
4471
|
e.push(...i.run(t));
|
|
@@ -4428,214 +4474,215 @@ function Pn(t) {
|
|
|
4428
4474
|
return {
|
|
4429
4475
|
url: ((n = t.location) == null ? void 0 : n.href) ?? "",
|
|
4430
4476
|
timestamp: Date.now(),
|
|
4431
|
-
violations: e,
|
|
4477
|
+
violations: I ? Ae(e, I) : e,
|
|
4432
4478
|
ruleCount: a.length
|
|
4433
4479
|
};
|
|
4434
4480
|
}
|
|
4435
|
-
const
|
|
4436
|
-
function
|
|
4437
|
-
if (
|
|
4438
|
-
return
|
|
4439
|
-
const a =
|
|
4440
|
-
return a ||
|
|
4481
|
+
const Wn = new Map(Se.map((t) => [t.id, t]));
|
|
4482
|
+
function Yn(t) {
|
|
4483
|
+
if (I)
|
|
4484
|
+
return Z().find((n) => n.id === t);
|
|
4485
|
+
const a = Wn.get(t);
|
|
4486
|
+
return a || Q.find((e) => e.id === t);
|
|
4441
4487
|
}
|
|
4442
|
-
const
|
|
4443
|
-
"document-title": { description: "Documents must have a <title> element to provide users with an overview of content.", guidance: "Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp')." },
|
|
4444
|
-
bypass: { description: "Page must have a mechanism to bypass repeated blocks of content.", guidance: 'Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.' },
|
|
4445
|
-
"page-has-heading-one": { description: "Page should contain a level-one heading.", guidance: "A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have exactly one h1 that describes the main content, typically matching or similar to the page title." },
|
|
4446
|
-
"frame-title": { description: "Frames must have an accessible name.", guidance: "Screen readers announce frame titles when users navigate frames. Add a title attribute to <iframe> and <frame> elements that describes the frame's purpose (e.g., <iframe title='Video player'>). Avoid generic titles like 'frame' or 'iframe'. If the frame is decorative, use aria-hidden='true'." },
|
|
4447
|
-
"frame-title-unique": { description: "Frame titles should be unique.", guidance: "When multiple frames have identical titles, screen reader users cannot distinguish between them. Give each frame a unique, descriptive title that explains its specific purpose or content." },
|
|
4448
|
-
"meta-viewport": { description: "Viewport meta tag must not disable user scaling.", guidance: "Users with low vision need to zoom content up to 200% or more. Setting user-scalable=no or maximum-scale=1 prevents zooming and fails WCAG. Remove these restrictions. If your layout breaks at high zoom, fix the responsive design rather than preventing zoom." },
|
|
4449
|
-
"meta-refresh": { description: "Meta refresh must not redirect or refresh automatically.", guidance: "Automatic page refreshes or redirects can disorient users, especially those using screen readers or with cognitive disabilities. They may lose their place or not have time to read content. If a redirect is needed, use a server-side redirect (HTTP 301/302) instead. For timed refreshes, provide user controls." },
|
|
4450
|
-
blink: { description: "The <blink> element must not be used.", guidance: "Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons." },
|
|
4451
|
-
marquee: { description: "The <marquee> element must not be used.", guidance: "Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds." },
|
|
4452
|
-
"img-alt": { description: `Images must have alternate text. Add an alt attribute to <img> elements. Decorative images may use an empty alt attribute (alt=""), role='none', or role='presentation'.`, guidance: "Every image needs an alt attribute. For informative images, describe the content or function concisely. For decorative images (backgrounds, spacers, purely visual flourishes), use alt='' to hide them from screen readers. Never omit alt entirely—screen readers may read the filename instead." },
|
|
4453
|
-
"svg-img-alt": { description: "SVG elements with an img, graphics-document, or graphics-symbol role must have an accessible name via a <title> element, aria-label, or aria-labelledby.", guidance: "Inline SVGs with role='img' need accessible names. Add a <title> element as the first child of the SVG (screen readers will announce it), or use aria-label on the SVG element. For complex SVGs, use aria-labelledby referencing both a <title> and <desc> element. Decorative SVGs should use aria-hidden='true' instead." },
|
|
4454
|
-
"input-image-alt": { description: 'Image inputs (<input type="image">) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.', guidance: "Image buttons (<input type='image'>) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image." },
|
|
4455
|
-
"image-redundant-alt": { description: "Image alt text should not duplicate adjacent link or button text. When alt text repeats surrounding text, screen reader users hear the same information twice.", guidance: "When an image is inside a link or button that also has text, make the alt text complementary rather than identical. If the image is purely decorative in that context, use alt='' to avoid repetition." },
|
|
4456
|
-
"image-alt-redundant-words": { description: "Image alt text should not contain words like 'image', 'photo', or 'picture' — screen readers already announce the element type.", guidance: "Screen readers already announce 'image' or 'graphic' before reading alt text, so phrases like 'image of', 'photo of', or 'picture of' are redundant. Remove these words and describe what the image shows. For example, change 'image of a dog' to 'golden retriever playing fetch'." },
|
|
4457
|
-
"area-alt": { description: "Image map <area> elements must have alternative text.", guidance: "Each clickable region in an image map needs alternative text so screen reader users know what the region represents. Add an alt attribute to every <area> element describing its purpose. For complex image maps, consider using alternative approaches like SVG with embedded links, or a list of text links." },
|
|
4458
|
-
"object-alt": { description: "<object> elements must have alternative text.", guidance: "Object elements embed external content that may not be accessible to all users. Provide alternative text via aria-label, aria-labelledby, or a title attribute. The fallback content inside <object> is only shown when the object fails to load and does not serve as an accessible name." },
|
|
4459
|
-
"role-img-alt": { description: "Elements with role='img' must have an accessible name.", guidance: "When you assign role='img' to an element (like a div containing icon fonts or CSS backgrounds), you must provide an accessible name via aria-label or aria-labelledby. Without this, screen reader users have no way to understand what the image represents. If the image is decorative, use role='presentation' or role='none' instead." },
|
|
4460
|
-
"server-side-image-map": { description: "Server-side image maps must not be used.", guidance: "Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead." },
|
|
4461
|
-
label: { description: "Form elements must have labels. Use <label>, aria-label, or aria-labelledby.", guidance: "Every form input needs an accessible label so users understand what information to enter. Use a <label> element with a for attribute matching the input's id, wrap the input in a <label>, or use aria-label/aria-labelledby for custom components. Placeholders are not sufficient as labels since they disappear when typing." },
|
|
4462
|
-
"form-field-multiple-labels": { description: "Form fields should not have multiple label elements.", guidance: "When a form field has multiple <label> elements pointing to it, assistive technologies may announce only one label or behave inconsistently. Use a single <label> and combine any additional text into it, or use aria-describedby for supplementary information." },
|
|
4463
|
-
"select-name": { description: "Select elements must have a programmatically associated label via <label>, aria-label, or aria-labelledby.", guidance: "Select dropdowns need labels so users understand what choice they're making. Use a <label> element with a for attribute matching the select's id, or wrap the select in a <label>. For selects without visible labels, use aria-label. The first <option> is not a substitute for a proper label." },
|
|
4464
|
-
"input-button-name": { description: "Input buttons must have discernible text via value, aria-label, or aria-labelledby.", guidance: "Input buttons (<input type='submit'>, type='button', type='reset'>) need accessible names so users know what action the button performs. Add a value attribute with descriptive text (e.g., value='Submit Form'), or use aria-label if the value must differ from the accessible name." },
|
|
4465
|
-
"autocomplete-valid": { description: "Autocomplete attribute must use valid values from the HTML specification.", guidance: "The autocomplete attribute helps users fill forms by identifying input purposes. Use standard values like 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. This benefits users with cognitive disabilities, motor impairments, and anyone using password managers or autofill. Check the HTML specification for the complete list of valid tokens." },
|
|
4466
|
-
"label-content-name-mismatch": { description: "Interactive elements with visible text must have accessible names that contain that text.", guidance: "For voice control users who activate controls by speaking their visible label, the accessible name must include the visible text. If aria-label is 'Submit form' but the button shows 'Send', voice users saying 'click Send' won't activate it. Ensure aria-label/aria-labelledby contains or matches the visible text." },
|
|
4467
|
-
"label-title-only": { description: "Form elements should not use title attribute as the only accessible name.", guidance: "The title attribute is unreliable as a label because it only appears on hover/focus (not visible to touch users) and is often ignored by assistive technologies. Use a visible <label> element, aria-label, or aria-labelledby instead. Title can supplement a label but should not replace it." },
|
|
4468
|
-
tabindex: { description: "Elements should not have tabindex greater than 0, which disrupts natural tab order.", guidance: "Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence." },
|
|
4469
|
-
"focus-order-semantics": { description: "Elements that receive keyboard focus must have an appropriate role so assistive technologies can convey their purpose. Non-interactive elements with tabindex='0' need a valid interactive ARIA role.", guidance: "When adding tabindex='0' to non-interactive elements like <div> or <span>, screen readers announce them generically. Add an appropriate role (button, link, tab, etc.) so users understand the element's purpose. Also add keyboard event handlers (Enter/Space for buttons, Enter for links). Consider using native interactive elements instead." },
|
|
4470
|
-
"nested-interactive": { description: "Interactive controls must not be nested inside each other.", guidance: "Nesting interactive elements (like a button inside a link, or a link inside a button) creates unpredictable behavior and confuses assistive technologies. The browser may remove the inner element from the accessibility tree. Restructure the HTML so interactive elements are siblings, not nested. If you need a clickable card, use CSS and JavaScript rather than nesting." },
|
|
4471
|
-
"scrollable-region-focusable": { description: "Scrollable regions must be keyboard accessible.", guidance: "Content that scrolls must be accessible to keyboard users. If a region has overflow:scroll or overflow:auto and contains scrollable content, it needs either tabindex='0' to be focusable, or it must contain focusable elements. Without this, keyboard users cannot scroll the content." },
|
|
4472
|
-
accesskeys: { description: "Accesskey attribute values must be unique.", guidance: "When multiple elements share the same accesskey, browser behavior becomes unpredictable - usually only the first element is activated. Ensure each accesskey value is unique within the page. Also consider that accesskeys can conflict with browser and screen reader shortcuts, so use them sparingly." },
|
|
4473
|
-
"heading-order": { description: "Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.", guidance: "Screen reader users navigate by headings to understand page structure. Skipping levels (h2 to h4) suggests missing content and creates confusion. Start with h1 for the page title, then use h2 for main sections, h3 for subsections, etc. You can go back up (h3 to h2) when starting a new section." },
|
|
4474
|
-
"empty-heading": { description: "Headings must have discernible text.", guidance: "Screen reader users navigate pages by headings, so empty headings create confusing navigation points. Ensure all headings contain visible text or accessible names. If a heading is used purely for visual styling, use CSS instead of heading elements." },
|
|
4475
|
-
"p-as-heading": { description: "Paragraphs should not be styled to look like headings.", guidance: "When paragraphs are styled with bold, large fonts to look like headings, screen reader users miss the semantic structure. Use proper heading elements (h1-h6) instead of styled paragraphs. If you need specific styling, apply CSS to the heading elements while maintaining proper heading hierarchy." },
|
|
4476
|
-
"landmark-one-main": { description: "Page should have exactly one main landmark.", guidance: "The main landmark contains the primary content of the page. Screen readers allow users to jump directly to main content. Use a single <main> element (or role='main') to wrap the central content, excluding headers, footers, and navigation." },
|
|
4477
|
-
"landmark-no-duplicate-banner": { description: "Page should not have more than one banner landmark.", guidance: "The banner landmark (typically <header>) identifies site-oriented content like logos and search. Only one top-level banner is allowed per page. If you need multiple headers, nest them inside sectioning elements (article, section, aside) where they become scoped headers rather than page-level banners." },
|
|
4478
|
-
"landmark-no-duplicate-contentinfo": { description: "Page should not have more than one contentinfo landmark.", guidance: "The contentinfo landmark (typically <footer>) contains information about the page like copyright and contact info. Only one top-level contentinfo is allowed per page. Nest additional footers inside sectioning elements to scope them." },
|
|
4479
|
-
"landmark-no-duplicate-main": { description: "Page should not have more than one main landmark.", guidance: "Only one main landmark should exist per page. The main landmark identifies the primary content area. If you have multiple content sections, use <section> with appropriate headings instead of multiple main elements." },
|
|
4480
|
-
"landmark-banner-is-top-level": { description: "Banner landmark should not be nested within another landmark.", guidance: "The banner landmark should be a top-level landmark, not nested inside article, aside, main, nav, or section. If a header is inside these elements, it automatically becomes a generic header rather than a banner. Remove explicit role='banner' from nested headers or restructure the page." },
|
|
4481
|
-
"landmark-contentinfo-is-top-level": { description: "Contentinfo landmark should not be nested within another landmark.", guidance: "The contentinfo landmark should be a top-level landmark. A footer inside article, aside, main, nav, or section becomes a scoped footer, not a contentinfo landmark. Remove explicit role='contentinfo' from nested footers or move the footer outside sectioning elements." },
|
|
4482
|
-
"landmark-main-is-top-level": { description: "Main landmark should not be nested within another landmark.", guidance: "The main landmark must be a top-level landmark since it represents the primary content of the page. Do not nest <main> or role='main' inside article, aside, nav, or section elements." },
|
|
4483
|
-
"landmark-complementary-is-top-level": { description: "Aside (complementary) landmark should be top-level or directly inside main.", guidance: "The complementary landmark (aside) should be top-level or a direct child of main. Nesting aside deep within other landmarks reduces its discoverability for screen reader users navigating by landmarks." },
|
|
4484
|
-
"landmark-unique": { description: "Landmarks should have unique labels when there are multiple of the same type.", guidance: "When a page has multiple landmarks of the same type (e.g., multiple nav elements), each should have a unique accessible name via aria-label or aria-labelledby. This helps screen reader users distinguish between them (e.g., 'Main navigation' vs 'Footer navigation')." },
|
|
4485
|
-
region: { description: "All page content should be contained within landmarks.", guidance: "Screen reader users navigate pages by landmarks. Content outside landmarks is harder to find and understand. Wrap all visible content in appropriate landmarks: <header>, <nav>, <main>, <aside>, <footer>, or <section> with a label. Skip links may exist outside landmarks." },
|
|
4486
|
-
list: { description: "<ul> and <ol> must only contain <li>, <script>, or <template> as direct children.", guidance: "Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, restructure your CSS to style the <li> elements directly." },
|
|
4487
|
-
listitem: { description: "<li> elements must be contained in a <ul>, <ol>, or <menu>.", guidance: "List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Outside of these containers, assistive technologies cannot convey the list relationship. Wrap <li> elements in the appropriate list container." },
|
|
4488
|
-
dlitem: { description: "<dt> and <dd> elements must be contained in a <dl>.", guidance: "Definition terms (<dt>) and definitions (<dd>) only have semantic meaning inside a definition list (<dl>). Outside of <dl>, they're treated as generic text. Wrap related <dt> and <dd> pairs in a <dl> element to convey the term/definition relationship to assistive technologies." },
|
|
4489
|
-
"definition-list": { description: "<dl> elements must only contain <dt>, <dd>, <div>, <script>, or <template>.", guidance: "Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or restructure using proper definition list markup." },
|
|
4490
|
-
"aria-roles": { description: "ARIA role values must be valid.", guidance: "Invalid role values are ignored by assistive technologies, meaning the element will not have the intended semantics. Check the spelling and use only roles defined in the WAI-ARIA specification. Common roles include: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem." },
|
|
4491
|
-
"aria-valid-attr": { description: "ARIA attributes must be valid (correctly spelled).", guidance: "Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+)." },
|
|
4492
|
-
"aria-valid-attr-value": { description: "ARIA attributes must have valid values.", guidance: "Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs." },
|
|
4493
|
-
"aria-required-attr": { description: "Elements with ARIA roles must have all required ARIA attributes.", guidance: "Some ARIA roles require specific attributes to function correctly. For example, checkbox requires aria-checked, slider requires aria-valuenow, heading requires aria-level. Without these attributes, assistive technologies cannot convey the element's state or value to users. Add the missing required attribute with an appropriate value." },
|
|
4494
|
-
"aria-allowed-attr": { description: "ARIA attributes must be allowed for the element's role.", guidance: "Each ARIA role supports specific attributes. Using unsupported attributes creates confusion for assistive technologies. Check the ARIA specification for which attributes are valid for each role, or remove the attribute if it's not needed." },
|
|
4495
|
-
"aria-allowed-role": { description: "ARIA role must be appropriate for the element.", guidance: "Not all ARIA roles can be applied to all HTML elements. Many elements have implicit roles (e.g., <header> is implicitly banner, <nav> is navigation, <main> is main). Adding an explicit role that matches the implicit role is redundant. Adding a conflicting role breaks semantics. Either remove the role attribute or use a different element." },
|
|
4496
|
-
"aria-required-children": { description: "Certain ARIA roles require specific child roles to be present.", guidance: "Some ARIA roles represent containers that must contain specific child roles for proper semantics. For example, a list must contain listitems, a menu must contain menuitems. Add the required child elements with appropriate roles, or use native HTML elements that provide these semantics implicitly (e.g., <ul> with <li>)." },
|
|
4497
|
-
"aria-required-parent": { description: "Certain ARIA roles must be contained within specific parent roles.", guidance: "Some ARIA roles represent items that must exist within specific container roles. For example, a listitem must be within a list, a tab must be within a tablist. Wrap the element in the appropriate parent, or use native HTML elements that provide this structure (e.g., <li> inside <ul>)." },
|
|
4498
|
-
"aria-hidden-body": { description: "aria-hidden='true' must not be present on the document body.", guidance: "Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead." },
|
|
4499
|
-
"aria-hidden-focus": { description: "Elements with aria-hidden='true' must not contain focusable elements.", guidance: "When aria-hidden='true' hides an element from assistive technologies but the element contains focusable children, keyboard users can focus those children but screen reader users won't know they exist. Either remove focusable elements from the hidden region, add tabindex='-1' to them, or remove aria-hidden." },
|
|
4500
|
-
"aria-command-name": { description: "ARIA commands must have an accessible name.", guidance: "Interactive ARIA command roles (button, link, menuitem) must have accessible names so users know what action they perform. Add visible text content, aria-label, or aria-labelledby to provide a name." },
|
|
4501
|
-
"aria-input-field-name": { description: "ARIA input fields must have an accessible name.", guidance: "ARIA input widgets (combobox, listbox, searchbox, slider, spinbutton, textbox) must have accessible names so users understand what data to enter. Add a visible label with aria-labelledby, or use aria-label if a visible label is not possible." },
|
|
4502
|
-
"aria-toggle-field-name": { description: "ARIA toggle fields must have an accessible name.", guidance: "ARIA toggle controls (checkbox, switch, radio, menuitemcheckbox, menuitemradio) must have accessible names so users understand what option they're selecting. Add visible text content, aria-label, or use aria-labelledby to reference a visible label." },
|
|
4503
|
-
"aria-meter-name": { description: "ARIA meter elements must have an accessible name.", guidance: "Meter elements display a value within a known range (like disk usage or password strength). They must have accessible names so screen reader users understand what is being measured. Use aria-label or aria-labelledby to provide context." },
|
|
4504
|
-
"aria-progressbar-name": { description: "ARIA progressbar elements must have an accessible name.", guidance: "Progress indicators must have accessible names so screen reader users understand what process is being tracked. Use aria-label (e.g., 'File upload progress') or aria-labelledby to reference a visible heading or label." },
|
|
4505
|
-
"aria-dialog-name": { description: "ARIA dialogs must have an accessible name.", guidance: "Dialog and alertdialog elements must have accessible names so screen reader users understand the dialog's purpose when it opens. Use aria-label or aria-labelledby pointing to the dialog's heading. Native <dialog> elements should also have an accessible name." },
|
|
4506
|
-
"aria-tooltip-name": { description: "ARIA tooltips must have an accessible name.", guidance: "Tooltip elements must have accessible names (usually their text content). The tooltip content itself typically serves as the accessible name. Ensure the tooltip contains descriptive text content or has aria-label." },
|
|
4507
|
-
"aria-treeitem-name": { description: "ARIA treeitem elements must have an accessible name.", guidance: "Tree items must have accessible names so screen reader users can understand the tree structure and navigate it effectively. Provide text content, aria-label, or aria-labelledby for each treeitem." },
|
|
4508
|
-
"aria-prohibited-attr": { description: "ARIA attributes must not be prohibited for the element's role.", guidance: "Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role." },
|
|
4509
|
-
"presentation-role-conflict": { description: "Elements with role='presentation' or role='none' must not be focusable or have global ARIA attributes.", guidance: "When an element has role='presentation' or role='none', it's marked as decorative and removed from the accessibility tree. However, if the element is focusable or has certain ARIA attributes, the presentation role is ignored and the element remains accessible. This creates confusion. Either remove the presentation role, or remove the focusability/ARIA attributes." },
|
|
4510
|
-
"button-name": { description: "Buttons must have discernible text.", guidance: "Screen reader users need to know what a button does. Add visible text content, aria-label, or aria-labelledby. For icon buttons, use aria-label describing the action (e.g., aria-label='Close'). If the button contains an image, ensure the image has alt text describing the button's action." },
|
|
4511
|
-
"summary-name": { description: "<summary> elements must have an accessible name.", guidance: "The <summary> element provides the visible label for a <details> disclosure widget. It must have descriptive text content so screen reader users understand what will be revealed when expanded. Add clear, concise text that indicates what content is contained in the details section." },
|
|
4512
|
-
"link-name": { description: "Links must have discernible text via content, aria-label, or aria-labelledby.", guidance: "Screen reader users need to know where a link goes. Add descriptive text content, aria-label, or use aria-labelledby. For image links, ensure the image has alt text describing the link destination. Avoid generic text like 'click here' or 'read more'—link text should make sense out of context." },
|
|
4513
|
-
"skip-link": { description: "Skip links must point to a valid target on the page.", guidance: "Skip links allow keyboard users to bypass repetitive navigation and jump directly to main content. The skip link should be the first focusable element on the page, link to the main content (e.g., href='#main'), and become visible when focused. It can be visually hidden until focused using CSS." },
|
|
4514
|
-
"link-in-text-block": { description: "Links within text blocks must be distinguishable by more than color alone.", guidance: "Users who cannot perceive color differences need other visual cues to identify links. Links in text should have underlines or other non-color indicators. If using color alone, ensure 3:1 contrast with surrounding text AND provide additional indication on focus/hover." },
|
|
4515
|
-
"html-has-lang": { description: "The <html> element must have a lang attribute.", guidance: "Screen readers use the lang attribute to determine which language rules and pronunciation to use. Without it, content may be mispronounced. Set lang to the primary language of the page (e.g., lang='en' for English, lang='es' for Spanish)." },
|
|
4516
|
-
"html-lang-valid": { description: "The lang attribute on <html> must have a valid value.", guidance: "The lang attribute must use a valid BCP 47 language tag. Use a 2 or 3 letter language code (e.g., 'en', 'fr', 'zh'), optionally followed by a region code (e.g., 'en-US', 'pt-BR'). Invalid tags prevent screen readers from correctly pronouncing content." },
|
|
4517
|
-
"valid-lang": { description: "The lang attribute must have a valid value on all elements.", guidance: "When content in a different language appears within a page (e.g., a French quote in an English document), wrap it with a lang attribute to ensure correct pronunciation. The lang value must be a valid BCP 47 tag. Common codes: en, es, fr, de, zh, ja, pt, ar, ru." },
|
|
4518
|
-
"html-xml-lang-mismatch": { description: "The lang and xml:lang attributes on <html> must match.", guidance: "In XHTML documents, if both lang and xml:lang are present, they must specify the same base language. Mismatched values confuse assistive technologies. Either remove xml:lang (preferred for HTML5) or ensure both attributes have identical values." },
|
|
4519
|
-
"td-headers-attr": { description: "All cells in a table using headers attribute must reference valid header IDs.", guidance: "The headers attribute on table cells must reference IDs of header cells (th or td) within the same table. This creates explicit associations for screen readers. Verify all referenced IDs exist and spell them correctly. For simple tables, consider using scope on th elements instead." },
|
|
4520
|
-
"th-has-data-cells": { description: "Table headers should be associated with data cells.", guidance: "A table with header cells (th) but no data cells (td) is likely a misuse of table markup for layout or has missing content. Either add data cells that the headers describe, or use appropriate non-table markup if this is not tabular data." },
|
|
4521
|
-
"td-has-header": { description: "Data cells in tables larger than 3x3 should have associated headers.", guidance: "In complex tables, screen reader users need header associations to understand data cells. Use th elements with scope attribute, or the headers attribute on td elements. For simple tables (≤3x3), this is less critical as context is usually clear." },
|
|
4522
|
-
"scope-attr-valid": { description: "The scope attribute on table headers must have a valid value.", guidance: "The scope attribute tells screen readers which cells a header applies to. Valid values are: row, col, rowgroup, colgroup. Using invalid values breaks the association between headers and cells." },
|
|
4523
|
-
"empty-table-header": { description: "Table header cells should have visible text.", guidance: "Empty table headers provide no information to screen reader users. Either add descriptive text to the header, or if the header is intentionally empty (like a corner cell), consider using a td element instead or adding a visually hidden label." },
|
|
4524
|
-
"duplicate-id-aria": { description: "IDs used in ARIA and label associations must be unique to avoid broken references.", guidance: "When aria-labelledby, aria-describedby, aria-controls, or label[for] reference a duplicate ID, only the first matching element is used. This breaks the intended relationship and may leave controls unnamed or descriptions missing. Ensure IDs referenced by ARIA attributes and label associations are unique throughout the document." },
|
|
4525
|
-
"video-caption": { description: "Video elements must have captions via <track kind='captions'>.", guidance: "Captions provide text alternatives for audio content in videos, benefiting deaf users and those who cannot hear audio. Add a <track> element with kind='captions' pointing to a WebVTT caption file. Captions should include both dialogue and important sound effects." },
|
|
4526
|
-
"audio-caption": { description: "Audio elements should have a text alternative or transcript.", guidance: "Audio-only content like podcasts or recordings needs a text alternative for deaf users. Provide a transcript either on the same page or linked nearby. The transcript should include all spoken content and descriptions of relevant sounds." },
|
|
4527
|
-
"color-contrast": { description: "Text elements must have sufficient color contrast against the background.", guidance: "WCAG SC 1.4.3 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (>=24px or >=18.66px bold). Increase the contrast by darkening the text or lightening the background, or vice versa." }
|
|
4528
|
-
},
|
|
4529
|
-
"document-title": { description: "Los documentos deben tener un elemento <title> para proporcionar a los usuarios una vista general del contenido.", guidance: "Los usuarios de lectores de pantalla dependen de los títulos de página para identificar y navegar entre pestañas/ventanas. Agregue un elemento <title> descriptivo en <head> que resuma el propósito de la página. Mantenga los títulos únicos en todo el sitio, colocando el contenido específico antes del nombre del sitio (por ejemplo, 'Contáctenos - Acme Corp')." },
|
|
4530
|
-
bypass: { description: "La página debe tener un mecanismo para omitir bloques de contenido repetidos.", guidance: 'Los usuarios de teclado deben poder omitir contenido repetitivo como la navegación. Proporcione un enlace de salto en la parte superior de la página que enlace al contenido principal (por ejemplo, <a href="#main">Saltar al contenido principal</a>), o use un landmark <main>. Los lectores de pantalla pueden saltar directamente a los landmarks, por lo que un elemento <main> correctamente marcado satisface este requisito.' },
|
|
4531
|
-
"page-has-heading-one": { description: "La página debe contener un encabezado de nivel uno.", guidance: "Un encabezado de nivel uno (<h1> o role='heading' con aria-level='1') ayuda a los usuarios a comprender el tema de la página y proporciona un punto de referencia para la navegación con lector de pantalla. Cada página debe tener exactamente un h1 que describa el contenido principal, típicamente coincidiendo o similar al título de la página." },
|
|
4532
|
-
"frame-title": { description: "Los marcos deben tener un nombre accesible.", guidance: "Los lectores de pantalla anuncian los títulos de los marcos cuando los usuarios navegan por ellos. Agregue un atributo title a los elementos <iframe> y <frame> que describa el propósito del marco (por ejemplo, <iframe title='Reproductor de video'>). Evite títulos genéricos como 'marco' o 'iframe'. Si el marco es decorativo, use aria-hidden='true'." },
|
|
4533
|
-
"frame-title-unique": { description: "Los títulos de los marcos deben ser únicos.", guidance: "Cuando varios marcos tienen títulos idénticos, los usuarios de lectores de pantalla no pueden distinguirlos. Dé a cada marco un título único y descriptivo que explique su propósito específico o contenido." },
|
|
4534
|
-
"meta-viewport": { description: "La etiqueta meta viewport no debe deshabilitar el zoom del usuario.", guidance: "Los usuarios con baja visión necesitan ampliar el contenido al 200% o más. Establecer user-scalable=no o maximum-scale=1 impide el zoom y no cumple con WCAG. Elimine estas restricciones. Si su diseño se rompe con zoom alto, corrija el diseño responsivo en lugar de impedir el zoom." },
|
|
4535
|
-
"meta-refresh": { description: "La etiqueta meta refresh no debe redirigir o actualizar automáticamente.", guidance: "Las actualizaciones o redirecciones automáticas de página pueden desorientar a los usuarios, especialmente aquellos que usan lectores de pantalla o con discapacidades cognitivas. Pueden perder su lugar o no tener tiempo para leer el contenido. Si se necesita una redirección, use una redirección del lado del servidor (HTTP 301/302). Para actualizaciones temporizadas, proporcione controles al usuario." },
|
|
4536
|
-
blink: { description: "El elemento <blink> no debe usarse.", guidance: "El contenido parpadeante puede causar convulsiones en usuarios con epilepsia fotosensible y es una distracción para usuarios con trastornos de atención. El elemento <blink> está obsoleto y nunca debe usarse. Si necesita llamar la atención sobre el contenido, use métodos menos intrusivos como color, bordes o iconos." },
|
|
4537
|
-
marquee: { description: "El elemento <marquee> no debe usarse.", guidance: "El contenido que se desplaza o se mueve es difícil de leer para muchos usuarios, especialmente aquellos con discapacidades cognitivas o visuales. El elemento <marquee> está obsoleto. Reemplace el texto en movimiento con contenido estático. Si el contenido debe desplazarse, proporcione controles de pausa/detención y asegúrese de que se detenga después de 5 segundos." },
|
|
4538
|
-
"img-alt": { description: `Las imágenes deben tener texto alternativo. Agregue un atributo alt a los elementos <img>. Las imágenes decorativas pueden usar un atributo alt vacío (alt=""), role='none' o role='presentation'.`, guidance: "Cada imagen necesita un atributo alt. Para imágenes informativas, describa el contenido o la función de manera concisa. Para imágenes decorativas (fondos, espaciadores, adornos puramente visuales), use alt='' para ocultarlas de los lectores de pantalla. Nunca omita alt por completo: los lectores de pantalla pueden leer el nombre del archivo en su lugar." },
|
|
4539
|
-
"svg-img-alt": { description: "Los elementos SVG con rol img, graphics-document o graphics-symbol deben tener un nombre accesible mediante un elemento <title>, aria-label o aria-labelledby.", guidance: "Los SVG en línea con role='img' necesitan nombres accesibles. Agregue un elemento <title> como primer hijo del SVG (los lectores de pantalla lo anunciarán), o use aria-label en el elemento SVG. Para SVGs complejos, use aria-labelledby haciendo referencia tanto a un elemento <title> como a un elemento <desc>. Los SVGs decorativos deben usar aria-hidden='true' en su lugar." },
|
|
4540
|
-
"input-image-alt": { description: 'Las entradas de imagen (<input type="image">) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.', guidance: "Los botones de imagen (<input type='image'>) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen." },
|
|
4541
|
-
"image-redundant-alt": { description: "El texto alternativo de la imagen no debe duplicar el texto del enlace o botón adyacente. Cuando el texto alt repite el texto circundante, los usuarios de lectores de pantalla escuchan la misma información dos veces.", guidance: "Cuando una imagen está dentro de un enlace o botón que también tiene texto, haga que el texto alt sea complementario en lugar de idéntico. Si la imagen es puramente decorativa en ese contexto, use alt='' para evitar la repetición." },
|
|
4542
|
-
"image-alt-redundant-words": { description: "El texto alternativo de la imagen no debe contener palabras como 'imagen', 'foto' o 'fotografía': los lectores de pantalla ya anuncian el tipo de elemento.", guidance: "Los lectores de pantalla ya anuncian 'imagen' o 'gráfico' antes de leer el texto alt, por lo que frases como 'imagen de', 'foto de' o 'fotografía de' son redundantes. Elimine estas palabras y describa lo que muestra la imagen. Por ejemplo, cambie 'imagen de un perro' a 'golden retriever jugando a buscar'." },
|
|
4543
|
-
"area-alt": { description: "Los elementos <area> del mapa de imagen deben tener texto alternativo.", guidance: "Cada región clicable en un mapa de imagen necesita texto alternativo para que los usuarios de lectores de pantalla sepan qué representa la región. Agregue un atributo alt a cada elemento <area> describiendo su propósito. Para mapas de imagen complejos, considere usar enfoques alternativos como SVG con enlaces incrustados, o una lista de enlaces de texto." },
|
|
4544
|
-
"object-alt": { description: "Los elementos <object> deben tener texto alternativo.", guidance: "Los elementos object incrustan contenido externo que puede no ser accesible para todos los usuarios. Proporcione texto alternativo mediante aria-label, aria-labelledby o un atributo title. El contenido de respaldo dentro de <object> solo se muestra cuando el objeto no se carga y no sirve como nombre accesible." },
|
|
4545
|
-
"role-img-alt": { description: "Los elementos con role='img' deben tener un nombre accesible.", guidance: "Cuando asigna role='img' a un elemento (como un div que contiene fuentes de iconos o fondos CSS), debe proporcionar un nombre accesible mediante aria-label o aria-labelledby. Sin esto, los usuarios de lectores de pantalla no tienen forma de entender lo que representa la imagen. Si la imagen es decorativa, use role='presentation' o role='none' en su lugar." },
|
|
4546
|
-
"server-side-image-map": { description: "No se deben usar mapas de imagen del lado del servidor.", guidance: "Los mapas de imagen del lado del servidor (usando el atributo ismap) envían coordenadas de clic al servidor, lo cual es inaccesible para usuarios de teclado y lectores de pantalla que no pueden hacer clic con precisión en regiones específicas. Reemplace con mapas de imagen del lado del cliente (elementos <map> con <area>) que proporcionan acceso por teclado y nombres accesibles, o use imágenes/botones enlazados." },
|
|
4547
|
-
label: { description: "Los elementos de formulario deben tener etiquetas. Use <label>, aria-label o aria-labelledby.", guidance: "Cada entrada de formulario necesita una etiqueta accesible para que los usuarios comprendan qué información ingresar. Use un elemento <label> con un atributo for que coincida con el id de la entrada, envuelva la entrada en un <label>, o use aria-label/aria-labelledby para componentes personalizados. Los placeholders no son suficientes como etiquetas ya que desaparecen al escribir." },
|
|
4548
|
-
"form-field-multiple-labels": { description: "Los campos de formulario no deben tener múltiples elementos label.", guidance: "Cuando un campo de formulario tiene múltiples elementos <label> apuntando a él, las tecnologías de asistencia pueden anunciar solo una etiqueta o comportarse de manera inconsistente. Use un solo <label> y combine cualquier texto adicional en él, o use aria-describedby para información complementaria." },
|
|
4549
|
-
"select-name": { description: "Los elementos select deben tener una etiqueta asociada programáticamente mediante <label>, aria-label o aria-labelledby.", guidance: "Los menús desplegables select necesitan etiquetas para que los usuarios comprendan qué elección están haciendo. Use un elemento <label> con un atributo for que coincida con el id del select, o envuelva el select en un <label>. Para selects sin etiquetas visibles, use aria-label. La primera <option> no es un sustituto de una etiqueta adecuada." },
|
|
4550
|
-
"input-button-name": { description: "Los botones de entrada deben tener texto discernible mediante value, aria-label o aria-labelledby.", guidance: "Los botones de entrada (<input type='submit'>, type='button', type='reset'>) necesitan nombres accesibles para que los usuarios sepan qué acción realiza el botón. Agregue un atributo value con texto descriptivo (por ejemplo, value='Enviar formulario'), o use aria-label si el valor debe diferir del nombre accesible." },
|
|
4551
|
-
"autocomplete-valid": { description: "El atributo autocomplete debe usar valores válidos de la especificación HTML.", guidance: "El atributo autocomplete ayuda a los usuarios a completar formularios identificando los propósitos de las entradas. Use valores estándar como 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. Esto beneficia a usuarios con discapacidades cognitivas, impedimentos motores y cualquier persona que use administradores de contraseñas o autocompletado. Consulte la especificación HTML para la lista completa de tokens válidos." },
|
|
4552
|
-
"label-content-name-mismatch": { description: "Los elementos interactivos con texto visible deben tener nombres accesibles que contengan ese texto.", guidance: "Para los usuarios de control por voz que activan controles hablando su etiqueta visible, el nombre accesible debe incluir el texto visible. Si aria-label es 'Enviar formulario' pero el botón muestra 'Enviar', los usuarios de voz que digan 'clic Enviar' no lo activarán. Asegúrese de que aria-label/aria-labelledby contenga o coincida con el texto visible." },
|
|
4553
|
-
"label-title-only": { description: "Los elementos de formulario no deben usar el atributo title como único nombre accesible.", guidance: "El atributo title no es confiable como etiqueta porque solo aparece al pasar el cursor/enfocar (no visible para usuarios táctiles) y a menudo es ignorado por las tecnologías de asistencia. Use un elemento <label> visible, aria-label o aria-labelledby en su lugar. El title puede complementar una etiqueta pero no debe reemplazarla." },
|
|
4554
|
-
tabindex: { description: "Los elementos no deben tener tabindex mayor que 0, lo cual altera el orden natural de tabulación.", guidance: "Los valores positivos de tabindex fuerzan a los elementos al frente del orden de tabulación independientemente de la posición en el DOM, creando una navegación impredecible para los usuarios de teclado. Use tabindex='0' para agregar elementos al orden natural de tabulación, o tabindex='-1' para hacer elementos enfocables programáticamente pero no en el orden de tabulación. Confíe en el orden del DOM para la secuencia de tabulación." },
|
|
4555
|
-
"focus-order-semantics": { description: "Los elementos que reciben el foco del teclado deben tener un rol apropiado para que las tecnologías de asistencia puedan transmitir su propósito. Los elementos no interactivos con tabindex='0' necesitan un rol ARIA interactivo válido.", guidance: "Al agregar tabindex='0' a elementos no interactivos como <div> o <span>, los lectores de pantalla los anuncian genéricamente. Agregue un rol apropiado (button, link, tab, etc.) para que los usuarios comprendan el propósito del elemento. También agregue manejadores de eventos de teclado (Enter/Espacio para botones, Enter para enlaces). Considere usar elementos interactivos nativos en su lugar." },
|
|
4556
|
-
"nested-interactive": { description: "Los controles interactivos no deben estar anidados dentro de otros.", guidance: "Anidar elementos interactivos (como un botón dentro de un enlace, o un enlace dentro de un botón) crea un comportamiento impredecible y confunde a las tecnologías de asistencia. El navegador puede eliminar el elemento interno del árbol de accesibilidad. Reestructure el HTML para que los elementos interactivos sean hermanos, no anidados. Si necesita una tarjeta clicable, use CSS y JavaScript en lugar de anidar." },
|
|
4557
|
-
"scrollable-region-focusable": { description: "Las regiones desplazables deben ser accesibles por teclado.", guidance: "El contenido que se desplaza debe ser accesible para los usuarios de teclado. Si una región tiene overflow:scroll u overflow:auto y contiene contenido desplazable, necesita tabindex='0' para ser enfocable, o debe contener elementos enfocables. Sin esto, los usuarios de teclado no pueden desplazar el contenido." },
|
|
4558
|
-
accesskeys: { description: "Los valores del atributo accesskey deben ser únicos.", guidance: "Cuando múltiples elementos comparten la misma accesskey, el comportamiento del navegador se vuelve impredecible; generalmente solo se activa el primer elemento. Asegúrese de que cada valor de accesskey sea único dentro de la página. También considere que las accesskeys pueden entrar en conflicto con los atajos del navegador y del lector de pantalla, así que úselas con moderación." },
|
|
4559
|
-
"heading-order": { description: "Los niveles de encabezado deben incrementarse de uno en uno; saltarse niveles (por ejemplo, h2 a h4) dificulta la navegación.", guidance: "Los usuarios de lectores de pantalla navegan por encabezados para comprender la estructura de la página. Saltarse niveles (h2 a h4) sugiere contenido faltante y crea confusión. Comience con h1 para el título de la página, luego use h2 para secciones principales, h3 para subsecciones, etc. Puede volver a subir (h3 a h2) al comenzar una nueva sección." },
|
|
4560
|
-
"empty-heading": { description: "Los encabezados deben tener texto discernible.", guidance: "Los usuarios de lectores de pantalla navegan las páginas por encabezados, por lo que los encabezados vacíos crean puntos de navegación confusos. Asegúrese de que todos los encabezados contengan texto visible o nombres accesibles. Si un encabezado se usa puramente para estilo visual, use CSS en lugar de elementos de encabezado." },
|
|
4561
|
-
"p-as-heading": { description: "Los párrafos no deben estilizarse para parecer encabezados.", guidance: "Cuando los párrafos se estilizan con negrita y fuentes grandes para parecer encabezados, los usuarios de lectores de pantalla pierden la estructura semántica. Use elementos de encabezado apropiados (h1-h6) en lugar de párrafos estilizados. Si necesita un estilo específico, aplique CSS a los elementos de encabezado manteniendo la jerarquía adecuada de encabezados." },
|
|
4562
|
-
"landmark-one-main": { description: "La página debe tener exactamente un landmark main.", guidance: "El landmark main contiene el contenido principal de la página. Los lectores de pantalla permiten a los usuarios saltar directamente al contenido principal. Use un solo elemento <main> (o role='main') para envolver el contenido central, excluyendo encabezados, pies de página y navegación." },
|
|
4563
|
-
"landmark-no-duplicate-banner": { description: "La página no debe tener más de un landmark banner.", guidance: "El landmark banner (típicamente <header>) identifica contenido orientado al sitio como logotipos y búsqueda. Solo se permite un banner de nivel superior por página. Si necesita múltiples encabezados, anídelos dentro de elementos de sección (article, section, aside) donde se convierten en encabezados de alcance en lugar de banners de nivel de página." },
|
|
4564
|
-
"landmark-no-duplicate-contentinfo": { description: "La página no debe tener más de un landmark contentinfo.", guidance: "El landmark contentinfo (típicamente <footer>) contiene información sobre la página como derechos de autor e información de contacto. Solo se permite un contentinfo de nivel superior por página. Anide pies de página adicionales dentro de elementos de sección para delimitar su alcance." },
|
|
4565
|
-
"landmark-no-duplicate-main": { description: "La página no debe tener más de un landmark main.", guidance: "Solo debe existir un landmark main por página. El landmark main identifica el área de contenido principal. Si tiene múltiples secciones de contenido, use <section> con encabezados apropiados en lugar de múltiples elementos main." },
|
|
4566
|
-
"landmark-banner-is-top-level": { description: "El landmark banner no debe estar anidado dentro de otro landmark.", guidance: "El landmark banner debe ser un landmark de nivel superior, no anidado dentro de article, aside, main, nav o section. Si un header está dentro de estos elementos, automáticamente se convierte en un encabezado genérico en lugar de un banner. Elimine el role='banner' explícito de los headers anidados o reestructure la página." },
|
|
4567
|
-
"landmark-contentinfo-is-top-level": { description: "El landmark contentinfo no debe estar anidado dentro de otro landmark.", guidance: "El landmark contentinfo debe ser un landmark de nivel superior. Un footer dentro de article, aside, main, nav o section se convierte en un pie de página de alcance, no en un landmark contentinfo. Elimine el role='contentinfo' explícito de los footers anidados o mueva el footer fuera de los elementos de sección." },
|
|
4568
|
-
"landmark-main-is-top-level": { description: "El landmark main no debe estar anidado dentro de otro landmark.", guidance: "El landmark main debe ser un landmark de nivel superior ya que representa el contenido principal de la página. No anide <main> o role='main' dentro de elementos article, aside, nav o section." },
|
|
4569
|
-
"landmark-complementary-is-top-level": { description: "El landmark aside (complementary) debe ser de nivel superior o estar directamente dentro de main.", guidance: "El landmark complementary (aside) debe ser de nivel superior o un hijo directo de main. Anidar aside profundamente dentro de otros landmarks reduce su descubrimiento para los usuarios de lectores de pantalla que navegan por landmarks." },
|
|
4570
|
-
"landmark-unique": { description: "Los landmarks deben tener etiquetas únicas cuando hay múltiples del mismo tipo.", guidance: "Cuando una página tiene múltiples landmarks del mismo tipo (por ejemplo, múltiples elementos nav), cada uno debe tener un nombre accesible único mediante aria-label o aria-labelledby. Esto ayuda a los usuarios de lectores de pantalla a distinguirlos (por ejemplo, 'Navegación principal' vs 'Navegación del pie de página')." },
|
|
4571
|
-
region: { description: "Todo el contenido de la página debe estar contenido dentro de landmarks.", guidance: "Los usuarios de lectores de pantalla navegan las páginas por landmarks. El contenido fuera de los landmarks es más difícil de encontrar y comprender. Envuelva todo el contenido visible en landmarks apropiados: <header>, <nav>, <main>, <aside>, <footer>, o <section> con una etiqueta. Los enlaces de salto pueden existir fuera de los landmarks." },
|
|
4572
|
-
list: { description: "<ul> y <ol> solo deben contener <li>, <script> o <template> como hijos directos.", guidance: "Los lectores de pantalla anuncian la estructura de la lista ('lista con 5 elementos') basándose en el marcado correcto. Colocar elementos que no son <li> directamente dentro de <ul> u <ol> rompe esta estructura. Envuelva el contenido en elementos <li>, o si necesita divs envolventes para el estilo, reestructure su CSS para estilizar los elementos <li> directamente." },
|
|
4573
|
-
listitem: { description: "Los elementos <li> deben estar contenidos en un <ul>, <ol> o <menu>.", guidance: "Los elementos de lista (<li>) solo tienen significado semántico dentro de un contenedor de lista (<ul>, <ol> o <menu>). Fuera de estos contenedores, las tecnologías de asistencia no pueden transmitir la relación de lista. Envuelva los elementos <li> en el contenedor de lista apropiado." },
|
|
4574
|
-
dlitem: { description: "Los elementos <dt> y <dd> deben estar contenidos en un <dl>.", guidance: "Los términos de definición (<dt>) y las definiciones (<dd>) solo tienen significado semántico dentro de una lista de definiciones (<dl>). Fuera de <dl>, se tratan como texto genérico. Envuelva los pares relacionados de <dt> y <dd> en un elemento <dl> para transmitir la relación término/definición a las tecnologías de asistencia." },
|
|
4575
|
-
"definition-list": { description: "Los elementos <dl> solo deben contener <dt>, <dd>, <div>, <script> o <template>.", guidance: "Las listas de definiciones tienen requisitos estrictos de contenido. Solo <dt> (términos), <dd> (definiciones) y <div> (para agrupar pares dt/dd) son hijos válidos. Otros elementos rompen la estructura de la lista para los lectores de pantalla. Mueva los elementos inválidos fuera del <dl>, o reestructure usando el marcado adecuado de lista de definiciones." },
|
|
4576
|
-
"aria-roles": { description: "Los valores de rol ARIA deben ser válidos.", guidance: "Los valores de rol inválidos son ignorados por las tecnologías de asistencia, lo que significa que el elemento no tendrá la semántica prevista. Verifique la ortografía y use solo roles definidos en la especificación WAI-ARIA. Los roles comunes incluyen: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem." },
|
|
4577
|
-
"aria-valid-attr": { description: "Los atributos ARIA deben ser válidos (correctamente escritos).", guidance: "Los atributos ARIA mal escritos son ignorados por las tecnologías de asistencia. Verifique la ortografía contra la especificación WAI-ARIA. Errores comunes: aria-labeledby (debe ser aria-labelledby), aria-role (debe ser role), aria-description (válido en ARIA 1.3+)." },
|
|
4578
|
-
"aria-valid-attr-value": { description: "Los atributos ARIA deben tener valores válidos.", guidance: "Cada atributo ARIA acepta tipos de valores específicos. Los atributos booleanos (aria-hidden, aria-disabled) aceptan solo 'true' o 'false'. Los atributos triestado (aria-checked, aria-pressed) también aceptan 'mixed'. Los atributos de token (aria-live, aria-autocomplete) aceptan valores predefinidos. Los atributos de referencia de ID (aria-labelledby, aria-describedby) deben referenciar IDs de elementos existentes." },
|
|
4579
|
-
"aria-required-attr": { description: "Los elementos con roles ARIA deben tener todos los atributos ARIA requeridos.", guidance: "Algunos roles ARIA requieren atributos específicos para funcionar correctamente. Por ejemplo, checkbox requiere aria-checked, slider requiere aria-valuenow, heading requiere aria-level. Sin estos atributos, las tecnologías de asistencia no pueden transmitir el estado o valor del elemento a los usuarios. Agregue el atributo requerido faltante con un valor apropiado." },
|
|
4580
|
-
"aria-allowed-attr": { description: "Los atributos ARIA deben estar permitidos para el rol del elemento.", guidance: "Cada rol ARIA admite atributos específicos. Usar atributos no admitidos crea confusión para las tecnologías de asistencia. Consulte la especificación ARIA para saber qué atributos son válidos para cada rol, o elimine el atributo si no es necesario." },
|
|
4581
|
-
"aria-allowed-role": { description: "El rol ARIA debe ser apropiado para el elemento.", guidance: "No todos los roles ARIA se pueden aplicar a todos los elementos HTML. Muchos elementos tienen roles implícitos (por ejemplo, <header> es implícitamente banner, <nav> es navigation, <main> es main). Agregar un rol explícito que coincida con el rol implícito es redundante. Agregar un rol conflictivo rompe la semántica. Elimine el atributo role o use un elemento diferente." },
|
|
4582
|
-
"aria-required-children": { description: "Ciertos roles ARIA requieren que estén presentes roles hijos específicos.", guidance: "Algunos roles ARIA representan contenedores que deben contener roles hijos específicos para una semántica adecuada. Por ejemplo, una lista debe contener listitems, un menú debe contener menuitems. Agregue los elementos hijos requeridos con roles apropiados, o use elementos HTML nativos que proporcionen esta semántica implícitamente (por ejemplo, <ul> con <li>)." },
|
|
4583
|
-
"aria-required-parent": { description: "Ciertos roles ARIA deben estar contenidos dentro de roles padre específicos.", guidance: "Algunos roles ARIA representan elementos que deben existir dentro de roles de contenedor específicos. Por ejemplo, un listitem debe estar dentro de una lista, un tab debe estar dentro de un tablist. Envuelva el elemento en el padre apropiado, o use elementos HTML nativos que proporcionen esta estructura (por ejemplo, <li> dentro de <ul>)." },
|
|
4584
|
-
"aria-hidden-body": { description: "aria-hidden='true' no debe estar presente en el body del documento.", guidance: "Establecer aria-hidden='true' en el elemento body oculta todo el contenido de la página de las tecnologías de asistencia, haciendo la página completamente inaccesible para los usuarios de lectores de pantalla. Elimine aria-hidden del elemento body. Si necesita ocultar contenido temporalmente (por ejemplo, detrás de un modal), use aria-hidden en secciones específicas en su lugar." },
|
|
4585
|
-
"aria-hidden-focus": { description: "Los elementos con aria-hidden='true' no deben contener elementos enfocables.", guidance: "Cuando aria-hidden='true' oculta un elemento de las tecnologías de asistencia pero el elemento contiene hijos enfocables, los usuarios de teclado pueden enfocar esos hijos pero los usuarios de lectores de pantalla no sabrán que existen. Elimine los elementos enfocables de la región oculta, agregue tabindex='-1' a ellos, o elimine aria-hidden." },
|
|
4586
|
-
"aria-command-name": { description: "Los comandos ARIA deben tener un nombre accesible.", guidance: "Los roles de comando ARIA interactivos (button, link, menuitem) deben tener nombres accesibles para que los usuarios sepan qué acción realizan. Agregue contenido de texto visible, aria-label o aria-labelledby para proporcionar un nombre." },
|
|
4587
|
-
"aria-input-field-name": { description: "Los campos de entrada ARIA deben tener un nombre accesible.", guidance: "Los widgets de entrada ARIA (combobox, listbox, searchbox, slider, spinbutton, textbox) deben tener nombres accesibles para que los usuarios comprendan qué datos ingresar. Agregue una etiqueta visible con aria-labelledby, o use aria-label si una etiqueta visible no es posible." },
|
|
4588
|
-
"aria-toggle-field-name": { description: "Los campos de alternancia ARIA deben tener un nombre accesible.", guidance: "Los controles de alternancia ARIA (checkbox, switch, radio, menuitemcheckbox, menuitemradio) deben tener nombres accesibles para que los usuarios comprendan qué opción están seleccionando. Agregue contenido de texto visible, aria-label, o use aria-labelledby para referenciar una etiqueta visible." },
|
|
4589
|
-
"aria-meter-name": { description: "Los elementos meter ARIA deben tener un nombre accesible.", guidance: "Los elementos meter muestran un valor dentro de un rango conocido (como uso de disco o fortaleza de contraseña). Deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué se está midiendo. Use aria-label o aria-labelledby para proporcionar contexto." },
|
|
4590
|
-
"aria-progressbar-name": { description: "Los elementos progressbar ARIA deben tener un nombre accesible.", guidance: "Los indicadores de progreso deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué proceso se está rastreando. Use aria-label (por ejemplo, 'Progreso de carga de archivo') o aria-labelledby para referenciar un encabezado o etiqueta visible." },
|
|
4591
|
-
"aria-dialog-name": { description: "Los diálogos ARIA deben tener un nombre accesible.", guidance: "Los elementos dialog y alertdialog deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan el propósito del diálogo cuando se abre. Use aria-label o aria-labelledby apuntando al encabezado del diálogo. Los elementos nativos <dialog> también deben tener un nombre accesible." },
|
|
4592
|
-
"aria-tooltip-name": { description: "Los tooltips ARIA deben tener un nombre accesible.", guidance: "Los elementos tooltip deben tener nombres accesibles (generalmente su contenido de texto). El contenido del tooltip típicamente sirve como el nombre accesible. Asegúrese de que el tooltip contenga contenido de texto descriptivo o tenga aria-label." },
|
|
4593
|
-
"aria-treeitem-name": { description: "Los elementos treeitem ARIA deben tener un nombre accesible.", guidance: "Los elementos de árbol deben tener nombres accesibles para que los usuarios de lectores de pantalla puedan comprender la estructura del árbol y navegarlo eficazmente. Proporcione contenido de texto, aria-label o aria-labelledby para cada treeitem." },
|
|
4594
|
-
"aria-prohibited-attr": { description: "Los atributos ARIA no deben estar prohibidos para el rol del elemento.", guidance: "Algunos roles ARIA prohíben ciertos atributos. Por ejemplo, roles como 'none', 'presentation', 'generic' y roles de nivel de texto (code, emphasis, strong) prohíben aria-label y aria-labelledby porque el nombramiento no está soportado para estos roles. Elimine los atributos prohibidos o cambie el rol." },
|
|
4595
|
-
"presentation-role-conflict": { description: "Los elementos con role='presentation' o role='none' no deben ser enfocables ni tener atributos ARIA globales.", guidance: "Cuando un elemento tiene role='presentation' o role='none', está marcado como decorativo y se elimina del árbol de accesibilidad. Sin embargo, si el elemento es enfocable o tiene ciertos atributos ARIA, el rol de presentación se ignora y el elemento permanece accesible. Esto crea confusión. Elimine el rol de presentación, o elimine la enfocabilidad/atributos ARIA." },
|
|
4596
|
-
"button-name": { description: "Los botones deben tener texto discernible.", guidance: "Los usuarios de lectores de pantalla necesitan saber qué hace un botón. Agregue contenido de texto visible, aria-label o aria-labelledby. Para botones de icono, use aria-label describiendo la acción (por ejemplo, aria-label='Cerrar'). Si el botón contiene una imagen, asegúrese de que la imagen tenga texto alt describiendo la acción del botón." },
|
|
4597
|
-
"summary-name": { description: "Los elementos <summary> deben tener un nombre accesible.", guidance: "El elemento <summary> proporciona la etiqueta visible para un widget de divulgación <details>. Debe tener contenido de texto descriptivo para que los usuarios de lectores de pantalla comprendan qué se revelará al expandirse. Agregue texto claro y conciso que indique qué contenido está en la sección de detalles." },
|
|
4598
|
-
"link-name": { description: "Los enlaces deben tener texto discernible mediante contenido, aria-label o aria-labelledby.", guidance: "Los usuarios de lectores de pantalla necesitan saber a dónde lleva un enlace. Agregue contenido de texto descriptivo, aria-label, o use aria-labelledby. Para enlaces de imagen, asegúrese de que la imagen tenga texto alt describiendo el destino del enlace. Evite texto genérico como 'haga clic aquí' o 'leer más': el texto del enlace debe tener sentido fuera de contexto." },
|
|
4599
|
-
"skip-link": { description: "Los enlaces de salto deben apuntar a un destino válido en la página.", guidance: "Los enlaces de salto permiten a los usuarios de teclado omitir la navegación repetitiva y saltar directamente al contenido principal. El enlace de salto debe ser el primer elemento enfocable en la página, enlazar al contenido principal (por ejemplo, href='#main'), y hacerse visible cuando se enfoca. Puede estar visualmente oculto hasta enfocarse usando CSS." },
|
|
4600
|
-
"link-in-text-block": { description: "Los enlaces dentro de bloques de texto deben distinguirse por algo más que solo el color.", guidance: "Los usuarios que no pueden percibir diferencias de color necesitan otras señales visuales para identificar enlaces. Los enlaces en texto deben tener subrayados u otros indicadores no cromáticos. Si usa solo color, asegure un contraste de 3:1 con el texto circundante Y proporcione una indicación adicional al enfocar/pasar el cursor." },
|
|
4601
|
-
"html-has-lang": { description: "El elemento <html> debe tener un atributo lang.", guidance: "Los lectores de pantalla usan el atributo lang para determinar qué reglas de idioma y pronunciación usar. Sin él, el contenido puede pronunciarse incorrectamente. Establezca lang en el idioma principal de la página (por ejemplo, lang='en' para inglés, lang='es' para español)." },
|
|
4602
|
-
"html-lang-valid": { description: "El atributo lang en <html> debe tener un valor válido.", guidance: "El atributo lang debe usar una etiqueta de idioma BCP 47 válida. Use un código de idioma de 2 o 3 letras (por ejemplo, 'en', 'fr', 'zh'), opcionalmente seguido de un código de región (por ejemplo, 'en-US', 'pt-BR'). Las etiquetas inválidas impiden que los lectores de pantalla pronuncien correctamente el contenido." },
|
|
4603
|
-
"valid-lang": { description: "El atributo lang debe tener un valor válido en todos los elementos.", guidance: "Cuando aparece contenido en un idioma diferente dentro de una página (por ejemplo, una cita en francés en un documento en inglés), envuélvalo con un atributo lang para asegurar la pronunciación correcta. El valor de lang debe ser una etiqueta BCP 47 válida. Códigos comunes: en, es, fr, de, zh, ja, pt, ar, ru." },
|
|
4604
|
-
"html-xml-lang-mismatch": { description: "Los atributos lang y xml:lang en <html> deben coincidir.", guidance: "En documentos XHTML, si tanto lang como xml:lang están presentes, deben especificar el mismo idioma base. Los valores no coincidentes confunden a las tecnologías de asistencia. Elimine xml:lang (preferido para HTML5) o asegúrese de que ambos atributos tengan valores idénticos." },
|
|
4605
|
-
"td-headers-attr": { description: "Todas las celdas en una tabla que usan el atributo headers deben referenciar IDs de encabezado válidos.", guidance: "El atributo headers en las celdas de tabla debe referenciar IDs de celdas de encabezado (th o td) dentro de la misma tabla. Esto crea asociaciones explícitas para lectores de pantalla. Verifique que todos los IDs referenciados existan y estén escritos correctamente. Para tablas simples, considere usar scope en elementos th en su lugar." },
|
|
4606
|
-
"th-has-data-cells": { description: "Los encabezados de tabla deben estar asociados con celdas de datos.", guidance: "Una tabla con celdas de encabezado (th) pero sin celdas de datos (td) probablemente es un mal uso del marcado de tabla para diseño o tiene contenido faltante. Agregue celdas de datos que los encabezados describan, o use marcado apropiado que no sea de tabla si estos no son datos tabulares." },
|
|
4607
|
-
"td-has-header": { description: "Las celdas de datos en tablas mayores de 3x3 deben tener encabezados asociados.", guidance: "En tablas complejas, los usuarios de lectores de pantalla necesitan asociaciones de encabezados para comprender las celdas de datos. Use elementos th con atributo scope, o el atributo headers en elementos td. Para tablas simples (3x3 o menos), esto es menos crítico ya que el contexto generalmente es claro." },
|
|
4608
|
-
"scope-attr-valid": { description: "El atributo scope en los encabezados de tabla debe tener un valor válido.", guidance: "El atributo scope indica a los lectores de pantalla a qué celdas se aplica un encabezado. Los valores válidos son: row, col, rowgroup, colgroup. Usar valores inválidos rompe la asociación entre encabezados y celdas." },
|
|
4609
|
-
"empty-table-header": { description: "Las celdas de encabezado de tabla deben tener texto visible.", guidance: "Los encabezados de tabla vacíos no proporcionan información a los usuarios de lectores de pantalla. Agregue texto descriptivo al encabezado, o si el encabezado está intencionalmente vacío (como una celda de esquina), considere usar un elemento td en su lugar o agregar una etiqueta visualmente oculta." },
|
|
4610
|
-
"duplicate-id-aria": { description: "Los IDs usados en asociaciones ARIA y label deben ser únicos para evitar referencias rotas.", guidance: "Cuando aria-labelledby, aria-describedby, aria-controls o label[for] referencian un ID duplicado, solo se usa el primer elemento coincidente. Esto rompe la relación prevista y puede dejar controles sin nombre o descripciones faltantes. Asegúrese de que los IDs referenciados por atributos ARIA y asociaciones de etiquetas sean únicos en todo el documento." },
|
|
4611
|
-
"video-caption": { description: "Los elementos de video deben tener subtítulos mediante <track kind='captions'>.", guidance: "Los subtítulos proporcionan alternativas de texto para el contenido de audio en videos, beneficiando a usuarios sordos y aquellos que no pueden escuchar el audio. Agregue un elemento <track> con kind='captions' apuntando a un archivo de subtítulos WebVTT. Los subtítulos deben incluir tanto el diálogo como los efectos de sonido importantes." },
|
|
4612
|
-
"audio-caption": { description: "Los elementos de audio deben tener una alternativa de texto o transcripción.", guidance: "El contenido solo de audio como podcasts o grabaciones necesita una alternativa de texto para usuarios sordos. Proporcione una transcripción en la misma página o enlazada cerca. La transcripción debe incluir todo el contenido hablado y descripciones de sonidos relevantes." },
|
|
4613
|
-
"color-contrast": { description: "Los elementos de texto deben tener suficiente contraste de color contra el fondo.", guidance: "WCAG SC 1.4.3 requiere una relación de contraste de al menos 4.5:1 para texto normal y 3:1 para texto grande (>=24px o >=18.66px en negrita). Aumente el contraste oscureciendo el texto o aclarando el fondo, o viceversa." }
|
|
4488
|
+
const Xn = {
|
|
4489
|
+
"document-title": { description: "Documents must have a <title> element to provide users with an overview of content.", guidance: "Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp').", messages: { "Document <title> element is empty.": "Document <title> element is empty.", "Document is missing a <title> element.": "Document is missing a <title> element." } },
|
|
4490
|
+
bypass: { description: "Page must have a mechanism to bypass repeated blocks of content.", guidance: 'Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.', messages: { "Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.": "Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link." } },
|
|
4491
|
+
"page-has-heading-one": { description: "Page should contain a level-one heading.", guidance: "A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have exactly one h1 that describes the main content, typically matching or similar to the page title.", messages: { "Page does not contain a level-one heading.": "Page does not contain a level-one heading." } },
|
|
4492
|
+
"frame-title": { description: "Frames must have an accessible name.", guidance: "Screen readers announce frame titles when users navigate frames. Add a title attribute to <iframe> and <frame> elements that describes the frame's purpose (e.g., <iframe title='Video player'>). Avoid generic titles like 'frame' or 'iframe'. If the frame is decorative, use aria-hidden='true'.", messages: { "Frame is missing an accessible name. Add a title attribute.": "Frame is missing an accessible name. Add a title attribute." } },
|
|
4493
|
+
"frame-title-unique": { description: "Frame titles should be unique.", guidance: "When multiple frames have identical titles, screen reader users cannot distinguish between them. Give each frame a unique, descriptive title that explains its specific purpose or content.", messages: { "Frame title is not unique. Use a distinct title for each frame.": "Frame title is not unique. Use a distinct title for each frame." } },
|
|
4494
|
+
"meta-viewport": { description: "Viewport meta tag must not disable user scaling.", guidance: "Users with low vision need to zoom content up to 200% or more. Setting user-scalable=no or maximum-scale=1 prevents zooming and fails WCAG. Remove these restrictions. If your layout breaks at high zoom, fix the responsive design rather than preventing zoom.", messages: { "Viewport disables user scaling (user-scalable={0}). Remove this restriction.": "Viewport disables user scaling (user-scalable={0}). Remove this restriction.", "Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove.": "Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove." } },
|
|
4495
|
+
"meta-refresh": { description: "Meta refresh must not redirect or refresh automatically.", guidance: "Automatic page refreshes or redirects can disorient users, especially those using screen readers or with cognitive disabilities. They may lose their place or not have time to read content. If a redirect is needed, use a server-side redirect (HTTP 301/302) instead. For timed refreshes, provide user controls.", messages: { "Page redirects after {0} seconds without warning. Use server-side redirect.": "Page redirects after {0} seconds without warning. Use server-side redirect.", "Page auto-refreshes after {0} seconds. Provide user control over refresh.": "Page auto-refreshes after {0} seconds. Provide user control over refresh." } },
|
|
4496
|
+
blink: { description: "The <blink> element must not be used.", guidance: "Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons.", messages: { "The <blink> element causes accessibility issues. Remove it entirely.": "The <blink> element causes accessibility issues. Remove it entirely." } },
|
|
4497
|
+
marquee: { description: "The <marquee> element must not be used.", guidance: "Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds.", messages: { "The <marquee> element causes accessibility issues. Replace with static content.": "The <marquee> element causes accessibility issues. Replace with static content." } },
|
|
4498
|
+
"img-alt": { description: `Images must have alternate text. Add an alt attribute to <img> elements. Decorative images may use an empty alt attribute (alt=""), role='none', or role='presentation'.`, guidance: "Every image needs an alt attribute. For informative images, describe the content or function concisely. For decorative images (backgrounds, spacers, purely visual flourishes), use alt='' to hide them from screen readers. Never omit alt entirely—screen readers may read the filename instead.", messages: { 'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.': 'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.', "Image element missing alt attribute.": "Image element missing alt attribute.", 'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.': 'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.' } },
|
|
4499
|
+
"svg-img-alt": { description: "SVG elements with an img, graphics-document, or graphics-symbol role must have an accessible name via a <title> element, aria-label, or aria-labelledby.", guidance: "Inline SVGs with role='img' need accessible names. Add a <title> element as the first child of the SVG (screen readers will announce it), or use aria-label on the SVG element. For complex SVGs, use aria-labelledby referencing both a <title> and <desc> element. Decorative SVGs should use aria-hidden='true' instead.", messages: { "{0} with role='{1}' has no accessible name.": "{0} with role='{1}' has no accessible name." } },
|
|
4500
|
+
"input-image-alt": { description: 'Image inputs (<input type="image">) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.', guidance: "Image buttons (<input type='image'>) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.", messages: { "Image input missing alt text.": "Image input missing alt text." } },
|
|
4501
|
+
"image-redundant-alt": { description: "Image alt text should not duplicate adjacent link or button text. When alt text repeats surrounding text, screen reader users hear the same information twice.", guidance: "When an image is inside a link or button that also has text, make the alt text complementary rather than identical. If the image is purely decorative in that context, use alt='' to avoid repetition.", messages: { 'Alt text "{0}" duplicates surrounding {1} text.': 'Alt text "{0}" duplicates surrounding {1} text.' } },
|
|
4502
|
+
"image-alt-redundant-words": { description: "Image alt text should not contain words like 'image', 'photo', or 'picture' — screen readers already announce the element type.", guidance: "Screen readers already announce 'image' or 'graphic' before reading alt text, so phrases like 'image of', 'photo of', or 'picture of' are redundant. Remove these words and describe what the image shows. For example, change 'image of a dog' to 'golden retriever playing fetch'.", messages: { 'Alt text "{0}" contains redundant word(s): {1}.': 'Alt text "{0}" contains redundant word(s): {1}.' } },
|
|
4503
|
+
"area-alt": { description: "Image map <area> elements must have alternative text.", guidance: "Each clickable region in an image map needs alternative text so screen reader users know what the region represents. Add an alt attribute to every <area> element describing its purpose. For complex image maps, consider using alternative approaches like SVG with embedded links, or a list of text links.", messages: { "Image map <area> element is missing alternative text.": "Image map <area> element is missing alternative text." } },
|
|
4504
|
+
"object-alt": { description: "<object> elements must have alternative text.", guidance: "Object elements embed external content that may not be accessible to all users. Provide alternative text via aria-label, aria-labelledby, or a title attribute. The fallback content inside <object> is only shown when the object fails to load and does not serve as an accessible name.", messages: { "<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.": "<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute." } },
|
|
4505
|
+
"role-img-alt": { description: "Elements with role='img' must have an accessible name.", guidance: "When you assign role='img' to an element (like a div containing icon fonts or CSS backgrounds), you must provide an accessible name via aria-label or aria-labelledby. Without this, screen reader users have no way to understand what the image represents. If the image is decorative, use role='presentation' or role='none' instead.", messages: { "Element with role='img' has no accessible name. Add aria-label or aria-labelledby.": "Element with role='img' has no accessible name. Add aria-label or aria-labelledby." } },
|
|
4506
|
+
"server-side-image-map": { description: "Server-side image maps must not be used.", guidance: "Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead.", messages: { "Server-side image map detected. Use client-side image map with <map> and <area> elements instead.": "Server-side image map detected. Use client-side image map with <map> and <area> elements instead." } },
|
|
4507
|
+
label: { description: "Form elements must have labels. Use <label>, aria-label, or aria-labelledby.", guidance: "Every form input needs an accessible label so users understand what information to enter. Use a <label> element with a for attribute matching the input's id, wrap the input in a <label>, or use aria-label/aria-labelledby for custom components. Placeholders are not sufficient as labels since they disappear when typing.", messages: { "Form element has no accessible label.": "Form element has no accessible label." } },
|
|
4508
|
+
"form-field-multiple-labels": { description: "Form fields should not have multiple label elements.", guidance: "When a form field has multiple <label> elements pointing to it, assistive technologies may announce only one label or behave inconsistently. Use a single <label> and combine any additional text into it, or use aria-describedby for supplementary information.", messages: { "Form field has {0} labels. Use a single label element.": "Form field has {0} labels. Use a single label element." } },
|
|
4509
|
+
"select-name": { description: "Select elements must have a programmatically associated label via <label>, aria-label, or aria-labelledby.", guidance: "Select dropdowns need labels so users understand what choice they're making. Use a <label> element with a for attribute matching the select's id, or wrap the select in a <label>. For selects without visible labels, use aria-label. The first <option> is not a substitute for a proper label.", messages: { "Select element has no accessible name.": "Select element has no accessible name." } },
|
|
4510
|
+
"input-button-name": { description: "Input buttons must have discernible text via value, aria-label, or aria-labelledby.", guidance: "Input buttons (<input type='submit'>, type='button', type='reset'>) need accessible names so users know what action the button performs. Add a value attribute with descriptive text (e.g., value='Submit Form'), or use aria-label if the value must differ from the accessible name.", messages: { "Input button has no discernible text.": "Input button has no discernible text." } },
|
|
4511
|
+
"autocomplete-valid": { description: "Autocomplete attribute must use valid values from the HTML specification.", guidance: "The autocomplete attribute helps users fill forms by identifying input purposes. Use standard values like 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. This benefits users with cognitive disabilities, motor impairments, and anyone using password managers or autofill. Check the HTML specification for the complete list of valid tokens.", messages: { 'Invalid autocomplete value "{0}".': 'Invalid autocomplete value "{0}".' } },
|
|
4512
|
+
"label-content-name-mismatch": { description: "Interactive elements with visible text must have accessible names that contain that text.", guidance: "For voice control users who activate controls by speaking their visible label, the accessible name must include the visible text. If aria-label is 'Submit form' but the button shows 'Send', voice users saying 'click Send' won't activate it. Ensure aria-label/aria-labelledby contains or matches the visible text.", messages: { 'Accessible name "{0}" does not contain visible text "{1}".': 'Accessible name "{0}" does not contain visible text "{1}".' } },
|
|
4513
|
+
"label-title-only": { description: "Form elements should not use title attribute as the only accessible name.", guidance: "The title attribute is unreliable as a label because it only appears on hover/focus (not visible to touch users) and is often ignored by assistive technologies. Use a visible <label> element, aria-label, or aria-labelledby instead. Title can supplement a label but should not replace it.", messages: { "Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead.": "Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead." } },
|
|
4514
|
+
tabindex: { description: "Elements should not have tabindex greater than 0, which disrupts natural tab order.", guidance: "Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence.", messages: { 'Element has tabindex="{0}" which disrupts tab order.': 'Element has tabindex="{0}" which disrupts tab order.' } },
|
|
4515
|
+
"focus-order-semantics": { description: "Elements that receive keyboard focus must have an appropriate role so assistive technologies can convey their purpose. Non-interactive elements with tabindex='0' need a valid interactive ARIA role.", guidance: "When adding tabindex='0' to non-interactive elements like <div> or <span>, screen readers announce them generically. Add an appropriate role (button, link, tab, etc.) so users understand the element's purpose. Also add keyboard event handlers (Enter/Space for buttons, Enter for links). Consider using native interactive elements instead.", messages: { 'Non-interactive <{0}> with tabindex="0" has no interactive role.': 'Non-interactive <{0}> with tabindex="0" has no interactive role.' } },
|
|
4516
|
+
"nested-interactive": { description: "Interactive controls must not be nested inside each other.", guidance: "Nesting interactive elements (like a button inside a link, or a link inside a button) creates unpredictable behavior and confuses assistive technologies. The browser may remove the inner element from the accessibility tree. Restructure the HTML so interactive elements are siblings, not nested. If you need a clickable card, use CSS and JavaScript rather than nesting.", messages: { "Interactive element <{0}> is nested inside <{1}>.": "Interactive element <{0}> is nested inside <{1}>." } },
|
|
4517
|
+
"scrollable-region-focusable": { description: "Scrollable regions must be keyboard accessible.", guidance: "Content that scrolls must be accessible to keyboard users. If a region has overflow:scroll or overflow:auto and contains scrollable content, it needs either tabindex='0' to be focusable, or it must contain focusable elements. Without this, keyboard users cannot scroll the content.", messages: { "Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements.": "Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements." } },
|
|
4518
|
+
accesskeys: { description: "Accesskey attribute values must be unique.", guidance: "When multiple elements share the same accesskey, browser behavior becomes unpredictable - usually only the first element is activated. Ensure each accesskey value is unique within the page. Also consider that accesskeys can conflict with browser and screen reader shortcuts, so use them sparingly.", messages: { 'Duplicate accesskey "{0}". Each accesskey must be unique.': 'Duplicate accesskey "{0}". Each accesskey must be unique.' } },
|
|
4519
|
+
"heading-order": { description: "Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.", guidance: "Screen reader users navigate by headings to understand page structure. Skipping levels (h2 to h4) suggests missing content and creates confusion. Start with h1 for the page title, then use h2 for main sections, h3 for subsections, etc. You can go back up (h3 to h2) when starting a new section.", messages: { "Heading level {0} skipped from level {1}.": "Heading level {0} skipped from level {1}." } },
|
|
4520
|
+
"empty-heading": { description: "Headings must have discernible text.", guidance: "Screen reader users navigate pages by headings, so empty headings create confusing navigation points. Ensure all headings contain visible text or accessible names. If a heading is used purely for visual styling, use CSS instead of heading elements.", messages: { "Heading is empty. Add text content or remove the heading element.": "Heading is empty. Add text content or remove the heading element." } },
|
|
4521
|
+
"p-as-heading": { description: "Paragraphs should not be styled to look like headings.", guidance: "When paragraphs are styled with bold, large fonts to look like headings, screen reader users miss the semantic structure. Use proper heading elements (h1-h6) instead of styled paragraphs. If you need specific styling, apply CSS to the heading elements while maintaining proper heading hierarchy.", messages: { "Paragraph appears to be styled as a heading. Use an h1-h6 element instead.": "Paragraph appears to be styled as a heading. Use an h1-h6 element instead." } },
|
|
4522
|
+
"landmark-one-main": { description: "Page should have exactly one main landmark.", guidance: "The main landmark contains the primary content of the page. Screen readers allow users to jump directly to main content. Use a single <main> element (or role='main') to wrap the central content, excluding headers, footers, and navigation.", messages: { "Page has no main landmark.": "Page has no main landmark.", "Page has multiple main landmarks.": "Page has multiple main landmarks." } },
|
|
4523
|
+
"landmark-no-duplicate-banner": { description: "Page should not have more than one banner landmark.", guidance: "The banner landmark (typically <header>) identifies site-oriented content like logos and search. Only one top-level banner is allowed per page. If you need multiple headers, nest them inside sectioning elements (article, section, aside) where they become scoped headers rather than page-level banners.", messages: { "Page has multiple banner landmarks.": "Page has multiple banner landmarks." } },
|
|
4524
|
+
"landmark-no-duplicate-contentinfo": { description: "Page should not have more than one contentinfo landmark.", guidance: "The contentinfo landmark (typically <footer>) contains information about the page like copyright and contact info. Only one top-level contentinfo is allowed per page. Nest additional footers inside sectioning elements to scope them.", messages: { "Page has multiple contentinfo landmarks.": "Page has multiple contentinfo landmarks." } },
|
|
4525
|
+
"landmark-no-duplicate-main": { description: "Page should not have more than one main landmark.", guidance: "Only one main landmark should exist per page. The main landmark identifies the primary content area. If you have multiple content sections, use <section> with appropriate headings instead of multiple main elements.", messages: { "Page has multiple main landmarks.": "Page has multiple main landmarks." } },
|
|
4526
|
+
"landmark-banner-is-top-level": { description: "Banner landmark should not be nested within another landmark.", guidance: "The banner landmark should be a top-level landmark, not nested inside article, aside, main, nav, or section. If a header is inside these elements, it automatically becomes a generic header rather than a banner. Remove explicit role='banner' from nested headers or restructure the page.", messages: { "Banner landmark is nested within another landmark.": "Banner landmark is nested within another landmark." } },
|
|
4527
|
+
"landmark-contentinfo-is-top-level": { description: "Contentinfo landmark should not be nested within another landmark.", guidance: "The contentinfo landmark should be a top-level landmark. A footer inside article, aside, main, nav, or section becomes a scoped footer, not a contentinfo landmark. Remove explicit role='contentinfo' from nested footers or move the footer outside sectioning elements.", messages: { "Contentinfo landmark is nested within another landmark.": "Contentinfo landmark is nested within another landmark." } },
|
|
4528
|
+
"landmark-main-is-top-level": { description: "Main landmark should not be nested within another landmark.", guidance: "The main landmark must be a top-level landmark since it represents the primary content of the page. Do not nest <main> or role='main' inside article, aside, nav, or section elements.", messages: { "Main landmark is nested within another landmark.": "Main landmark is nested within another landmark." } },
|
|
4529
|
+
"landmark-complementary-is-top-level": { description: "Aside (complementary) landmark should be top-level or directly inside main.", guidance: "The complementary landmark (aside) should be top-level or a direct child of main. Nesting aside deep within other landmarks reduces its discoverability for screen reader users navigating by landmarks.", messages: { "Complementary landmark should be top-level.": "Complementary landmark should be top-level." } },
|
|
4530
|
+
"landmark-unique": { description: "Landmarks should have unique labels when there are multiple of the same type.", guidance: "When a page has multiple landmarks of the same type (e.g., multiple nav elements), each should have a unique accessible name via aria-label or aria-labelledby. This helps screen reader users distinguish between them (e.g., 'Main navigation' vs 'Footer navigation').", messages: { 'Multiple {0} landmarks have the same label "{1}".': 'Multiple {0} landmarks have the same label "{1}".', "Multiple {0} landmarks have no label. Add unique aria-label attributes.": "Multiple {0} landmarks have no label. Add unique aria-label attributes." } },
|
|
4531
|
+
region: { description: "All page content should be contained within landmarks.", guidance: "Screen reader users navigate pages by landmarks. Content outside landmarks is harder to find and understand. Wrap all visible content in appropriate landmarks: <header>, <nav>, <main>, <aside>, <footer>, or <section> with a label. Skip links may exist outside landmarks.", messages: { "Content is not contained within a landmark region.": "Content is not contained within a landmark region." } },
|
|
4532
|
+
list: { description: "<ul> and <ol> must only contain <li>, <script>, or <template> as direct children.", guidance: "Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, restructure your CSS to style the <li> elements directly.", messages: { "List contains non-<li> child <{0}>.": "List contains non-<li> child <{0}>." } },
|
|
4533
|
+
listitem: { description: "<li> elements must be contained in a <ul>, <ol>, or <menu>.", guidance: "List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Outside of these containers, assistive technologies cannot convey the list relationship. Wrap <li> elements in the appropriate list container.", messages: { "<li> is not contained in a <ul>, <ol>, or <menu>.": "<li> is not contained in a <ul>, <ol>, or <menu>." } },
|
|
4534
|
+
dlitem: { description: "<dt> and <dd> elements must be contained in a <dl>.", guidance: "Definition terms (<dt>) and definitions (<dd>) only have semantic meaning inside a definition list (<dl>). Outside of <dl>, they're treated as generic text. Wrap related <dt> and <dd> pairs in a <dl> element to convey the term/definition relationship to assistive technologies.", messages: { "<{0}> is not contained in a <dl>.": "<{0}> is not contained in a <dl>." } },
|
|
4535
|
+
"definition-list": { description: "<dl> elements must only contain <dt>, <dd>, <div>, <script>, or <template>.", guidance: "Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or restructure using proper definition list markup.", messages: { "<dl> contains invalid child <{0}>.": "<dl> contains invalid child <{0}>." } },
|
|
4536
|
+
"aria-roles": { description: "ARIA role values must be valid.", guidance: "Invalid role values are ignored by assistive technologies, meaning the element will not have the intended semantics. Check the spelling and use only roles defined in the WAI-ARIA specification. Common roles include: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.", messages: { 'Invalid ARIA role "{0}".': 'Invalid ARIA role "{0}".' } },
|
|
4537
|
+
"aria-valid-attr": { description: "ARIA attributes must be valid (correctly spelled).", guidance: "Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+).", messages: { 'Invalid ARIA attribute "{0}".': 'Invalid ARIA attribute "{0}".' } },
|
|
4538
|
+
"aria-valid-attr-value": { description: "ARIA attributes must have valid values.", guidance: "Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs.", messages: { '{0} must be "true" or "false", got "{1}".': '{0} must be "true" or "false", got "{1}".', '{0} must be "true", "false", or "mixed", got "{1}".': '{0} must be "true", "false", or "mixed", got "{1}".', '{0} must be an integer, got "{1}".': '{0} must be an integer, got "{1}".', '{0} must be a number, got "{1}".': '{0} must be a number, got "{1}".', 'Invalid value "{0}" for {1}.': 'Invalid value "{0}" for {1}.' } },
|
|
4539
|
+
"aria-required-attr": { description: "Elements with ARIA roles must have all required ARIA attributes.", guidance: "Some ARIA roles require specific attributes to function correctly. For example, checkbox requires aria-checked, slider requires aria-valuenow, heading requires aria-level. Without these attributes, assistive technologies cannot convey the element's state or value to users. Add the missing required attribute with an appropriate value.", messages: { 'Role "{0}" requires attribute "{1}".': 'Role "{0}" requires attribute "{1}".' } },
|
|
4540
|
+
"aria-allowed-attr": { description: "ARIA attributes must be allowed for the element's role.", guidance: "Each ARIA role supports specific attributes. Using unsupported attributes creates confusion for assistive technologies. Check the ARIA specification for which attributes are valid for each role, or remove the attribute if it's not needed.", messages: { 'ARIA attribute "{0}" is not allowed on role "{1}".': 'ARIA attribute "{0}" is not allowed on role "{1}".' } },
|
|
4541
|
+
"aria-allowed-role": { description: "ARIA role must be appropriate for the element.", guidance: "Not all ARIA roles can be applied to all HTML elements. Many elements have implicit roles (e.g., <header> is implicitly banner, <nav> is navigation, <main> is main). Adding an explicit role that matches the implicit role is redundant. Adding a conflicting role breaks semantics. Either remove the role attribute or use a different element.", messages: { "Element <{0}> should not have an explicit role.": "Element <{0}> should not have an explicit role.", 'Role "{0}" is not allowed on element <{1}>.': 'Role "{0}" is not allowed on element <{1}>.' } },
|
|
4542
|
+
"aria-required-children": { description: "Certain ARIA roles require specific child roles to be present.", guidance: "Some ARIA roles represent containers that must contain specific child roles for proper semantics. For example, a list must contain listitems, a menu must contain menuitems. Add the required child elements with appropriate roles, or use native HTML elements that provide these semantics implicitly (e.g., <ul> with <li>).", messages: { 'Role "{0}" requires children with role: {1}.': 'Role "{0}" requires children with role: {1}.' } },
|
|
4543
|
+
"aria-required-parent": { description: "Certain ARIA roles must be contained within specific parent roles.", guidance: "Some ARIA roles represent items that must exist within specific container roles. For example, a listitem must be within a list, a tab must be within a tablist. Wrap the element in the appropriate parent, or use native HTML elements that provide this structure (e.g., <li> inside <ul>).", messages: { 'Role "{0}" must be contained within: {1}.': 'Role "{0}" must be contained within: {1}.' } },
|
|
4544
|
+
"aria-hidden-body": { description: "aria-hidden='true' must not be present on the document body.", guidance: "Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead.", messages: { "aria-hidden='true' on body hides all content from assistive technologies.": "aria-hidden='true' on body hides all content from assistive technologies." } },
|
|
4545
|
+
"aria-hidden-focus": { description: "Elements with aria-hidden='true' must not contain focusable elements.", guidance: "When aria-hidden='true' hides an element from assistive technologies but the element contains focusable children, keyboard users can focus those children but screen reader users won't know they exist. Either remove focusable elements from the hidden region, add tabindex='-1' to them, or remove aria-hidden.", messages: { "Focusable element is inside an aria-hidden region.": "Focusable element is inside an aria-hidden region." } },
|
|
4546
|
+
"aria-command-name": { description: "ARIA commands must have an accessible name.", guidance: "Interactive ARIA command roles (button, link, menuitem) must have accessible names so users know what action they perform. Add visible text content, aria-label, or aria-labelledby to provide a name.", messages: { "ARIA command has no accessible name.": "ARIA command has no accessible name." } },
|
|
4547
|
+
"aria-input-field-name": { description: "ARIA input fields must have an accessible name.", guidance: "ARIA input widgets (combobox, listbox, searchbox, slider, spinbutton, textbox) must have accessible names so users understand what data to enter. Add a visible label with aria-labelledby, or use aria-label if a visible label is not possible.", messages: { "ARIA input field has no accessible name.": "ARIA input field has no accessible name." } },
|
|
4548
|
+
"aria-toggle-field-name": { description: "ARIA toggle fields must have an accessible name.", guidance: "ARIA toggle controls (checkbox, switch, radio, menuitemcheckbox, menuitemradio) must have accessible names so users understand what option they're selecting. Add visible text content, aria-label, or use aria-labelledby to reference a visible label.", messages: { "ARIA toggle field has no accessible name.": "ARIA toggle field has no accessible name." } },
|
|
4549
|
+
"aria-meter-name": { description: "ARIA meter elements must have an accessible name.", guidance: "Meter elements display a value within a known range (like disk usage or password strength). They must have accessible names so screen reader users understand what is being measured. Use aria-label or aria-labelledby to provide context.", messages: { "Meter has no accessible name.": "Meter has no accessible name." } },
|
|
4550
|
+
"aria-progressbar-name": { description: "ARIA progressbar elements must have an accessible name.", guidance: "Progress indicators must have accessible names so screen reader users understand what process is being tracked. Use aria-label (e.g., 'File upload progress') or aria-labelledby to reference a visible heading or label.", messages: { "Progressbar has no accessible name.": "Progressbar has no accessible name." } },
|
|
4551
|
+
"aria-dialog-name": { description: "ARIA dialogs must have an accessible name.", guidance: "Dialog and alertdialog elements must have accessible names so screen reader users understand the dialog's purpose when it opens. Use aria-label or aria-labelledby pointing to the dialog's heading. Native <dialog> elements should also have an accessible name.", messages: { "Dialog has no accessible name.": "Dialog has no accessible name." } },
|
|
4552
|
+
"aria-tooltip-name": { description: "ARIA tooltips must have an accessible name.", guidance: "Tooltip elements must have accessible names (usually their text content). The tooltip content itself typically serves as the accessible name. Ensure the tooltip contains descriptive text content or has aria-label.", messages: { "Tooltip has no accessible name.": "Tooltip has no accessible name." } },
|
|
4553
|
+
"aria-treeitem-name": { description: "ARIA treeitem elements must have an accessible name.", guidance: "Tree items must have accessible names so screen reader users can understand the tree structure and navigate it effectively. Provide text content, aria-label, or aria-labelledby for each treeitem.", messages: { "Treeitem has no accessible name.": "Treeitem has no accessible name." } },
|
|
4554
|
+
"aria-prohibited-attr": { description: "ARIA attributes must not be prohibited for the element's role.", guidance: "Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role.", messages: { "aria-label and aria-labelledby are prohibited on <{0}> elements.": "aria-label and aria-labelledby are prohibited on <{0}> elements.", 'aria-label and aria-labelledby are prohibited on role "{0}".': 'aria-label and aria-labelledby are prohibited on role "{0}".', 'Attribute "{0}" is prohibited on role "{1}".': 'Attribute "{0}" is prohibited on role "{1}".' } },
|
|
4555
|
+
"presentation-role-conflict": { description: "Elements with role='presentation' or role='none' must not be focusable or have global ARIA attributes.", guidance: "When an element has role='presentation' or role='none', it's marked as decorative and removed from the accessibility tree. However, if the element is focusable or has certain ARIA attributes, the presentation role is ignored and the element remains accessible. This creates confusion. Either remove the presentation role, or remove the focusability/ARIA attributes.", messages: { "Presentation role conflicts with: {0}. The role will be ignored.": "Presentation role conflicts with: {0}. The role will be ignored.", 'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.': 'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.' } },
|
|
4556
|
+
"button-name": { description: "Buttons must have discernible text.", guidance: "Screen reader users need to know what a button does. Add visible text content, aria-label, or aria-labelledby. For icon buttons, use aria-label describing the action (e.g., aria-label='Close'). If the button contains an image, ensure the image has alt text describing the button's action.", messages: { "Button has no discernible text.": "Button has no discernible text." } },
|
|
4557
|
+
"summary-name": { description: "<summary> elements must have an accessible name.", guidance: "The <summary> element provides the visible label for a <details> disclosure widget. It must have descriptive text content so screen reader users understand what will be revealed when expanded. Add clear, concise text that indicates what content is contained in the details section.", messages: { "<summary> element has no accessible name. Add descriptive text.": "<summary> element has no accessible name. Add descriptive text." } },
|
|
4558
|
+
"link-name": { description: "Links must have discernible text via content, aria-label, or aria-labelledby.", guidance: "Screen reader users need to know where a link goes. Add descriptive text content, aria-label, or use aria-labelledby. For image links, ensure the image has alt text describing the link destination. Avoid generic text like 'click here' or 'read more'—link text should make sense out of context.", messages: { "Link has no discernible text.": "Link has no discernible text." } },
|
|
4559
|
+
"skip-link": { description: "Skip links must point to a valid target on the page.", guidance: "Skip links allow keyboard users to bypass repetitive navigation and jump directly to main content. The skip link should be the first focusable element on the page, link to the main content (e.g., href='#main'), and become visible when focused. It can be visually hidden until focused using CSS.", messages: { 'Skip link points to "#{0}" which does not exist on the page.': 'Skip link points to "#{0}" which does not exist on the page.' } },
|
|
4560
|
+
"link-in-text-block": { description: "Links within text blocks must be distinguishable by more than color alone.", guidance: "Users who cannot perceive color differences need other visual cues to identify links. Links in text should have underlines or other non-color indicators. If using color alone, ensure 3:1 contrast with surrounding text AND provide additional indication on focus/hover.", messages: { "Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.": "Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border." } },
|
|
4561
|
+
"html-has-lang": { description: "The <html> element must have a lang attribute.", guidance: "Screen readers use the lang attribute to determine which language rules and pronunciation to use. Without it, content may be mispronounced. Set lang to the primary language of the page (e.g., lang='en' for English, lang='es' for Spanish).", messages: { "<html> element missing lang attribute.": "<html> element missing lang attribute." } },
|
|
4562
|
+
"html-lang-valid": { description: "The lang attribute on <html> must have a valid value.", guidance: "The lang attribute must use a valid BCP 47 language tag. Use a 2 or 3 letter language code (e.g., 'en', 'fr', 'zh'), optionally followed by a region code (e.g., 'en-US', 'pt-BR'). Invalid tags prevent screen readers from correctly pronouncing content.", messages: { 'Invalid lang attribute value "{0}".': 'Invalid lang attribute value "{0}".' } },
|
|
4563
|
+
"valid-lang": { description: "The lang attribute must have a valid value on all elements.", guidance: "When content in a different language appears within a page (e.g., a French quote in an English document), wrap it with a lang attribute to ensure correct pronunciation. The lang value must be a valid BCP 47 tag. Common codes: en, es, fr, de, zh, ja, pt, ar, ru.", messages: { "Empty lang attribute value.": "Empty lang attribute value.", 'Invalid lang attribute value "{0}".': 'Invalid lang attribute value "{0}".' } },
|
|
4564
|
+
"html-xml-lang-mismatch": { description: "The lang and xml:lang attributes on <html> must match.", guidance: "In XHTML documents, if both lang and xml:lang are present, they must specify the same base language. Mismatched values confuse assistive technologies. Either remove xml:lang (preferred for HTML5) or ensure both attributes have identical values.", messages: { 'lang="{0}" and xml:lang="{1}" do not match.': 'lang="{0}" and xml:lang="{1}" do not match.' } },
|
|
4565
|
+
"td-headers-attr": { description: "All cells in a table using headers attribute must reference valid header IDs.", guidance: "The headers attribute on table cells must reference IDs of header cells (th or td) within the same table. This creates explicit associations for screen readers. Verify all referenced IDs exist and spell them correctly. For simple tables, consider using scope on th elements instead.", messages: { 'Headers attribute references the cell itself ("{0}").': 'Headers attribute references the cell itself ("{0}").', 'Headers attribute references non-existent ID "{0}".': 'Headers attribute references non-existent ID "{0}".' } },
|
|
4566
|
+
"th-has-data-cells": { description: "Table headers should be associated with data cells.", guidance: "A table with header cells (th) but no data cells (td) is likely a misuse of table markup for layout or has missing content. Either add data cells that the headers describe, or use appropriate non-table markup if this is not tabular data.", messages: { "Table has header cells but no data cells.": "Table has header cells but no data cells." } },
|
|
4567
|
+
"td-has-header": { description: "Data cells in tables larger than 3x3 should have associated headers.", guidance: "In complex tables, screen reader users need header associations to understand data cells. Use th elements with scope attribute, or the headers attribute on td elements. For simple tables (≤3x3), this is less critical as context is usually clear.", messages: { "Data cell has no associated header. Add th elements with scope, or headers attribute.": "Data cell has no associated header. Add th elements with scope, or headers attribute." } },
|
|
4568
|
+
"scope-attr-valid": { description: "The scope attribute on table headers must have a valid value.", guidance: "The scope attribute tells screen readers which cells a header applies to. Valid values are: row, col, rowgroup, colgroup. Using invalid values breaks the association between headers and cells.", messages: { 'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.': 'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.' } },
|
|
4569
|
+
"empty-table-header": { description: "Table header cells should have visible text.", guidance: "Empty table headers provide no information to screen reader users. Either add descriptive text to the header, or if the header is intentionally empty (like a corner cell), consider using a td element instead or adding a visually hidden label.", messages: { "Table header cell is empty. Add text or use aria-label.": "Table header cell is empty. Add text or use aria-label." } },
|
|
4570
|
+
"duplicate-id-aria": { description: "IDs used in ARIA and label associations must be unique to avoid broken references.", guidance: "When aria-labelledby, aria-describedby, aria-controls, or label[for] reference a duplicate ID, only the first matching element is used. This breaks the intended relationship and may leave controls unnamed or descriptions missing. Ensure IDs referenced by ARIA attributes and label associations are unique throughout the document.", messages: { 'Duplicate ID "{0}" referenced by {1}.': 'Duplicate ID "{0}" referenced by {1}.' } },
|
|
4571
|
+
"video-caption": { description: "Video elements must have captions via <track kind='captions'>.", guidance: "Captions provide text alternatives for audio content in videos, benefiting deaf users and those who cannot hear audio. Add a <track> element with kind='captions' pointing to a WebVTT caption file. Captions should include both dialogue and important sound effects.", messages: { "Video element has no captions track.": "Video element has no captions track." } },
|
|
4572
|
+
"audio-caption": { description: "Audio elements should have a text alternative or transcript.", guidance: "Audio-only content like podcasts or recordings needs a text alternative for deaf users. Provide a transcript either on the same page or linked nearby. The transcript should include all spoken content and descriptions of relevant sounds.", messages: { "Audio element has no transcript or text alternative. Add a transcript or track element.": "Audio element has no transcript or text alternative. Add a transcript or track element." } },
|
|
4573
|
+
"color-contrast": { description: "Text elements must have sufficient color contrast against the background.", guidance: "WCAG SC 1.4.3 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (>=24px or >=18.66px bold). Increase the contrast by darkening the text or lightening the background, or vice versa.", messages: { "Insufficient color contrast ratio of {0}:1 (required {1}:1).": "Insufficient color contrast ratio of {0}:1 (required {1}:1)." } }
|
|
4574
|
+
}, Kn = {
|
|
4575
|
+
"document-title": { description: "Los documentos deben tener un elemento <title> para proporcionar a los usuarios una vista general del contenido.", guidance: "Los usuarios de lectores de pantalla dependen de los títulos de página para identificar y navegar entre pestañas/ventanas. Agregue un elemento <title> descriptivo en <head> que resuma el propósito de la página. Mantenga los títulos únicos en todo el sitio, colocando el contenido específico antes del nombre del sitio (por ejemplo, 'Contáctenos - Acme Corp').", messages: { "Document <title> element is empty.": "El elemento <title> del documento está vacío.", "Document is missing a <title> element.": "Al documento le falta un elemento <title>." } },
|
|
4576
|
+
bypass: { description: "La página debe tener un mecanismo para omitir bloques de contenido repetidos.", guidance: 'Los usuarios de teclado deben poder omitir contenido repetitivo como la navegación. Proporcione un enlace de salto en la parte superior de la página que enlace al contenido principal (por ejemplo, <a href="#main">Saltar al contenido principal</a>), o use un landmark <main>. Los lectores de pantalla pueden saltar directamente a los landmarks, por lo que un elemento <main> correctamente marcado satisface este requisito.', messages: { "Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.": "La página no tiene un mecanismo para omitir contenido repetido. Agregue un landmark <main> o un enlace de salto." } },
|
|
4577
|
+
"page-has-heading-one": { description: "La página debe contener un encabezado de nivel uno.", guidance: "Un encabezado de nivel uno (<h1> o role='heading' con aria-level='1') ayuda a los usuarios a comprender el tema de la página y proporciona un punto de referencia para la navegación con lector de pantalla. Cada página debe tener exactamente un h1 que describa el contenido principal, típicamente coincidiendo o similar al título de la página.", messages: { "Page does not contain a level-one heading.": "La página no contiene un encabezado de nivel uno." } },
|
|
4578
|
+
"frame-title": { description: "Los marcos deben tener un nombre accesible.", guidance: "Los lectores de pantalla anuncian los títulos de los marcos cuando los usuarios navegan por ellos. Agregue un atributo title a los elementos <iframe> y <frame> que describa el propósito del marco (por ejemplo, <iframe title='Reproductor de video'>). Evite títulos genéricos como 'marco' o 'iframe'. Si el marco es decorativo, use aria-hidden='true'.", messages: { "Frame is missing an accessible name. Add a title attribute.": "Al marco le falta un nombre accesible. Agregue un atributo title." } },
|
|
4579
|
+
"frame-title-unique": { description: "Los títulos de los marcos deben ser únicos.", guidance: "Cuando varios marcos tienen títulos idénticos, los usuarios de lectores de pantalla no pueden distinguirlos. Dé a cada marco un título único y descriptivo que explique su propósito específico o contenido.", messages: { "Frame title is not unique. Use a distinct title for each frame.": "El título del marco no es único. Use un título distinto para cada marco." } },
|
|
4580
|
+
"meta-viewport": { description: "La etiqueta meta viewport no debe deshabilitar el zoom del usuario.", guidance: "Los usuarios con baja visión necesitan ampliar el contenido al 200% o más. Establecer user-scalable=no o maximum-scale=1 impide el zoom y no cumple con WCAG. Elimine estas restricciones. Si su diseño se rompe con zoom alto, corrija el diseño responsivo en lugar de impedir el zoom.", messages: { "Viewport disables user scaling (user-scalable={0}). Remove this restriction.": "El viewport deshabilita el escalado del usuario (user-scalable={0}). Elimine esta restricción.", "Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove.": "El viewport maximum-scale={0} restringe el zoom. Establezca al menos 2 o elimínelo." } },
|
|
4581
|
+
"meta-refresh": { description: "La etiqueta meta refresh no debe redirigir o actualizar automáticamente.", guidance: "Las actualizaciones o redirecciones automáticas de página pueden desorientar a los usuarios, especialmente aquellos que usan lectores de pantalla o con discapacidades cognitivas. Pueden perder su lugar o no tener tiempo para leer el contenido. Si se necesita una redirección, use una redirección del lado del servidor (HTTP 301/302). Para actualizaciones temporizadas, proporcione controles al usuario.", messages: { "Page redirects after {0} seconds without warning. Use server-side redirect.": "La página redirige después de {0} segundos sin advertencia. Use redirección del lado del servidor.", "Page auto-refreshes after {0} seconds. Provide user control over refresh.": "La página se actualiza automáticamente después de {0} segundos. Proporcione control al usuario sobre la actualización." } },
|
|
4582
|
+
blink: { description: "El elemento <blink> no debe usarse.", guidance: "El contenido parpadeante puede causar convulsiones en usuarios con epilepsia fotosensible y es una distracción para usuarios con trastornos de atención. El elemento <blink> está obsoleto y nunca debe usarse. Si necesita llamar la atención sobre el contenido, use métodos menos intrusivos como color, bordes o iconos.", messages: { "The <blink> element causes accessibility issues. Remove it entirely.": "El elemento <blink> causa problemas de accesibilidad. Elimínelo por completo." } },
|
|
4583
|
+
marquee: { description: "El elemento <marquee> no debe usarse.", guidance: "El contenido que se desplaza o se mueve es difícil de leer para muchos usuarios, especialmente aquellos con discapacidades cognitivas o visuales. El elemento <marquee> está obsoleto. Reemplace el texto en movimiento con contenido estático. Si el contenido debe desplazarse, proporcione controles de pausa/detención y asegúrese de que se detenga después de 5 segundos.", messages: { "The <marquee> element causes accessibility issues. Replace with static content.": "El elemento <marquee> causa problemas de accesibilidad. Reemplace con contenido estático." } },
|
|
4584
|
+
"img-alt": { description: `Las imágenes deben tener texto alternativo. Agregue un atributo alt a los elementos <img>. Las imágenes decorativas pueden usar un atributo alt vacío (alt=""), role='none' o role='presentation'.`, guidance: "Cada imagen necesita un atributo alt. Para imágenes informativas, describa el contenido o la función de manera concisa. Para imágenes decorativas (fondos, espaciadores, adornos puramente visuales), use alt='' para ocultarlas de los lectores de pantalla. Nunca omita alt por completo: los lectores de pantalla pueden leer el nombre del archivo en su lugar.", messages: { 'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.': 'La imagen tiene texto alt solo con espacios en blanco. Use alt="" para imágenes decorativas o proporcione texto descriptivo.', "Image element missing alt attribute.": "Al elemento de imagen le falta el atributo alt.", 'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.': 'El elemento con role="img" no tiene nombre accesible. Agregue aria-label o aria-labelledby.' } },
|
|
4585
|
+
"svg-img-alt": { description: "Los elementos SVG con rol img, graphics-document o graphics-symbol deben tener un nombre accesible mediante un elemento <title>, aria-label o aria-labelledby.", guidance: "Los SVG en línea con role='img' necesitan nombres accesibles. Agregue un elemento <title> como primer hijo del SVG (los lectores de pantalla lo anunciarán), o use aria-label en el elemento SVG. Para SVGs complejos, use aria-labelledby haciendo referencia tanto a un elemento <title> como a un elemento <desc>. Los SVGs decorativos deben usar aria-hidden='true' en su lugar.", messages: { "{0} with role='{1}' has no accessible name.": "{0} con role='{1}' no tiene nombre accesible." } },
|
|
4586
|
+
"input-image-alt": { description: 'Las entradas de imagen (<input type="image">) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.', guidance: "Los botones de imagen (<input type='image'>) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.", messages: { "Image input missing alt text.": "A la entrada de imagen le falta texto alt." } },
|
|
4587
|
+
"image-redundant-alt": { description: "El texto alternativo de la imagen no debe duplicar el texto del enlace o botón adyacente. Cuando el texto alt repite el texto circundante, los usuarios de lectores de pantalla escuchan la misma información dos veces.", guidance: "Cuando una imagen está dentro de un enlace o botón que también tiene texto, haga que el texto alt sea complementario en lugar de idéntico. Si la imagen es puramente decorativa en ese contexto, use alt='' para evitar la repetición.", messages: { 'Alt text "{0}" duplicates surrounding {1} text.': 'El texto alt "{0}" duplica el texto del {1} circundante.' } },
|
|
4588
|
+
"image-alt-redundant-words": { description: "El texto alternativo de la imagen no debe contener palabras como 'imagen', 'foto' o 'fotografía': los lectores de pantalla ya anuncian el tipo de elemento.", guidance: "Los lectores de pantalla ya anuncian 'imagen' o 'gráfico' antes de leer el texto alt, por lo que frases como 'imagen de', 'foto de' o 'fotografía de' son redundantes. Elimine estas palabras y describa lo que muestra la imagen. Por ejemplo, cambie 'imagen de un perro' a 'golden retriever jugando a buscar'.", messages: { 'Alt text "{0}" contains redundant word(s): {1}.': 'El texto alt "{0}" contiene palabra(s) redundante(s): {1}.' } },
|
|
4589
|
+
"area-alt": { description: "Los elementos <area> del mapa de imagen deben tener texto alternativo.", guidance: "Cada región clicable en un mapa de imagen necesita texto alternativo para que los usuarios de lectores de pantalla sepan qué representa la región. Agregue un atributo alt a cada elemento <area> describiendo su propósito. Para mapas de imagen complejos, considere usar enfoques alternativos como SVG con enlaces incrustados, o una lista de enlaces de texto.", messages: { "Image map <area> element is missing alternative text.": "Al elemento <area> del mapa de imagen le falta texto alternativo." } },
|
|
4590
|
+
"object-alt": { description: "Los elementos <object> deben tener texto alternativo.", guidance: "Los elementos object incrustan contenido externo que puede no ser accesible para todos los usuarios. Proporcione texto alternativo mediante aria-label, aria-labelledby o un atributo title. El contenido de respaldo dentro de <object> solo se muestra cuando el objeto no se carga y no sirve como nombre accesible.", messages: { "<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.": "Al elemento <object> le falta texto alternativo. Agregue aria-label, aria-labelledby o un atributo title." } },
|
|
4591
|
+
"role-img-alt": { description: "Los elementos con role='img' deben tener un nombre accesible.", guidance: "Cuando asigna role='img' a un elemento (como un div que contiene fuentes de iconos o fondos CSS), debe proporcionar un nombre accesible mediante aria-label o aria-labelledby. Sin esto, los usuarios de lectores de pantalla no tienen forma de entender lo que representa la imagen. Si la imagen es decorativa, use role='presentation' o role='none' en su lugar.", messages: { "Element with role='img' has no accessible name. Add aria-label or aria-labelledby.": "El elemento con role='img' no tiene nombre accesible. Agregue aria-label o aria-labelledby." } },
|
|
4592
|
+
"server-side-image-map": { description: "No se deben usar mapas de imagen del lado del servidor.", guidance: "Los mapas de imagen del lado del servidor (usando el atributo ismap) envían coordenadas de clic al servidor, lo cual es inaccesible para usuarios de teclado y lectores de pantalla que no pueden hacer clic con precisión en regiones específicas. Reemplace con mapas de imagen del lado del cliente (elementos <map> con <area>) que proporcionan acceso por teclado y nombres accesibles, o use imágenes/botones enlazados.", messages: { "Server-side image map detected. Use client-side image map with <map> and <area> elements instead.": "Se detectó un mapa de imagen del lado del servidor. Use un mapa de imagen del lado del cliente con elementos <map> y <area> en su lugar." } },
|
|
4593
|
+
label: { description: "Los elementos de formulario deben tener etiquetas. Use <label>, aria-label o aria-labelledby.", guidance: "Cada entrada de formulario necesita una etiqueta accesible para que los usuarios comprendan qué información ingresar. Use un elemento <label> con un atributo for que coincida con el id de la entrada, envuelva la entrada en un <label>, o use aria-label/aria-labelledby para componentes personalizados. Los placeholders no son suficientes como etiquetas ya que desaparecen al escribir.", messages: { "Form element has no accessible label.": "El elemento de formulario no tiene etiqueta accesible." } },
|
|
4594
|
+
"form-field-multiple-labels": { description: "Los campos de formulario no deben tener múltiples elementos label.", guidance: "Cuando un campo de formulario tiene múltiples elementos <label> apuntando a él, las tecnologías de asistencia pueden anunciar solo una etiqueta o comportarse de manera inconsistente. Use un solo <label> y combine cualquier texto adicional en él, o use aria-describedby para información complementaria.", messages: { "Form field has {0} labels. Use a single label element.": "El campo de formulario tiene {0} etiquetas. Use un solo elemento label." } },
|
|
4595
|
+
"select-name": { description: "Los elementos select deben tener una etiqueta asociada programáticamente mediante <label>, aria-label o aria-labelledby.", guidance: "Los menús desplegables select necesitan etiquetas para que los usuarios comprendan qué elección están haciendo. Use un elemento <label> con un atributo for que coincida con el id del select, o envuelva el select en un <label>. Para selects sin etiquetas visibles, use aria-label. La primera <option> no es un sustituto de una etiqueta adecuada.", messages: { "Select element has no accessible name.": "El elemento select no tiene nombre accesible." } },
|
|
4596
|
+
"input-button-name": { description: "Los botones de entrada deben tener texto discernible mediante value, aria-label o aria-labelledby.", guidance: "Los botones de entrada (<input type='submit'>, type='button', type='reset'>) necesitan nombres accesibles para que los usuarios sepan qué acción realiza el botón. Agregue un atributo value con texto descriptivo (por ejemplo, value='Enviar formulario'), o use aria-label si el valor debe diferir del nombre accesible.", messages: { "Input button has no discernible text.": "El botón de entrada no tiene texto discernible." } },
|
|
4597
|
+
"autocomplete-valid": { description: "El atributo autocomplete debe usar valores válidos de la especificación HTML.", guidance: "El atributo autocomplete ayuda a los usuarios a completar formularios identificando los propósitos de las entradas. Use valores estándar como 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. Esto beneficia a usuarios con discapacidades cognitivas, impedimentos motores y cualquier persona que use administradores de contraseñas o autocompletado. Consulte la especificación HTML para la lista completa de tokens válidos.", messages: { 'Invalid autocomplete value "{0}".': 'Valor de autocomplete inválido "{0}".' } },
|
|
4598
|
+
"label-content-name-mismatch": { description: "Los elementos interactivos con texto visible deben tener nombres accesibles que contengan ese texto.", guidance: "Para los usuarios de control por voz que activan controles hablando su etiqueta visible, el nombre accesible debe incluir el texto visible. Si aria-label es 'Enviar formulario' pero el botón muestra 'Enviar', los usuarios de voz que digan 'clic Enviar' no lo activarán. Asegúrese de que aria-label/aria-labelledby contenga o coincida con el texto visible.", messages: { 'Accessible name "{0}" does not contain visible text "{1}".': 'El nombre accesible "{0}" no contiene el texto visible "{1}".' } },
|
|
4599
|
+
"label-title-only": { description: "Los elementos de formulario no deben usar el atributo title como único nombre accesible.", guidance: "El atributo title no es confiable como etiqueta porque solo aparece al pasar el cursor/enfocar (no visible para usuarios táctiles) y a menudo es ignorado por las tecnologías de asistencia. Use un elemento <label> visible, aria-label o aria-labelledby en su lugar. El title puede complementar una etiqueta pero no debe reemplazarla.", messages: { "Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead.": "El elemento de formulario usa el atributo title como única etiqueta. Use <label>, aria-label o aria-labelledby en su lugar." } },
|
|
4600
|
+
tabindex: { description: "Los elementos no deben tener tabindex mayor que 0, lo cual altera el orden natural de tabulación.", guidance: "Los valores positivos de tabindex fuerzan a los elementos al frente del orden de tabulación independientemente de la posición en el DOM, creando una navegación impredecible para los usuarios de teclado. Use tabindex='0' para agregar elementos al orden natural de tabulación, o tabindex='-1' para hacer elementos enfocables programáticamente pero no en el orden de tabulación. Confíe en el orden del DOM para la secuencia de tabulación.", messages: { 'Element has tabindex="{0}" which disrupts tab order.': 'El elemento tiene tabindex="{0}" lo cual altera el orden de tabulación.' } },
|
|
4601
|
+
"focus-order-semantics": { description: "Los elementos que reciben el foco del teclado deben tener un rol apropiado para que las tecnologías de asistencia puedan transmitir su propósito. Los elementos no interactivos con tabindex='0' necesitan un rol ARIA interactivo válido.", guidance: "Al agregar tabindex='0' a elementos no interactivos como <div> o <span>, los lectores de pantalla los anuncian genéricamente. Agregue un rol apropiado (button, link, tab, etc.) para que los usuarios comprendan el propósito del elemento. También agregue manejadores de eventos de teclado (Enter/Espacio para botones, Enter para enlaces). Considere usar elementos interactivos nativos en su lugar.", messages: { 'Non-interactive <{0}> with tabindex="0" has no interactive role.': 'El elemento no interactivo <{0}> con tabindex="0" no tiene un rol interactivo.' } },
|
|
4602
|
+
"nested-interactive": { description: "Los controles interactivos no deben estar anidados dentro de otros.", guidance: "Anidar elementos interactivos (como un botón dentro de un enlace, o un enlace dentro de un botón) crea un comportamiento impredecible y confunde a las tecnologías de asistencia. El navegador puede eliminar el elemento interno del árbol de accesibilidad. Reestructure el HTML para que los elementos interactivos sean hermanos, no anidados. Si necesita una tarjeta clicable, use CSS y JavaScript en lugar de anidar.", messages: { "Interactive element <{0}> is nested inside <{1}>.": "El elemento interactivo <{0}> está anidado dentro de <{1}>." } },
|
|
4603
|
+
"scrollable-region-focusable": { description: "Las regiones desplazables deben ser accesibles por teclado.", guidance: "El contenido que se desplaza debe ser accesible para los usuarios de teclado. Si una región tiene overflow:scroll u overflow:auto y contiene contenido desplazable, necesita tabindex='0' para ser enfocable, o debe contener elementos enfocables. Sin esto, los usuarios de teclado no pueden desplazar el contenido.", messages: { "Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements.": "La región desplazable no es accesible por teclado. Agregue tabindex='0' o incluya elementos enfocables." } },
|
|
4604
|
+
accesskeys: { description: "Los valores del atributo accesskey deben ser únicos.", guidance: "Cuando múltiples elementos comparten la misma accesskey, el comportamiento del navegador se vuelve impredecible; generalmente solo se activa el primer elemento. Asegúrese de que cada valor de accesskey sea único dentro de la página. También considere que las accesskeys pueden entrar en conflicto con los atajos del navegador y del lector de pantalla, así que úselas con moderación.", messages: { 'Duplicate accesskey "{0}". Each accesskey must be unique.': 'Accesskey duplicada "{0}". Cada accesskey debe ser única.' } },
|
|
4605
|
+
"heading-order": { description: "Los niveles de encabezado deben incrementarse de uno en uno; saltarse niveles (por ejemplo, h2 a h4) dificulta la navegación.", guidance: "Los usuarios de lectores de pantalla navegan por encabezados para comprender la estructura de la página. Saltarse niveles (h2 a h4) sugiere contenido faltante y crea confusión. Comience con h1 para el título de la página, luego use h2 para secciones principales, h3 para subsecciones, etc. Puede volver a subir (h3 a h2) al comenzar una nueva sección.", messages: { "Heading level {0} skipped from level {1}.": "Nivel de encabezado {0} saltado desde el nivel {1}." } },
|
|
4606
|
+
"empty-heading": { description: "Los encabezados deben tener texto discernible.", guidance: "Los usuarios de lectores de pantalla navegan las páginas por encabezados, por lo que los encabezados vacíos crean puntos de navegación confusos. Asegúrese de que todos los encabezados contengan texto visible o nombres accesibles. Si un encabezado se usa puramente para estilo visual, use CSS en lugar de elementos de encabezado.", messages: { "Heading is empty. Add text content or remove the heading element.": "El encabezado está vacío. Agregue contenido de texto o elimine el elemento de encabezado." } },
|
|
4607
|
+
"p-as-heading": { description: "Los párrafos no deben estilizarse para parecer encabezados.", guidance: "Cuando los párrafos se estilizan con negrita y fuentes grandes para parecer encabezados, los usuarios de lectores de pantalla pierden la estructura semántica. Use elementos de encabezado apropiados (h1-h6) en lugar de párrafos estilizados. Si necesita un estilo específico, aplique CSS a los elementos de encabezado manteniendo la jerarquía adecuada de encabezados.", messages: { "Paragraph appears to be styled as a heading. Use an h1-h6 element instead.": "El párrafo parece estar estilizado como un encabezado. Use un elemento h1-h6 en su lugar." } },
|
|
4608
|
+
"landmark-one-main": { description: "La página debe tener exactamente un landmark main.", guidance: "El landmark main contiene el contenido principal de la página. Los lectores de pantalla permiten a los usuarios saltar directamente al contenido principal. Use un solo elemento <main> (o role='main') para envolver el contenido central, excluyendo encabezados, pies de página y navegación.", messages: { "Page has no main landmark.": "La página no tiene un landmark main.", "Page has multiple main landmarks.": "La página tiene múltiples landmarks main." } },
|
|
4609
|
+
"landmark-no-duplicate-banner": { description: "La página no debe tener más de un landmark banner.", guidance: "El landmark banner (típicamente <header>) identifica contenido orientado al sitio como logotipos y búsqueda. Solo se permite un banner de nivel superior por página. Si necesita múltiples encabezados, anídelos dentro de elementos de sección (article, section, aside) donde se convierten en encabezados de alcance en lugar de banners de nivel de página.", messages: { "Page has multiple banner landmarks.": "La página tiene múltiples landmarks banner." } },
|
|
4610
|
+
"landmark-no-duplicate-contentinfo": { description: "La página no debe tener más de un landmark contentinfo.", guidance: "El landmark contentinfo (típicamente <footer>) contiene información sobre la página como derechos de autor e información de contacto. Solo se permite un contentinfo de nivel superior por página. Anide pies de página adicionales dentro de elementos de sección para delimitar su alcance.", messages: { "Page has multiple contentinfo landmarks.": "La página tiene múltiples landmarks contentinfo." } },
|
|
4611
|
+
"landmark-no-duplicate-main": { description: "La página no debe tener más de un landmark main.", guidance: "Solo debe existir un landmark main por página. El landmark main identifica el área de contenido principal. Si tiene múltiples secciones de contenido, use <section> con encabezados apropiados en lugar de múltiples elementos main.", messages: { "Page has multiple main landmarks.": "La página tiene múltiples landmarks main." } },
|
|
4612
|
+
"landmark-banner-is-top-level": { description: "El landmark banner no debe estar anidado dentro de otro landmark.", guidance: "El landmark banner debe ser un landmark de nivel superior, no anidado dentro de article, aside, main, nav o section. Si un header está dentro de estos elementos, automáticamente se convierte en un encabezado genérico en lugar de un banner. Elimine el role='banner' explícito de los headers anidados o reestructure la página.", messages: { "Banner landmark is nested within another landmark.": "El landmark banner está anidado dentro de otro landmark." } },
|
|
4613
|
+
"landmark-contentinfo-is-top-level": { description: "El landmark contentinfo no debe estar anidado dentro de otro landmark.", guidance: "El landmark contentinfo debe ser un landmark de nivel superior. Un footer dentro de article, aside, main, nav o section se convierte en un pie de página de alcance, no en un landmark contentinfo. Elimine el role='contentinfo' explícito de los footers anidados o mueva el footer fuera de los elementos de sección.", messages: { "Contentinfo landmark is nested within another landmark.": "El landmark contentinfo está anidado dentro de otro landmark." } },
|
|
4614
|
+
"landmark-main-is-top-level": { description: "El landmark main no debe estar anidado dentro de otro landmark.", guidance: "El landmark main debe ser un landmark de nivel superior ya que representa el contenido principal de la página. No anide <main> o role='main' dentro de elementos article, aside, nav o section.", messages: { "Main landmark is nested within another landmark.": "El landmark main está anidado dentro de otro landmark." } },
|
|
4615
|
+
"landmark-complementary-is-top-level": { description: "El landmark aside (complementary) debe ser de nivel superior o estar directamente dentro de main.", guidance: "El landmark complementary (aside) debe ser de nivel superior o un hijo directo de main. Anidar aside profundamente dentro de otros landmarks reduce su descubrimiento para los usuarios de lectores de pantalla que navegan por landmarks.", messages: { "Complementary landmark should be top-level.": "El landmark complementary debe ser de nivel superior." } },
|
|
4616
|
+
"landmark-unique": { description: "Los landmarks deben tener etiquetas únicas cuando hay múltiples del mismo tipo.", guidance: "Cuando una página tiene múltiples landmarks del mismo tipo (por ejemplo, múltiples elementos nav), cada uno debe tener un nombre accesible único mediante aria-label o aria-labelledby. Esto ayuda a los usuarios de lectores de pantalla a distinguirlos (por ejemplo, 'Navegación principal' vs 'Navegación del pie de página').", messages: { 'Multiple {0} landmarks have the same label "{1}".': 'Múltiples landmarks {0} tienen la misma etiqueta "{1}".', "Multiple {0} landmarks have no label. Add unique aria-label attributes.": "Múltiples landmarks {0} no tienen etiqueta. Agregue atributos aria-label únicos." } },
|
|
4617
|
+
region: { description: "Todo el contenido de la página debe estar contenido dentro de landmarks.", guidance: "Los usuarios de lectores de pantalla navegan las páginas por landmarks. El contenido fuera de los landmarks es más difícil de encontrar y comprender. Envuelva todo el contenido visible en landmarks apropiados: <header>, <nav>, <main>, <aside>, <footer>, o <section> con una etiqueta. Los enlaces de salto pueden existir fuera de los landmarks.", messages: { "Content is not contained within a landmark region.": "El contenido no está contenido dentro de una región landmark." } },
|
|
4618
|
+
list: { description: "<ul> y <ol> solo deben contener <li>, <script> o <template> como hijos directos.", guidance: "Los lectores de pantalla anuncian la estructura de la lista ('lista con 5 elementos') basándose en el marcado correcto. Colocar elementos que no son <li> directamente dentro de <ul> u <ol> rompe esta estructura. Envuelva el contenido en elementos <li>, o si necesita divs envolventes para el estilo, reestructure su CSS para estilizar los elementos <li> directamente.", messages: { "List contains non-<li> child <{0}>.": "La lista contiene un hijo no <li>: <{0}>." } },
|
|
4619
|
+
listitem: { description: "Los elementos <li> deben estar contenidos en un <ul>, <ol> o <menu>.", guidance: "Los elementos de lista (<li>) solo tienen significado semántico dentro de un contenedor de lista (<ul>, <ol> o <menu>). Fuera de estos contenedores, las tecnologías de asistencia no pueden transmitir la relación de lista. Envuelva los elementos <li> en el contenedor de lista apropiado.", messages: { "<li> is not contained in a <ul>, <ol>, or <menu>.": "<li> no está contenido en un <ul>, <ol> o <menu>." } },
|
|
4620
|
+
dlitem: { description: "Los elementos <dt> y <dd> deben estar contenidos en un <dl>.", guidance: "Los términos de definición (<dt>) y las definiciones (<dd>) solo tienen significado semántico dentro de una lista de definiciones (<dl>). Fuera de <dl>, se tratan como texto genérico. Envuelva los pares relacionados de <dt> y <dd> en un elemento <dl> para transmitir la relación término/definición a las tecnologías de asistencia.", messages: { "<{0}> is not contained in a <dl>.": "<{0}> no está contenido en un <dl>." } },
|
|
4621
|
+
"definition-list": { description: "Los elementos <dl> solo deben contener <dt>, <dd>, <div>, <script> o <template>.", guidance: "Las listas de definiciones tienen requisitos estrictos de contenido. Solo <dt> (términos), <dd> (definiciones) y <div> (para agrupar pares dt/dd) son hijos válidos. Otros elementos rompen la estructura de la lista para los lectores de pantalla. Mueva los elementos inválidos fuera del <dl>, o reestructure usando el marcado adecuado de lista de definiciones.", messages: { "<dl> contains invalid child <{0}>.": "<dl> contiene un hijo inválido <{0}>." } },
|
|
4622
|
+
"aria-roles": { description: "Los valores de rol ARIA deben ser válidos.", guidance: "Los valores de rol inválidos son ignorados por las tecnologías de asistencia, lo que significa que el elemento no tendrá la semántica prevista. Verifique la ortografía y use solo roles definidos en la especificación WAI-ARIA. Los roles comunes incluyen: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.", messages: { 'Invalid ARIA role "{0}".': 'Rol ARIA inválido "{0}".' } },
|
|
4623
|
+
"aria-valid-attr": { description: "Los atributos ARIA deben ser válidos (correctamente escritos).", guidance: "Los atributos ARIA mal escritos son ignorados por las tecnologías de asistencia. Verifique la ortografía contra la especificación WAI-ARIA. Errores comunes: aria-labeledby (debe ser aria-labelledby), aria-role (debe ser role), aria-description (válido en ARIA 1.3+).", messages: { 'Invalid ARIA attribute "{0}".': 'Atributo ARIA inválido "{0}".' } },
|
|
4624
|
+
"aria-valid-attr-value": { description: "Los atributos ARIA deben tener valores válidos.", guidance: "Cada atributo ARIA acepta tipos de valores específicos. Los atributos booleanos (aria-hidden, aria-disabled) aceptan solo 'true' o 'false'. Los atributos triestado (aria-checked, aria-pressed) también aceptan 'mixed'. Los atributos de token (aria-live, aria-autocomplete) aceptan valores predefinidos. Los atributos de referencia de ID (aria-labelledby, aria-describedby) deben referenciar IDs de elementos existentes.", messages: { '{0} must be "true" or "false", got "{1}".': '{0} debe ser "true" o "false", se obtuvo "{1}".', '{0} must be "true", "false", or "mixed", got "{1}".': '{0} debe ser "true", "false" o "mixed", se obtuvo "{1}".', '{0} must be an integer, got "{1}".': '{0} debe ser un entero, se obtuvo "{1}".', '{0} must be a number, got "{1}".': '{0} debe ser un número, se obtuvo "{1}".', 'Invalid value "{0}" for {1}.': 'Valor inválido "{0}" para {1}.' } },
|
|
4625
|
+
"aria-required-attr": { description: "Los elementos con roles ARIA deben tener todos los atributos ARIA requeridos.", guidance: "Algunos roles ARIA requieren atributos específicos para funcionar correctamente. Por ejemplo, checkbox requiere aria-checked, slider requiere aria-valuenow, heading requiere aria-level. Sin estos atributos, las tecnologías de asistencia no pueden transmitir el estado o valor del elemento a los usuarios. Agregue el atributo requerido faltante con un valor apropiado.", messages: { 'Role "{0}" requires attribute "{1}".': 'El rol "{0}" requiere el atributo "{1}".' } },
|
|
4626
|
+
"aria-allowed-attr": { description: "Los atributos ARIA deben estar permitidos para el rol del elemento.", guidance: "Cada rol ARIA admite atributos específicos. Usar atributos no admitidos crea confusión para las tecnologías de asistencia. Consulte la especificación ARIA para saber qué atributos son válidos para cada rol, o elimine el atributo si no es necesario.", messages: { 'ARIA attribute "{0}" is not allowed on role "{1}".': 'El atributo ARIA "{0}" no está permitido en el rol "{1}".' } },
|
|
4627
|
+
"aria-allowed-role": { description: "El rol ARIA debe ser apropiado para el elemento.", guidance: "No todos los roles ARIA se pueden aplicar a todos los elementos HTML. Muchos elementos tienen roles implícitos (por ejemplo, <header> es implícitamente banner, <nav> es navigation, <main> es main). Agregar un rol explícito que coincida con el rol implícito es redundante. Agregar un rol conflictivo rompe la semántica. Elimine el atributo role o use un elemento diferente.", messages: { "Element <{0}> should not have an explicit role.": "El elemento <{0}> no debería tener un rol explícito.", 'Role "{0}" is not allowed on element <{1}>.': 'El rol "{0}" no está permitido en el elemento <{1}>.' } },
|
|
4628
|
+
"aria-required-children": { description: "Ciertos roles ARIA requieren que estén presentes roles hijos específicos.", guidance: "Algunos roles ARIA representan contenedores que deben contener roles hijos específicos para una semántica adecuada. Por ejemplo, una lista debe contener listitems, un menú debe contener menuitems. Agregue los elementos hijos requeridos con roles apropiados, o use elementos HTML nativos que proporcionen esta semántica implícitamente (por ejemplo, <ul> con <li>).", messages: { 'Role "{0}" requires children with role: {1}.': 'El rol "{0}" requiere hijos con rol: {1}.' } },
|
|
4629
|
+
"aria-required-parent": { description: "Ciertos roles ARIA deben estar contenidos dentro de roles padre específicos.", guidance: "Algunos roles ARIA representan elementos que deben existir dentro de roles de contenedor específicos. Por ejemplo, un listitem debe estar dentro de una lista, un tab debe estar dentro de un tablist. Envuelva el elemento en el padre apropiado, o use elementos HTML nativos que proporcionen esta estructura (por ejemplo, <li> dentro de <ul>).", messages: { 'Role "{0}" must be contained within: {1}.': 'El rol "{0}" debe estar contenido dentro de: {1}.' } },
|
|
4630
|
+
"aria-hidden-body": { description: "aria-hidden='true' no debe estar presente en el body del documento.", guidance: "Establecer aria-hidden='true' en el elemento body oculta todo el contenido de la página de las tecnologías de asistencia, haciendo la página completamente inaccesible para los usuarios de lectores de pantalla. Elimine aria-hidden del elemento body. Si necesita ocultar contenido temporalmente (por ejemplo, detrás de un modal), use aria-hidden en secciones específicas en su lugar.", messages: { "aria-hidden='true' on body hides all content from assistive technologies.": "aria-hidden='true' en el body oculta todo el contenido de las tecnologías de asistencia." } },
|
|
4631
|
+
"aria-hidden-focus": { description: "Los elementos con aria-hidden='true' no deben contener elementos enfocables.", guidance: "Cuando aria-hidden='true' oculta un elemento de las tecnologías de asistencia pero el elemento contiene hijos enfocables, los usuarios de teclado pueden enfocar esos hijos pero los usuarios de lectores de pantalla no sabrán que existen. Elimine los elementos enfocables de la región oculta, agregue tabindex='-1' a ellos, o elimine aria-hidden.", messages: { "Focusable element is inside an aria-hidden region.": "Un elemento enfocable está dentro de una región aria-hidden." } },
|
|
4632
|
+
"aria-command-name": { description: "Los comandos ARIA deben tener un nombre accesible.", guidance: "Los roles de comando ARIA interactivos (button, link, menuitem) deben tener nombres accesibles para que los usuarios sepan qué acción realizan. Agregue contenido de texto visible, aria-label o aria-labelledby para proporcionar un nombre.", messages: { "ARIA command has no accessible name.": "El comando ARIA no tiene nombre accesible." } },
|
|
4633
|
+
"aria-input-field-name": { description: "Los campos de entrada ARIA deben tener un nombre accesible.", guidance: "Los widgets de entrada ARIA (combobox, listbox, searchbox, slider, spinbutton, textbox) deben tener nombres accesibles para que los usuarios comprendan qué datos ingresar. Agregue una etiqueta visible con aria-labelledby, o use aria-label si una etiqueta visible no es posible.", messages: { "ARIA input field has no accessible name.": "El campo de entrada ARIA no tiene nombre accesible." } },
|
|
4634
|
+
"aria-toggle-field-name": { description: "Los campos de alternancia ARIA deben tener un nombre accesible.", guidance: "Los controles de alternancia ARIA (checkbox, switch, radio, menuitemcheckbox, menuitemradio) deben tener nombres accesibles para que los usuarios comprendan qué opción están seleccionando. Agregue contenido de texto visible, aria-label, o use aria-labelledby para referenciar una etiqueta visible.", messages: { "ARIA toggle field has no accessible name.": "El campo de alternancia ARIA no tiene nombre accesible." } },
|
|
4635
|
+
"aria-meter-name": { description: "Los elementos meter ARIA deben tener un nombre accesible.", guidance: "Los elementos meter muestran un valor dentro de un rango conocido (como uso de disco o fortaleza de contraseña). Deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué se está midiendo. Use aria-label o aria-labelledby para proporcionar contexto.", messages: { "Meter has no accessible name.": "El meter no tiene nombre accesible." } },
|
|
4636
|
+
"aria-progressbar-name": { description: "Los elementos progressbar ARIA deben tener un nombre accesible.", guidance: "Los indicadores de progreso deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué proceso se está rastreando. Use aria-label (por ejemplo, 'Progreso de carga de archivo') o aria-labelledby para referenciar un encabezado o etiqueta visible.", messages: { "Progressbar has no accessible name.": "La barra de progreso no tiene nombre accesible." } },
|
|
4637
|
+
"aria-dialog-name": { description: "Los diálogos ARIA deben tener un nombre accesible.", guidance: "Los elementos dialog y alertdialog deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan el propósito del diálogo cuando se abre. Use aria-label o aria-labelledby apuntando al encabezado del diálogo. Los elementos nativos <dialog> también deben tener un nombre accesible.", messages: { "Dialog has no accessible name.": "El diálogo no tiene nombre accesible." } },
|
|
4638
|
+
"aria-tooltip-name": { description: "Los tooltips ARIA deben tener un nombre accesible.", guidance: "Los elementos tooltip deben tener nombres accesibles (generalmente su contenido de texto). El contenido del tooltip típicamente sirve como el nombre accesible. Asegúrese de que el tooltip contenga contenido de texto descriptivo o tenga aria-label.", messages: { "Tooltip has no accessible name.": "El tooltip no tiene nombre accesible." } },
|
|
4639
|
+
"aria-treeitem-name": { description: "Los elementos treeitem ARIA deben tener un nombre accesible.", guidance: "Los elementos de árbol deben tener nombres accesibles para que los usuarios de lectores de pantalla puedan comprender la estructura del árbol y navegarlo eficazmente. Proporcione contenido de texto, aria-label o aria-labelledby para cada treeitem.", messages: { "Treeitem has no accessible name.": "El treeitem no tiene nombre accesible." } },
|
|
4640
|
+
"aria-prohibited-attr": { description: "Los atributos ARIA no deben estar prohibidos para el rol del elemento.", guidance: "Algunos roles ARIA prohíben ciertos atributos. Por ejemplo, roles como 'none', 'presentation', 'generic' y roles de nivel de texto (code, emphasis, strong) prohíben aria-label y aria-labelledby porque el nombramiento no está soportado para estos roles. Elimine los atributos prohibidos o cambie el rol.", messages: { "aria-label and aria-labelledby are prohibited on <{0}> elements.": "aria-label y aria-labelledby están prohibidos en elementos <{0}>.", 'aria-label and aria-labelledby are prohibited on role "{0}".': 'aria-label y aria-labelledby están prohibidos en el rol "{0}".', 'Attribute "{0}" is prohibited on role "{1}".': 'El atributo "{0}" está prohibido en el rol "{1}".' } },
|
|
4641
|
+
"presentation-role-conflict": { description: "Los elementos con role='presentation' o role='none' no deben ser enfocables ni tener atributos ARIA globales.", guidance: "Cuando un elemento tiene role='presentation' o role='none', está marcado como decorativo y se elimina del árbol de accesibilidad. Sin embargo, si el elemento es enfocable o tiene ciertos atributos ARIA, el rol de presentación se ignora y el elemento permanece accesible. Esto crea confusión. Elimine el rol de presentación, o elimine la enfocabilidad/atributos ARIA.", messages: { "Presentation role conflicts with: {0}. The role will be ignored.": "El rol de presentación entra en conflicto con: {0}. El rol será ignorado.", 'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.': 'El elemento con rol de presentación implícito (alt="") entra en conflicto con: {0}. El rol decorativo será ignorado.' } },
|
|
4642
|
+
"button-name": { description: "Los botones deben tener texto discernible.", guidance: "Los usuarios de lectores de pantalla necesitan saber qué hace un botón. Agregue contenido de texto visible, aria-label o aria-labelledby. Para botones de icono, use aria-label describiendo la acción (por ejemplo, aria-label='Cerrar'). Si el botón contiene una imagen, asegúrese de que la imagen tenga texto alt describiendo la acción del botón.", messages: { "Button has no discernible text.": "El botón no tiene texto discernible." } },
|
|
4643
|
+
"summary-name": { description: "Los elementos <summary> deben tener un nombre accesible.", guidance: "El elemento <summary> proporciona la etiqueta visible para un widget de divulgación <details>. Debe tener contenido de texto descriptivo para que los usuarios de lectores de pantalla comprendan qué se revelará al expandirse. Agregue texto claro y conciso que indique qué contenido está en la sección de detalles.", messages: { "<summary> element has no accessible name. Add descriptive text.": "El elemento <summary> no tiene nombre accesible. Agregue texto descriptivo." } },
|
|
4644
|
+
"link-name": { description: "Los enlaces deben tener texto discernible mediante contenido, aria-label o aria-labelledby.", guidance: "Los usuarios de lectores de pantalla necesitan saber a dónde lleva un enlace. Agregue contenido de texto descriptivo, aria-label, o use aria-labelledby. Para enlaces de imagen, asegúrese de que la imagen tenga texto alt describiendo el destino del enlace. Evite texto genérico como 'haga clic aquí' o 'leer más': el texto del enlace debe tener sentido fuera de contexto.", messages: { "Link has no discernible text.": "El enlace no tiene texto discernible." } },
|
|
4645
|
+
"skip-link": { description: "Los enlaces de salto deben apuntar a un destino válido en la página.", guidance: "Los enlaces de salto permiten a los usuarios de teclado omitir la navegación repetitiva y saltar directamente al contenido principal. El enlace de salto debe ser el primer elemento enfocable en la página, enlazar al contenido principal (por ejemplo, href='#main'), y hacerse visible cuando se enfoca. Puede estar visualmente oculto hasta enfocarse usando CSS.", messages: { 'Skip link points to "#{0}" which does not exist on the page.': 'El enlace de salto apunta a "#{0}" que no existe en la página.' } },
|
|
4646
|
+
"link-in-text-block": { description: "Los enlaces dentro de bloques de texto deben distinguirse por algo más que solo el color.", guidance: "Los usuarios que no pueden percibir diferencias de color necesitan otras señales visuales para identificar enlaces. Los enlaces en texto deben tener subrayados u otros indicadores no cromáticos. Si usa solo color, asegure un contraste de 3:1 con el texto circundante Y proporcione una indicación adicional al enfocar/pasar el cursor.", messages: { "Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.": "El enlace en el bloque de texto no es visualmente distinguible del texto circundante. Agregue un indicador visual no cromático como un subrayado o borde." } },
|
|
4647
|
+
"html-has-lang": { description: "El elemento <html> debe tener un atributo lang.", guidance: "Los lectores de pantalla usan el atributo lang para determinar qué reglas de idioma y pronunciación usar. Sin él, el contenido puede pronunciarse incorrectamente. Establezca lang en el idioma principal de la página (por ejemplo, lang='en' para inglés, lang='es' para español).", messages: { "<html> element missing lang attribute.": "Al elemento <html> le falta el atributo lang." } },
|
|
4648
|
+
"html-lang-valid": { description: "El atributo lang en <html> debe tener un valor válido.", guidance: "El atributo lang debe usar una etiqueta de idioma BCP 47 válida. Use un código de idioma de 2 o 3 letras (por ejemplo, 'en', 'fr', 'zh'), opcionalmente seguido de un código de región (por ejemplo, 'en-US', 'pt-BR'). Las etiquetas inválidas impiden que los lectores de pantalla pronuncien correctamente el contenido.", messages: { 'Invalid lang attribute value "{0}".': 'Valor de atributo lang inválido "{0}".' } },
|
|
4649
|
+
"valid-lang": { description: "El atributo lang debe tener un valor válido en todos los elementos.", guidance: "Cuando aparece contenido en un idioma diferente dentro de una página (por ejemplo, una cita en francés en un documento en inglés), envuélvalo con un atributo lang para asegurar la pronunciación correcta. El valor de lang debe ser una etiqueta BCP 47 válida. Códigos comunes: en, es, fr, de, zh, ja, pt, ar, ru.", messages: { "Empty lang attribute value.": "Valor de atributo lang vacío.", 'Invalid lang attribute value "{0}".': 'Valor de atributo lang inválido "{0}".' } },
|
|
4650
|
+
"html-xml-lang-mismatch": { description: "Los atributos lang y xml:lang en <html> deben coincidir.", guidance: "En documentos XHTML, si tanto lang como xml:lang están presentes, deben especificar el mismo idioma base. Los valores no coincidentes confunden a las tecnologías de asistencia. Elimine xml:lang (preferido para HTML5) o asegúrese de que ambos atributos tengan valores idénticos.", messages: { 'lang="{0}" and xml:lang="{1}" do not match.': 'lang="{0}" y xml:lang="{1}" no coinciden.' } },
|
|
4651
|
+
"td-headers-attr": { description: "Todas las celdas en una tabla que usan el atributo headers deben referenciar IDs de encabezado válidos.", guidance: "El atributo headers en las celdas de tabla debe referenciar IDs de celdas de encabezado (th o td) dentro de la misma tabla. Esto crea asociaciones explícitas para lectores de pantalla. Verifique que todos los IDs referenciados existan y estén escritos correctamente. Para tablas simples, considere usar scope en elementos th en su lugar.", messages: { 'Headers attribute references the cell itself ("{0}").': 'El atributo headers referencia a la propia celda ("{0}").', 'Headers attribute references non-existent ID "{0}".': 'El atributo headers referencia un ID inexistente "{0}".' } },
|
|
4652
|
+
"th-has-data-cells": { description: "Los encabezados de tabla deben estar asociados con celdas de datos.", guidance: "Una tabla con celdas de encabezado (th) pero sin celdas de datos (td) probablemente es un mal uso del marcado de tabla para diseño o tiene contenido faltante. Agregue celdas de datos que los encabezados describan, o use marcado apropiado que no sea de tabla si estos no son datos tabulares.", messages: { "Table has header cells but no data cells.": "La tabla tiene celdas de encabezado pero no celdas de datos." } },
|
|
4653
|
+
"td-has-header": { description: "Las celdas de datos en tablas mayores de 3x3 deben tener encabezados asociados.", guidance: "En tablas complejas, los usuarios de lectores de pantalla necesitan asociaciones de encabezados para comprender las celdas de datos. Use elementos th con atributo scope, o el atributo headers en elementos td. Para tablas simples (3x3 o menos), esto es menos crítico ya que el contexto generalmente es claro.", messages: { "Data cell has no associated header. Add th elements with scope, or headers attribute.": "La celda de datos no tiene encabezado asociado. Agregue elementos th con scope o el atributo headers." } },
|
|
4654
|
+
"scope-attr-valid": { description: "El atributo scope en los encabezados de tabla debe tener un valor válido.", guidance: "El atributo scope indica a los lectores de pantalla a qué celdas se aplica un encabezado. Los valores válidos son: row, col, rowgroup, colgroup. Usar valores inválidos rompe la asociación entre encabezados y celdas.", messages: { 'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.': 'Valor de scope inválido "{0}". Use row, col, rowgroup o colgroup.' } },
|
|
4655
|
+
"empty-table-header": { description: "Las celdas de encabezado de tabla deben tener texto visible.", guidance: "Los encabezados de tabla vacíos no proporcionan información a los usuarios de lectores de pantalla. Agregue texto descriptivo al encabezado, o si el encabezado está intencionalmente vacío (como una celda de esquina), considere usar un elemento td en su lugar o agregar una etiqueta visualmente oculta.", messages: { "Table header cell is empty. Add text or use aria-label.": "La celda de encabezado de tabla está vacía. Agregue texto o use aria-label." } },
|
|
4656
|
+
"duplicate-id-aria": { description: "Los IDs usados en asociaciones ARIA y label deben ser únicos para evitar referencias rotas.", guidance: "Cuando aria-labelledby, aria-describedby, aria-controls o label[for] referencian un ID duplicado, solo se usa el primer elemento coincidente. Esto rompe la relación prevista y puede dejar controles sin nombre o descripciones faltantes. Asegúrese de que los IDs referenciados por atributos ARIA y asociaciones de etiquetas sean únicos en todo el documento.", messages: { 'Duplicate ID "{0}" referenced by {1}.': 'ID duplicado "{0}" referenciado por {1}.' } },
|
|
4657
|
+
"video-caption": { description: "Los elementos de video deben tener subtítulos mediante <track kind='captions'>.", guidance: "Los subtítulos proporcionan alternativas de texto para el contenido de audio en videos, beneficiando a usuarios sordos y aquellos que no pueden escuchar el audio. Agregue un elemento <track> con kind='captions' apuntando a un archivo de subtítulos WebVTT. Los subtítulos deben incluir tanto el diálogo como los efectos de sonido importantes.", messages: { "Video element has no captions track.": "El elemento de video no tiene pista de subtítulos." } },
|
|
4658
|
+
"audio-caption": { description: "Los elementos de audio deben tener una alternativa de texto o transcripción.", guidance: "El contenido solo de audio como podcasts o grabaciones necesita una alternativa de texto para usuarios sordos. Proporcione una transcripción en la misma página o enlazada cerca. La transcripción debe incluir todo el contenido hablado y descripciones de sonidos relevantes.", messages: { "Audio element has no transcript or text alternative. Add a transcript or track element.": "El elemento de audio no tiene transcripción o alternativa de texto. Agregue una transcripción o elemento track." } },
|
|
4659
|
+
"color-contrast": { description: "Los elementos de texto deben tener suficiente contraste de color contra el fondo.", guidance: "WCAG SC 1.4.3 requiere una relación de contraste de al menos 4.5:1 para texto normal y 3:1 para texto grande (>=24px o >=18.66px en negrita). Aumente el contraste oscureciendo el texto o aclarando el fondo, o viceversa.", messages: { "Insufficient color contrast ratio of {0}:1 (required {1}:1).": "Relación de contraste de color insuficiente de {0}:1 (requerido {1}:1)." } }
|
|
4614
4660
|
};
|
|
4615
4661
|
export {
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4662
|
+
Ee as clearAllCaches,
|
|
4663
|
+
Be as clearAriaAttrAuditCache,
|
|
4664
|
+
Me as clearAriaHiddenCache,
|
|
4619
4665
|
Oe as clearColorCaches,
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4666
|
+
Le as clearComputedRoleCache,
|
|
4667
|
+
E as compileDeclarativeRule,
|
|
4668
|
+
Vn as configureRules,
|
|
4669
|
+
_n as createChunkedAudit,
|
|
4624
4670
|
v as getAccessibleName,
|
|
4625
4671
|
A as getAccessibleTextContent,
|
|
4626
|
-
|
|
4627
|
-
|
|
4672
|
+
Z as getActiveRules,
|
|
4673
|
+
N as getComputedRole,
|
|
4628
4674
|
u as getHtmlSnippet,
|
|
4629
|
-
|
|
4630
|
-
|
|
4675
|
+
ve as getImplicitRole,
|
|
4676
|
+
Yn as getRuleById,
|
|
4631
4677
|
m as getSelector,
|
|
4632
4678
|
b as isAriaHidden,
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4679
|
+
Ne as isValidRole,
|
|
4680
|
+
Xn as localeEn,
|
|
4681
|
+
Kn as localeEs,
|
|
4682
|
+
Un as querySelectorShadowAware,
|
|
4683
|
+
Bn as registerLocale,
|
|
4684
|
+
Se as rules,
|
|
4685
|
+
Gn as runAudit,
|
|
4686
|
+
Ae as translateViolations,
|
|
4687
|
+
On as validateDeclarativeRule
|
|
4641
4688
|
};
|