diff --git a/site/content/docs/05-accessibility-warnings.md b/site/content/docs/05-accessibility-warnings.md
index 9d0835b4432..438f2e47437 100644
--- a/site/content/docs/05-accessibility-warnings.md
+++ b/site/content/docs/05-accessibility-warnings.md
@@ -98,6 +98,18 @@ Enforce img alt attribute does not contain the word image, picture, or photo. Sc
---
+### `a11y-incorrect-aria-attribute-type`
+
+Enforce that only the correct type of value is used for aria attributes. For example, `aria-hidden`
+should only receive a boolean.
+
+```sv
+
+
+```
+
+---
+
### `a11y-invalid-attribute`
Enforce that attributes important for accessibility have a valid value. For example, `href` should not be empty, `'#'`, or `javascript:`.
diff --git a/src/compiler/compile/compiler_warnings.ts b/src/compiler/compile/compiler_warnings.ts
index 833722c3b95..267a97afcc3 100644
--- a/src/compiler/compile/compiler_warnings.ts
+++ b/src/compiler/compile/compiler_warnings.ts
@@ -1,12 +1,14 @@
// All compiler warnings should be listed and accessed from here
+import { ARIAPropertyDefinition } from 'aria-query';
+
/**
* @internal
*/
export default {
custom_element_no_tag: {
code: 'custom-element-no-tag',
- message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. . To hide this warning, use '
+ message: 'No custom element \'tag\' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. . To hide this warning, use '
},
unused_export_let: (component: string, property: string) => ({
code: 'unused-export-let',
@@ -60,6 +62,35 @@ export default {
code: 'a11y-aria-attributes',
message: `A11y: <${name}> should not have aria-* attributes`
}),
+ a11y_incorrect_attribute_type: (schema: ARIAPropertyDefinition, attribute: string) => {
+ let message;
+ switch (schema.type) {
+ case 'boolean':
+ message = `The value of '${attribute}' must be exactly one of true or false`;
+ break;
+ case 'id':
+ message = `The value of '${attribute}' must be a string that represents a DOM element ID`;
+ break;
+ case 'idlist':
+ message = `The value of '${attribute}' must be a space-separated list of strings that represent DOM element IDs`;
+ break;
+ case 'tristate':
+ message = `The value of '${attribute}' must be exactly one of true, false, or mixed`;
+ break;
+ case 'token':
+ message = `The value of '${attribute}' must be exactly one of ${(schema.values || []).join(', ')}`;
+ break;
+ case 'tokenlist':
+ message = `The value of '${attribute}' must be a space-separated list of one or more of ${(schema.values || []).join(', ')}`;
+ break;
+ default:
+ message = `The value of '${attribute}' must be of type ${schema.type}`;
+ }
+ return {
+ code: 'a11y-incorrect-aria-attribute-type',
+ message: `A11y: ${message}`
+ };
+ },
a11y_unknown_aria_attribute: (attribute: string, suggestion?: string) => ({
code: 'a11y-unknown-aria-attribute',
message: `A11y: Unknown aria attribute 'aria-${attribute}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '')
diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts
index 35131cefcfd..b7b2572986f 100644
--- a/src/compiler/compile/nodes/Element.ts
+++ b/src/compiler/compile/nodes/Element.ts
@@ -23,7 +23,7 @@ import { string_literal } from '../utils/stringify';
import { Literal } from 'estree';
import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors';
-import { ARIARoleDefintionKey, roles } from 'aria-query';
+import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query';
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;
@@ -177,6 +177,32 @@ function get_namespace(parent: Element, element: Element, explicit_namespace: st
return parent_element.namespace;
}
+function is_valid_aria_attribute_value(schema: ARIAPropertyDefinition, value: string | boolean): boolean {
+ switch (schema.type) {
+ case 'boolean':
+ return typeof value === 'boolean';
+ case 'string':
+ case 'id':
+ return typeof value === 'string';
+ case 'tristate':
+ return typeof value === 'boolean' || value === 'mixed';
+ case 'integer':
+ case 'number':
+ return typeof value !== 'boolean' && isNaN(Number(value)) === false;
+ case 'token': // single token
+ return (schema.values || [])
+ .indexOf(typeof value === 'string' ? value.toLowerCase() : value) > -1;
+ case 'idlist': // if list of ids, split each
+ return typeof value === 'string'
+ && value.split(' ').every((id) => typeof id === 'string');
+ case 'tokenlist': // if list of tokens, split each
+ return typeof value === 'string'
+ && value.split(' ').every((token) => (schema.values || []).indexOf(token.toLowerCase()) > -1);
+ default:
+ return false;
+ }
+}
+
export default class Element extends Node {
type: 'Element';
name: string;
@@ -431,6 +457,18 @@ export default class Element extends Node {
if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) {
component.warn(attribute, compiler_warnings.a11y_hidden(this.name));
}
+
+ // aria-proptypes
+ let value = attribute.get_static_value();
+ if (value === 'true') value = true;
+ if (value === 'false') value = false;
+
+ if (value !== null && value !== undefined && aria.has(name as ARIAProperty)) {
+ const schema = aria.get(name as ARIAProperty);
+ if (!is_valid_aria_attribute_value(schema, value)) {
+ component.warn(attribute, compiler_warnings.a11y_incorrect_attribute_type(schema, name));
+ }
+ }
}
// aria-role
diff --git a/test/validator/samples/a11y-aria-proptypes-boolean/input.svelte b/test/validator/samples/a11y-aria-proptypes-boolean/input.svelte
new file mode 100644
index 00000000000..c9929282296
--- /dev/null
+++ b/test/validator/samples/a11y-aria-proptypes-boolean/input.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+