Ensuring all users can easily control and are not adversely affected by slide rotation is an essential aspect of making carousels accessible.
For instance, the screen reader experience can be confusing and disorienting if slides that are not visible on screen are incorrectly hidden, e.g., displayed off-screen.
- Similarly, if slides rotate automatically and a screen reader user is not aware of the rotation, the user may read an element on slide one, execute the screen reader command for next element, and, instead of hearing the next element on slide one, hear an element from slide 2 without any knowledge that the element just announced is from an entirely new context.
+ Similarly, if slides rotate automatically and a screen reader user is not aware of the rotation, the user may read an element on slide 1, execute the screen reader command for next element, and, instead of hearing the next element on slide 1, hear an element from slide 2 without any knowledge that the element just announced is from an entirely new context.
-
-
-
Filter Patterns
@@ -486,4 +475,8 @@ lang: en
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/radio/examples/radio-activedescendant.md b/ARIA/apg/patterns/radio/examples/radio-activedescendant.md
index 31e0995f3..3a622a41c 100644
--- a/ARIA/apg/patterns/radio/examples/radio-activedescendant.md
+++ b/ARIA/apg/patterns/radio/examples/radio-activedescendant.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/radio/examples/radio-activedescendant/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -323,7 +323,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/radio/examples/radio-rating.md b/ARIA/apg/patterns/radio/examples/radio-rating.md
index 905088a30..1b39942f6 100644
--- a/ARIA/apg/patterns/radio/examples/radio-rating.md
+++ b/ARIA/apg/patterns/radio/examples/radio-rating.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/radio/examples/radio-rating/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -342,7 +342,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/radio/examples/radio.md b/ARIA/apg/patterns/radio/examples/radio.md
index 458123116..9008d7e6a 100644
--- a/ARIA/apg/patterns/radio/examples/radio.md
+++ b/ARIA/apg/patterns/radio/examples/radio.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/radio/examples/radio/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -313,7 +313,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/radio/radio-group-pattern.md b/ARIA/apg/patterns/radio/radio-group-pattern.md
index 5497d2ab5..274f610bf 100644
--- a/ARIA/apg/patterns/radio/radio-group-pattern.md
+++ b/ARIA/apg/patterns/radio/radio-group-pattern.md
@@ -181,4 +181,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/slider-multithumb/examples/slider-multithumb.md b/ARIA/apg/patterns/slider-multithumb/examples/slider-multithumb.md
index d564a85a3..9e6bce66f 100644
--- a/ARIA/apg/patterns/slider-multithumb/examples/slider-multithumb.md
+++ b/ARIA/apg/patterns/slider-multithumb/examples/slider-multithumb.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/slider-multithumb/examples/slider-multithumb/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -322,7 +322,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/slider-multithumb/slider-multithumb-pattern.md b/ARIA/apg/patterns/slider-multithumb/slider-multithumb-pattern.md
index 9fb911f21..ab809fd2d 100644
--- a/ARIA/apg/patterns/slider-multithumb/slider-multithumb-pattern.md
+++ b/ARIA/apg/patterns/slider-multithumb/slider-multithumb-pattern.md
@@ -129,4 +129,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/slider/examples/slider-color-viewer.md b/ARIA/apg/patterns/slider/examples/slider-color-viewer.md
index fe315ee97..d86c52679 100644
--- a/ARIA/apg/patterns/slider/examples/slider-color-viewer.md
+++ b/ARIA/apg/patterns/slider/examples/slider-color-viewer.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/slider/examples/slider-color-viewer/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -370,7 +370,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/slider/examples/slider-rating.md b/ARIA/apg/patterns/slider/examples/slider-rating.md
index 69d77eec1..f95fc7f27 100644
--- a/ARIA/apg/patterns/slider/examples/slider-rating.md
+++ b/ARIA/apg/patterns/slider/examples/slider-rating.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/slider/examples/slider-rating/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -373,7 +373,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/slider/examples/slider-seek.md b/ARIA/apg/patterns/slider/examples/slider-seek.md
index 27278baae..e8b18605e 100644
--- a/ARIA/apg/patterns/slider/examples/slider-seek.md
+++ b/ARIA/apg/patterns/slider/examples/slider-seek.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/slider/examples/slider-seek/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -365,7 +365,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/slider/examples/slider-temperature.md b/ARIA/apg/patterns/slider/examples/slider-temperature.md
index 54b72a035..72de7c56a 100644
--- a/ARIA/apg/patterns/slider/examples/slider-temperature.md
+++ b/ARIA/apg/patterns/slider/examples/slider-temperature.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/slider/examples/slider-temperature/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -346,7 +346,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/slider/slider-pattern.md b/ARIA/apg/patterns/slider/slider-pattern.md
index ed44f2183..6ba5df4d7 100644
--- a/ARIA/apg/patterns/slider/slider-pattern.md
+++ b/ARIA/apg/patterns/slider/slider-pattern.md
@@ -133,4 +133,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons.md b/ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons.md
index 6cba5e569..f0c28b2b5 100644
--- a/ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons.md
+++ b/ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons.md
@@ -1,6 +1,6 @@
---
# This file was generated by scripts/pre-build/library/formatForJekyll.js
-title: "Date Picker Spin Button Example"
+title: "(Deprecated) Date Picker Spin Button Example"
ref: /ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons/
github:
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -21,7 +21,7 @@ lang: en
---
-
Date Picker Spin Button Example
+
(Deprecated) Date Picker Spin Button Example
@@ -99,6 +99,13 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
About This Example
+
+
Deprecation Warning
+
+ This pattern has been deprecated, and will be removed in a future version of the ARIA Authoring Practices.
+ The Quantity Spin Button should be used as an alternative to this pattern.
+
+
The following example uses the Spin Button Pattern to implement a date picker.
@@ -382,7 +389,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
datepicker-spinbuttons.css
- Javascript:
+ JavaScript:
datepicker-spinbuttons.js
spinbutton-date.js
diff --git a/ARIA/apg/patterns/spinbutton/examples/quantity-spinbutton.md b/ARIA/apg/patterns/spinbutton/examples/quantity-spinbutton.md
new file mode 100644
index 000000000..23d4c539d
--- /dev/null
+++ b/ARIA/apg/patterns/spinbutton/examples/quantity-spinbutton.md
@@ -0,0 +1,518 @@
+---
+# This file was generated by scripts/pre-build/library/formatForJekyll.js
+title: "Quantity Spin Button Example"
+ref: /ARIA/apg/patterns/spinbutton/examples/quantity-spinbutton/
+
+github:
+ repository: w3c/aria-practices
+ branch: main
+ path: content/patterns/spinbutton/examples/quantity-spinbutton.html
+feedbackmail: public-aria-practices@w3.org
+permalink: /ARIA/apg/patterns/spinbutton/examples/quantity-spinbutton/
+
+sidebar: true
+
+footer: " "
+
+# Context here: https://github.com/w3c/wai-aria-practices/issues/31
+type_of_guidance: APG
+
+lang: en
+---
+
+
+Quantity Spin Button Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The code in this example is not intended for production environments.
+ Before using it for any purpose, read this to understand why.
+
+ This is an illustrative example of one way of using ARIA that conforms with the ARIA specification.
+
+
+
+
+
+
+
+
+ About This Example
+
+
+ The following example uses the Spin Button Pattern to implement three quantity inputs.
+
+ Similar examples include:
+
+ Toolbar Example : A toolbar that contains a spin button for setting font size.
+
+
+
+
+
+
+
+
+
+ Guests
+
+
+
+ Adults
+
+
+
+ −
+
+
+
+ +
+
+
+
Must be between 1 and 8
+
1 to 8
+
+
+
+
+ Kids
+
+
+
+ −
+
+
+
+ +
+
+
+
Must be between 0 and 8
+
0 to 8
+
+
+
+
+ Animals
+
+
+
+ −
+
+
+
+ +
+
+
+
Must be between 0 and 12
+
0 to 12
+
+
+
+
+
+
+
+
+
+
+ Accessibility Features
+
+
+ The element with role spinbutton allows text input, for
+ users who prefer to enter a value directly.
+
+
+ The spin button input and its adjacent buttons are visually
+ presented as a single form field containing an editable value, an
+ increment button , and a
+ decrement button .
+
+
+ When either the spin button input or its adjacent buttons have
+ received focus, a single visual focus indicator encompasses all
+ three, reinforcing the relationship between them.
+
+
+ For users who have not set a preference for reduced motion, the
+ focus indicator appears with subtle animation to draw attention.
+
+
+ The increment and decrement buttons:
+
+
+ Are generously sized for ease of use.
+
+
+ Are adjacent to the spin button input so they can be accessed by
+ users of touch-based and voice-based assistive technologies.
+
+
+ Are labeled with the title attribute, providing a
+ human-friendly representation of the plus and minus characters
+ for users of touch-based and voice-based assistive technologies. The
+ title attribute also presents a tooltip on hover,
+ clarifying the meaning of each button’s icon.
+
+
+ Use an invisible live region to announce the updated value
+ when pressed. The live region empties its contents after 3
+ seconds to avoid leaving stale content in the document.
+
+
+ Are excluded from the page Tab sequence with
+ tabindex="-1" because they are redundant with the
+ arrow key support provided to keyboard users.
+
+
+ Can be activated with voice control by speaking a command such
+ as Click add adult .
+
+
+
+
+ When forced colors are enabled, system
+ color keywords are used to ensure visibility and sufficient
+ contrast for the spin button’s content and interactive states.
+
+
+ Each spin button’s minimum and maximum values are programmatically
+ defined with aria-valuemin and
+ aria-valuemax, enabling assistive technologies to
+ convey that information to users in a predictable manner.
+
+
+ The minimum and maximum values are also visually expressed with
+ adjacent help text, but is purposefully not associated using
+ aria-describedby since the same information is already
+ programmatically defined.
+
+
+ When a user inputs an invalid value:
+
+
+ The spin button is visually styled to indicate an error state.
+
+
+ aria-invalid="true" is added to the input to inform
+ assistive technologies of the invalid state.
+
+
+ An error message is displayed and associated with
+ the input using aria-errormessage.
+
+
+
+
+
+
+
+ Keyboard Support
+ The spin buttons provide the following keyboard support described in the Spin Button Pattern .
+
+
+
+ Key
+ Function
+
+
+
+
+ Down Arrow
+ Decreases value by 1.
+
+
+ Up Arrow
+ Increases value by 1.
+
+
+ Home
+ Decreases to minimum value.
+
+
+ End
+ Increases to maximum value.
+
+
+ Standard single line text editing keys
+
+
+ Keys used for cursor movement and text manipulation, such as Delete and Shift + Right Arrow .
+ An HTML input with type="text" is used for the spin button so the browser will provide platform-specific editing keys.
+
+
+
+
+
+
+
+
+ Role, Property, State, and Tabindex Attributes
+
+
+
+ Role
+ Attribute
+ Element
+ Usage
+
+
+
+
+ spinbutton
+
+ input[type="text"]
+ Identifies the input[type="text"] element as a spin button.
+
+
+
+ aria-valuenow="NUMBER"
+ input[type="text"]
+
+
+ Indicates the current numeric value of the spin button.
+ Updated by JavaScript as users change the value of the spin button.
+
+
+
+
+
+ aria-valuemin="NUMBER"
+ input[type="text"]
+ Indicates the minimum allowed value for the spin button.
+
+
+
+ aria-valuemax="NUMBER"
+ input[type="text"]
+ Indicates the maximum allowed value for the spin button.
+
+
+
+ aria-invalid="true"
+ input[type="text"]
+ Indicates that the current value is invalid.
+
+
+
+ aria-errormessage="ID_REF"
+ input[type="text"]
+ Identifies the element that provides an error message for the spin button.
+
+
+
+ title="NAME_STRING"
+ button
+ Defines the accessible name for each increment and decrement button (Remove adult , Add adult , Remove kid , Add kid , Remove animal , and Add animal ).
+
+
+
+
+ aria-controls="ID_REF"
+
+ button
+ Identifies the element whose value will be modified when the button is activated.
+
+
+
+ tabindex="-1"
+ button
+ Removes the increment and decrement buttons from the page Tab sequence while keeping them focusable so they can be accessed with touch-based and voice-based assistive technologies.
+
+
+
+ aria-disabled="true"
+ button
+ Set when the minimum or maximum value has been reached to inform assistive technologies that the button has been disabled.
+
+
+
+ aria-hidden="true"
+ span
+ For assistive technology users, hides the visible minus and plus characters in the increment and decrement buttons since they are symbolic of the superior button labels provided by the title attribute.
+
+
+
+
+ output
+
+
+ An element with an implicit role of status and an implicit aria-live value of polite.
+ Triggers a screen reader announcement of the output element’s updated content at the next graceful opportunity.
+ When either the increment or decrement button is pressed, the current value of the spin button is injected into the output element.
+ Its contents are emptied 2000 milliseconds after injection.
+
+
+
+
+
+
+
+
+ JavaScript and CSS Source Code
+
+
+
+
+ HTML Source Code
+ To copy the following HTML code, please open it in CodePen.
+
+
+
+
+
+
+
+
+
+
diff --git a/ARIA/apg/patterns/spinbutton/spinbutton-pattern.md b/ARIA/apg/patterns/spinbutton/spinbutton-pattern.md
index dcbceecf6..dc1e42bb4 100644
--- a/ARIA/apg/patterns/spinbutton/spinbutton-pattern.md
+++ b/ARIA/apg/patterns/spinbutton/spinbutton-pattern.md
@@ -79,7 +79,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
@@ -150,4 +150,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/switch/examples/switch-button.md b/ARIA/apg/patterns/switch/examples/switch-button.md
index 526b7ff62..25c6b203e 100644
--- a/ARIA/apg/patterns/switch/examples/switch-button.md
+++ b/ARIA/apg/patterns/switch/examples/switch-button.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/switch/examples/switch-button/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -283,7 +283,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
switch-button.css
- Javascript:
+ JavaScript:
switch-button.js
diff --git a/ARIA/apg/patterns/switch/examples/switch-checkbox.md b/ARIA/apg/patterns/switch/examples/switch-checkbox.md
index 5e6d0328b..b1fd4d6a8 100644
--- a/ARIA/apg/patterns/switch/examples/switch-checkbox.md
+++ b/ARIA/apg/patterns/switch/examples/switch-checkbox.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/switch/examples/switch-checkbox/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -241,7 +241,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
switch-checkbox.css
- Javascript:
+ JavaScript:
switch-checkbox.js
diff --git a/ARIA/apg/patterns/switch/examples/switch.md b/ARIA/apg/patterns/switch/examples/switch.md
index 9b2608648..c1409bfff 100644
--- a/ARIA/apg/patterns/switch/examples/switch.md
+++ b/ARIA/apg/patterns/switch/examples/switch.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/switch/examples/switch/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -249,7 +249,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/switch/switch-pattern.md b/ARIA/apg/patterns/switch/switch-pattern.md
index 4d3b67e94..9001a17bc 100644
--- a/ARIA/apg/patterns/switch/switch-pattern.md
+++ b/ARIA/apg/patterns/switch/switch-pattern.md
@@ -129,4 +129,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/table/examples/sortable-table.md b/ARIA/apg/patterns/table/examples/sortable-table.md
index a0b79dbc5..642ca8740 100644
--- a/ARIA/apg/patterns/table/examples/sortable-table.md
+++ b/ARIA/apg/patterns/table/examples/sortable-table.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/table/examples/sortable-table/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -264,7 +264,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
sortable-table.css
- Javascript:
+ JavaScript:
sortable-table.js
diff --git a/ARIA/apg/patterns/table/examples/table.md b/ARIA/apg/patterns/table/examples/table.md
index 2484d0ae7..f15dbd7d4 100644
--- a/ARIA/apg/patterns/table/examples/table.md
+++ b/ARIA/apg/patterns/table/examples/table.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/table/examples/table/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -230,7 +230,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
CSS:
table.css
-
Javascript: Not applicable.
+
JavaScript: Not applicable.
diff --git a/ARIA/apg/patterns/table/table-pattern.md b/ARIA/apg/patterns/table/table-pattern.md
index 8d270a833..2a12bd3cb 100644
--- a/ARIA/apg/patterns/table/table-pattern.md
+++ b/ARIA/apg/patterns/table/table-pattern.md
@@ -140,4 +140,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/tabs/examples/tabs-actions.md b/ARIA/apg/patterns/tabs/examples/tabs-actions.md
index fe7525fb4..bc57dd6f8 100644
--- a/ARIA/apg/patterns/tabs/examples/tabs-actions.md
+++ b/ARIA/apg/patterns/tabs/examples/tabs-actions.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/tabs/examples/tabs-actions/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -546,8 +546,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/tabs/examples/tabs-automatic.md b/ARIA/apg/patterns/tabs/examples/tabs-automatic.md
index 1f4ae7c4d..40ce2f0ee 100644
--- a/ARIA/apg/patterns/tabs/examples/tabs-automatic.md
+++ b/ARIA/apg/patterns/tabs/examples/tabs-automatic.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/tabs/examples/tabs-automatic/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -406,7 +406,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/tabs/examples/tabs-manual.md b/ARIA/apg/patterns/tabs/examples/tabs-manual.md
index 810d38ea7..c0d706d38 100644
--- a/ARIA/apg/patterns/tabs/examples/tabs-manual.md
+++ b/ARIA/apg/patterns/tabs/examples/tabs-manual.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/tabs/examples/tabs-manual/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -400,7 +400,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
JavaScript and CSS Source Code
diff --git a/ARIA/apg/patterns/tabs/tabs-pattern.md b/ARIA/apg/patterns/tabs/tabs-pattern.md
index 962d38f25..f37d17e38 100644
--- a/ARIA/apg/patterns/tabs/tabs-pattern.md
+++ b/ARIA/apg/patterns/tabs/tabs-pattern.md
@@ -187,4 +187,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/toolbar/examples/toolbar.md b/ARIA/apg/patterns/toolbar/examples/toolbar.md
index c15e5ea96..1a239ba66 100644
--- a/ARIA/apg/patterns/toolbar/examples/toolbar.md
+++ b/ARIA/apg/patterns/toolbar/examples/toolbar.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/toolbar/examples/toolbar/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -1013,12 +1013,12 @@ But, in a larger sense, we can not dedicate, we can not consecrate, we can not h
diff --git a/ARIA/apg/patterns/toolbar/toolbar-pattern.md b/ARIA/apg/patterns/toolbar/toolbar-pattern.md
index 049ed45d0..4f90b20e9 100644
--- a/ARIA/apg/patterns/toolbar/toolbar-pattern.md
+++ b/ARIA/apg/patterns/toolbar/toolbar-pattern.md
@@ -168,4 +168,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/tooltip/tooltip-pattern.md b/ARIA/apg/patterns/tooltip/tooltip-pattern.md
index 021fd3d39..a2b2bf602 100644
--- a/ARIA/apg/patterns/tooltip/tooltip-pattern.md
+++ b/ARIA/apg/patterns/tooltip/tooltip-pattern.md
@@ -108,4 +108,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/treegrid/examples/treegrid-1.md b/ARIA/apg/patterns/treegrid/examples/treegrid-1.md
index 51d4294f1..1047c634d 100644
--- a/ARIA/apg/patterns/treegrid/examples/treegrid-1.md
+++ b/ARIA/apg/patterns/treegrid/examples/treegrid-1.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/treegrid/examples/treegrid-1/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -515,7 +515,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
treegrid-1.css
- Javascript:
+ JavaScript:
treegrid-1.js
diff --git a/ARIA/apg/patterns/treegrid/treegrid-pattern.md b/ARIA/apg/patterns/treegrid/treegrid-pattern.md
index 1387e74fb..0a9127aae 100644
--- a/ARIA/apg/patterns/treegrid/treegrid-pattern.md
+++ b/ARIA/apg/patterns/treegrid/treegrid-pattern.md
@@ -370,4 +370,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/treeview/examples/treeview-1a.md b/ARIA/apg/patterns/treeview/examples/treeview-1a.md
index 62b5fd848..9eb20f2cd 100644
--- a/ARIA/apg/patterns/treeview/examples/treeview-1a.md
+++ b/ARIA/apg/patterns/treeview/examples/treeview-1a.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/treeview/examples/treeview-1a/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -471,15 +471,15 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
tree.css
- Javascript:
+ JavaScript:
tree.js
- Javascript:
+ JavaScript:
treeitem.js
- Javascript:
+ JavaScript:
treeitemClick.js
diff --git a/ARIA/apg/patterns/treeview/examples/treeview-1b.md b/ARIA/apg/patterns/treeview/examples/treeview-1b.md
index 4cbef4517..54b0536b1 100644
--- a/ARIA/apg/patterns/treeview/examples/treeview-1b.md
+++ b/ARIA/apg/patterns/treeview/examples/treeview-1b.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/treeview/examples/treeview-1b/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -497,15 +497,15 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
tree.css
- Javascript:
+ JavaScript:
tree.js
- Javascript:
+ JavaScript:
treeitem.js
- Javascript:
+ JavaScript:
treeitemClick.js
diff --git a/ARIA/apg/patterns/treeview/examples/treeview-navigation.md b/ARIA/apg/patterns/treeview/examples/treeview-navigation.md
index 11376100f..e6aa904f1 100644
--- a/ARIA/apg/patterns/treeview/examples/treeview-navigation.md
+++ b/ARIA/apg/patterns/treeview/examples/treeview-navigation.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/treeview/examples/treeview-navigation/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -741,7 +741,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
treeview-navigation.css
- Javascript:
+ JavaScript:
treeview-navigation.js
diff --git a/ARIA/apg/patterns/treeview/treeview-pattern.md b/ARIA/apg/patterns/treeview/treeview-pattern.md
index 3e72a85a7..af7e383c6 100644
--- a/ARIA/apg/patterns/treeview/treeview-pattern.md
+++ b/ARIA/apg/patterns/treeview/treeview-pattern.md
@@ -327,4 +327,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/patterns/windowsplitter/windowsplitter-pattern.md b/ARIA/apg/patterns/windowsplitter/windowsplitter-pattern.md
index 21a201f23..5f666cf4e 100644
--- a/ARIA/apg/patterns/windowsplitter/windowsplitter-pattern.md
+++ b/ARIA/apg/patterns/windowsplitter/windowsplitter-pattern.md
@@ -144,4 +144,8 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/ARIA/apg/practices/keyboard-interface/keyboard-interface-practice.md b/ARIA/apg/practices/keyboard-interface/keyboard-interface-practice.md
index 9f56f807d..a7cb1b22a 100644
--- a/ARIA/apg/practices/keyboard-interface/keyboard-interface-practice.md
+++ b/ARIA/apg/practices/keyboard-interface/keyboard-interface-practice.md
@@ -261,10 +261,10 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
- The HTML tabindex
- and SVG2 tabindex
+ The HTML tabindex
+ and SVG2 tabindex
attributes can be used to add and remove elements from the tab sequence.
- The value of tabindex can also influence the order of the tab sequence, although authors are strongly advised not to use tabindex for that purpose.
+ The value of tabindex can also influence the order of the tab sequence, although authors are strongly advised not to use tabindex for that purpose.
@@ -279,23 +279,23 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
The most robust method of manipulating the order of the tab sequence while also maintaining alignment with the reading order that is currently available in all browsers is rearranging elements in the DOM.
-
The values of the tabindex attribute have the following effects.
+
The values of the tabindex attribute have the following effects.
- tabindex is not present or does not have a valid value
+ tabindex is not present or does not have a valid value
The element has its default focus behavior.
In HTML, only form controls and anchors with an HREF attribute are included in the tab sequence.
- tabindex="0"
+ tabindex="0"
The element is included in the tab sequence based on its position in the DOM.
- tabindex="-1"
+ tabindex="-1"
The element is not included in the tab sequence but is focusable with element.focus().
- tabindex="X" where X is an integer in the range 1 <= X <= 32767
+ tabindex="X" where X is an integer in the range 1 <= X <= 32767
Authors are strongly advised NOT to use these values.
- The element is placed in the tab sequence based on the value of tabindex.
- Elements with a tabindex value of 0 and elements that are focusable by default will be in the sequence after elements with a tabindex value of 1 or greater.
+ The element is placed in the tab sequence based on the value of tabindex.
+ Elements with a tabindex value of 0 and elements that are focusable by default will be in the sequence after elements with a tabindex value of 1 or greater.
@@ -334,14 +334,14 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
The following sections explain two strategies for managing focus inside composite elements: creating a roving tabindex and using the aria-activedescendant property.
+
The following sections explain two strategies for managing focus inside composite elements: creating a roving tabindex and using the aria-activedescendant property.
- Managing Focus Within Components Using a Roving tabindex
+ Managing Focus Within Components Using a Roving tabindex
- When using roving tabindex to manage focus in a composite UI component, the element that is to be included in the tab sequence has tabindex of "0" and all other focusable elements contained in the composite have tabindex of "-1".
- The algorithm for the roving tabindex strategy is as follows.
+ When using roving tabindex to manage focus in a composite UI component, the element that is to be included in the tab sequence has tabindex="0" and all other focusable elements contained in the composite have tabindex="-1".
+ The algorithm for the roving tabindex strategy is as follows.
@@ -362,29 +362,29 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
- One benefit of using roving tabindex rather than aria-activedescendant to manage focus is that the user agent will scroll the newly focused element into view.
+ One benefit of using roving tabindex rather than aria-activedescendant to manage focus is that the user agent will scroll the newly focused element into view.
- Managing Focus in Composites Using aria-activedescendant
+ Managing Focus in Composites Using aria-activedescendant
- If a component container has an ARIA role that supports the aria-activedescendant property, it is not necessary to manipulate the tabindex attribute and move DOM focus among focusable elements within the container.
+ If a component container has an ARIA role that supports the aria-activedescendant property, it is not necessary to manipulate the tabindex attribute and move DOM focus among focusable elements within the container.
Instead, only the container element needs to be included in the tab sequence.
- When the container has DOM focus, the value of aria-activedescendant on the container tells assistive technologies which element is active within the widget.
- Assistive technologies will consider the element referred to as active to be the focused element even though DOM focus is on the element that has the aria-activedescendant property.
- And, when the value of aria-activedescendant is changed, assistive technologies will receive focus change events equivalent to those received when DOM focus actually moves.
+ When the container has DOM focus, the value of aria-activedescendant on the container tells assistive technologies which element is active within the widget.
+ Assistive technologies will consider the element referred to as active to be the focused element even though DOM focus is on the element that has the aria-activedescendant property.
+ And, when the value of aria-activedescendant is changed, assistive technologies will receive focus change events equivalent to those received when DOM focus actually moves.
- The steps for using the aria-activedescendant method of managing focus are as follows.
+ The steps for using the aria-activedescendant method of managing focus are as follows.
- When the container element that has a role that supports aria-activedescendant is loaded or created, ensure that:
+ When the container element that has a role that supports aria-activedescendant is loaded or created, ensure that:
- The container element is included in the tab sequence as described in Keyboard Navigation Between Components or is a focusable element of a composite that implements a roving tabindex .
+ The container element is included in the tab sequence as described in Keyboard Navigation Between Components or is a focusable element of a composite that implements a roving tabindex .
- It has aria-activedescendant="IDREF" where IDREF is the ID of the element within the container that should be identified as active when the widget receives focus.
+ It has aria-activedescendant="IDREF" where IDREF is the id of the element within the container that should be identified as active when the widget receives focus.
The referenced element needs to meet the DOM relationship requirements described below.
@@ -393,25 +393,25 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
When the composite widget contains focus and the user presses a navigation key that moves focus within the widget, such as an arrow key:
- Change the value of aria-activedescendant on the container to refer to the element that should be reported to assistive technologies as active.
+ Change the value of aria-activedescendant on the container to refer to the element that should be reported to assistive technologies as active.
Move the visual focus indicator and, if necessary, scrolled the active element into view.
- If the design calls for a specific element to be focused the next time a user moves focus into the composite with Tab or Shift + Tab , check if aria-activedescendant is referring to that target element when the container loses focus.
- If it is not, set aria-activedescendant to refer to the target element.
+ If the design calls for a specific element to be focused the next time a user moves focus into the composite with Tab or Shift+Tab , check if aria-activedescendant is referring to that target element when the container loses focus.
+ If it is not, set aria-activedescendant to refer to the target element.
- The specification for aria-activedescendant places important restrictions on the DOM relationship between the focused element that has the aria-activedescendant attribute and the element referenced as active by the value of the attribute.
+ The specification for aria-activedescendant places important restrictions on the DOM relationship between the focused element that has the aria-activedescendant attribute and the element referenced as active by the value of the attribute.
One of the following three conditions must be met.
The element referenced as active is a DOM descendant of the focused referencing element.
The focused referencing element has a value specified for the aria-owns property that includes the ID of the element referenced as active.
- The focused referencing element has role of combobox , textbox , or searchbox and has aria-controls property referring to an element with a role that supports aria-activedescendant and either:
+ The focused referencing element has role of combobox , textbox , or searchbox and has aria-controls property referring to an element with a role that supports aria-activedescendant and either:
The element referenced as active is a descendant of the controlled element.
The controlled element has a value specified for the aria-owns property that includes the ID of the element referenced as active.
@@ -419,6 +419,16 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
+
+
+
Note
+
This guidance focuses specifically on keyboard interaction. However, authors also need to consider pointer interactions, such as mouse clicks
+ and touchscreen taps. When a component is clicked/tapped,
+ authors should take the same steps to set the correct tabindex or aria-activedescendant for the element,
+ in the same way that they would for keyboard navigation. Otherwise, this could lead to a confusing experience for users that switch between pointer
+ and keyboard navigation, as it will lead to a mismatch.
+
+
diff --git a/ARIA/apg/practices/practices.md b/ARIA/apg/practices/practices.md
index bcdb0a534..9a2fbf061 100644
--- a/ARIA/apg/practices/practices.md
+++ b/ARIA/apg/practices/practices.md
@@ -51,17 +51,6 @@ lang: en
-
-
-
@@ -148,4 +137,8 @@ lang: en
src="{{ '/content-assets/wai-aria-practices/shared/js/skipto.js' | relative_url }}"
data-skipto="colorTheme:aria; displayOption:popup; containerElement:div"
>
+
diff --git a/_external/aria-practices b/_external/aria-practices
index d16d25dd1..2cafe969a 160000
--- a/_external/aria-practices
+++ b/_external/aria-practices
@@ -1 +1 @@
-Subproject commit d16d25dd176638c1a04d779bdfebd87ff00ff793
+Subproject commit 2cafe969a58961e38324ca3cfbd777e721d774a6
diff --git a/_external/data b/_external/data
index fa0ce25cb..b353f827f 160000
--- a/_external/data
+++ b/_external/data
@@ -1 +1 @@
-Subproject commit fa0ce25cb3160a6e08d634740a2477dfe7e8fdcb
+Subproject commit b353f827f745968a28bb9473294279a84a5e03bb
diff --git a/content-assets/wai-aria-practices/about/coverage-and-quality/prop-coverage.csv b/content-assets/wai-aria-practices/about/coverage-and-quality/prop-coverage.csv
index 64c42b359..64f66b6c9 100644
--- a/content-assets/wai-aria-practices/about/coverage-and-quality/prop-coverage.csv
+++ b/content-assets/wai-aria-practices/about/coverage-and-quality/prop-coverage.csv
@@ -7,22 +7,22 @@
"aria-colcount","1","1","Guidance: Using aria-colcount and aria-colindex","Example: Data Grid"
"aria-colindex","3","1","Guidance: Using aria-colcount and aria-colindex","Guidance: Using aria-colindex When Column Indices Are Contiguous","Guidance: Using aria-colindex When Column Indices Are Not Contiguous","Example: Data Grid"
"aria-colspan","1","0","Guidance: Defining cell spans using aria-colspan and aria-rowspan"
-"aria-controls","0","22","Example: Accordion","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Checkbox (Mixed-State)","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Disclosure (Show/Hide) Card","Example: Disclosure (Show/Hide) for Answers to Frequently Asked Questions","Example: Disclosure (Show/Hide) for Image Description","Example: Disclosure Navigation Menu with Top-Level Links","Example: Disclosure Navigation Menu","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation","Example: Toolbar"
+"aria-controls","0","23","Example: Accordion","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Checkbox (Mixed-State)","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Disclosure (Show/Hide) Card","Example: Disclosure (Show/Hide) for Answers to Frequently Asked Questions","Example: Disclosure (Show/Hide) for Image Description","Example: Disclosure Navigation Menu with Top-Level Links","Example: Disclosure Navigation Menu","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Quantity Spin Button","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation","Example: Toolbar"
"aria-current","0","5","Example: Breadcrumb","Example: Disclosure Navigation Menu with Top-Level Links","Example: Disclosure Navigation Menu","Example: Navigation Menubar","Example: Navigation Treeview"
"aria-describedby","1","6","Guidance: Describing by referencing content with aria-describedby","Example: Alert Dialog","Example: Date Picker Combobox","Example: Date Picker Dialog","Example: Modal Dialog","Example: Infinite Scrolling Feed","Example: Table"
"aria-details","0","0"
-"aria-disabled","0","3","Example: Alert Dialog","Example: Editor Menubar","Example: Toolbar"
+"aria-disabled","0","4","Example: Alert Dialog","Example: Editor Menubar","Example: Quantity Spin Button","Example: Toolbar"
"aria-dropeffect","0","0"
-"aria-errormessage","0","0"
+"aria-errormessage","0","1","Example: Quantity Spin Button"
"aria-expanded","0","23","Example: Accordion","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Disclosure (Show/Hide) Card","Example: Disclosure (Show/Hide) for Answers to Frequently Asked Questions","Example: Disclosure (Show/Hide) for Image Description","Example: Disclosure Navigation Menu with Top-Level Links","Example: Disclosure Navigation Menu","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Editor Menubar","Example: Navigation Menubar","Example: Toolbar","Example: Treegrid Email Inbox","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties","Example: Navigation Treeview"
"aria-flowto","0","0"
"aria-grabbed","0","0"
"aria-haspopup","0","9","Example: Date Picker Combobox","Example: Editable Combobox with Grid Popup","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Editor Menubar","Example: Navigation Menubar","Example: Toolbar"
-"aria-hidden","0","16","Example: Button (IDL Version)","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox","Example: Editor Menubar","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Switch Using HTML Button","Example: Switch Using HTML Checkbox Input","Example: Switch","Example: Sortable Table","Example: Toolbar"
-"aria-invalid","0","0"
+"aria-hidden","0","17","Example: Button (IDL Version)","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox","Example: Editor Menubar","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Quantity Spin Button","Example: Switch Using HTML Button","Example: Switch Using HTML Checkbox Input","Example: Switch","Example: Sortable Table","Example: Toolbar"
+"aria-invalid","0","1","Example: Quantity Spin Button"
"aria-keyshortcuts","0","0"
-"aria-label","1","18","Guidance: Naming with a String Attribute Via aria-label","Example: Breadcrumb","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Date Picker Dialog","Example: Link","Example: Editor Menubar","Example: Navigation Menubar","Example: Rating Radio Group","Example: Horizontal Multi-Thumb Slider","Example: Date Picker Spin Button","Example: Table","Example: Toolbar","Example: Treegrid Email Inbox","Example: Navigation Treeview"
-"aria-labelledby","1","41","Guidance: Naming with Referenced Content Via aria-labelledby","Example: Accordion","Example: Alert Dialog","Example: Checkbox (Two State)","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Date Picker Dialog","Example: Modal Dialog","Example: Infinite Scrolling Feed","Example: Data Grid","Example: Layout Grid","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Navigation Menubar","Example: Meter","Example: Radio Group Using aria-activedescendant","Example: Rating Radio Group","Example: Radio Group Using Roving tabindex","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Switch Using HTML Button","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties","Example: Navigation Treeview","Example: Complementary Landmark","Example: Form Landmark","Example: Main Landmark","Example: Navigation Landmark","Example: Region Landmark","Example: Search Landmark"
+"aria-label","1","18","Guidance: Naming with a String Attribute Via aria-label","Example: Breadcrumb","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Date Picker Dialog","Example: Link","Example: Editor Menubar","Example: Navigation Menubar","Example: Rating Radio Group","Example: Horizontal Multi-Thumb Slider","Example: (Deprecated) Date Picker Spin Button","Example: Table","Example: Toolbar","Example: Treegrid Email Inbox","Example: Navigation Treeview"
+"aria-labelledby","1","41","Guidance: Naming with Referenced Content Via aria-labelledby","Example: Accordion","Example: Alert Dialog","Example: Checkbox (Two State)","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Date Picker Dialog","Example: Modal Dialog","Example: Infinite Scrolling Feed","Example: Data Grid","Example: Layout Grid","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Navigation Menubar","Example: Meter","Example: Radio Group Using aria-activedescendant","Example: Rating Radio Group","Example: Radio Group Using Roving tabindex","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Switch Using HTML Button","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties","Example: Navigation Treeview","Example: Complementary Landmark","Example: Form Landmark","Example: Main Landmark","Example: Navigation Landmark","Example: Region Landmark","Example: Search Landmark"
"aria-level","0","2","Example: Treegrid Email Inbox","Example: File Directory Treeview Using Declared Properties"
"aria-live","0","5","Example: Alert","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Date Picker Combobox","Example: Date Picker Dialog"
"aria-modal","0","4","Example: Alert Dialog","Example: Date Picker Combobox","Example: Date Picker Dialog","Example: Modal Dialog"
@@ -43,7 +43,7 @@
"aria-selected","0","17","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Date Picker Dialog","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties"
"aria-setsize","0","3","Example: Infinite Scrolling Feed","Example: Treegrid Email Inbox","Example: File Directory Treeview Using Declared Properties"
"aria-sort","1","2","Guidance: Indicating sort order with aria-sort","Example: Data Grid","Example: Sortable Table"
-"aria-valuemax","1","8","Guidance: Using aria-valuemin, aria-valuemax and aria-valuenow","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Toolbar"
-"aria-valuemin","0","8","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Toolbar"
-"aria-valuenow","1","8","Guidance: Using aria-valuemin, aria-valuemax and aria-valuenow","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Toolbar"
-"aria-valuetext","1","5","Guidance: Using aria-valuetext","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Toolbar"
+"aria-valuemax","1","9","Guidance: Using aria-valuemin, aria-valuemax and aria-valuenow","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Quantity Spin Button","Example: Toolbar"
+"aria-valuemin","0","9","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Quantity Spin Button","Example: Toolbar"
+"aria-valuenow","1","9","Guidance: Using aria-valuemin, aria-valuemax and aria-valuenow","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Quantity Spin Button","Example: Toolbar"
+"aria-valuetext","1","5","Guidance: Using aria-valuetext","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Toolbar"
diff --git a/content-assets/wai-aria-practices/about/coverage-and-quality/role-coverage.csv b/content-assets/wai-aria-practices/about/coverage-and-quality/role-coverage.csv
index e760c3cb2..c9c7ed7d2 100644
--- a/content-assets/wai-aria-practices/about/coverage-and-quality/role-coverage.csv
+++ b/content-assets/wai-aria-practices/about/coverage-and-quality/role-coverage.csv
@@ -25,7 +25,7 @@
"generic","0","0"
"grid","3","5","Guidance: Grid Popup Keyboard Interaction","Guidance: Grid (Interactive Tabular Data and Layout Containers) Pattern","Guidance: Grid and Table Properties","Example: Date Picker Combobox","Example: Editable Combobox with Grid Popup","Example: Date Picker Dialog","Example: Data Grid","Example: Layout Grid"
"gridcell","0","3","Example: Editable Combobox with Grid Popup","Example: Layout Grid","Example: Treegrid Email Inbox"
-"group","2","10","Guidance: Radio Group Pattern","Guidance: For Radio Group Contained in a Toolbar","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Checkbox (Two State)","Example: Listbox with Grouped Options","Example: Editor Menubar","Example: Color Viewer Slider","Example: Date Picker Spin Button","Example: Switch Using HTML Button","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties","Example: Navigation Treeview"
+"group","2","10","Guidance: Radio Group Pattern","Guidance: For Radio Group Contained in a Toolbar","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Checkbox (Two State)","Example: Listbox with Grouped Options","Example: Editor Menubar","Example: Color Viewer Slider","Example: (Deprecated) Date Picker Spin Button","Example: Switch Using HTML Button","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties","Example: Navigation Treeview"
"heading","0","0"
"img","0","0"
"input","0","0"
@@ -46,7 +46,7 @@
"meter","2","1","Guidance: Meter Pattern","Guidance: Range properties with meter","Example: Meter"
"navigation","5","3","Guidance: Fundamental Keyboard Navigation Conventions","Guidance: Keyboard Navigation Between Components (The Tab Sequence)","Guidance: Keyboard Navigation Inside Components","Guidance: Ensure Basic Access Via Navigation","Guidance: Navigation","Example: Navigation Menubar","Example: Navigation Treeview","Example: Navigation Landmark"
"none","0","7","Example: Navigation Menu Button","Example: Navigation Menubar","Example: Rating Radio Group","Example: Horizontal Multi-Thumb Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Navigation Treeview"
-"note","37","0","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note"
+"note","38","0","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note","Guidance: Note"
"option","0","8","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Select-Only Combobox","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox"
"paragraph","0","0"
"presentation","5","0","Guidance: Hiding Semantics with the presentation Role","Guidance: Common Uses of Role presentation","Guidance: Effects of Role presentation","Guidance: Conditions That Cause Role presentation to be Ignored","Guidance: Example Demonstrating Effects of the presentation Role"
@@ -62,7 +62,7 @@
"searchbox","0","0"
"separator","0","1","Example: Editor Menubar"
"slider","2","5","Guidance: Slider (Multi-Thumb) Pattern","Guidance: Slider Pattern","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider"
-"spinbutton","1","2","Guidance: Spinbutton Pattern","Example: Date Picker Spin Button","Example: Toolbar"
+"spinbutton","1","3","Guidance: Spinbutton Pattern","Example: (Deprecated) Date Picker Spin Button","Example: Quantity Spin Button","Example: Toolbar"
"status","0","0"
"switch","1","3","Guidance: Switch Pattern","Example: Switch Using HTML Button","Example: Switch Using HTML Checkbox Input","Example: Switch"
"tab","1","4","Guidance: Keyboard Navigation Between Components (The Tab Sequence)","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation"
diff --git a/content-assets/wai-aria-practices/patterns/menubar/examples/js/menubar-editor.js b/content-assets/wai-aria-practices/patterns/menubar/examples/js/menubar-editor.js
index e1fd29e02..2c77647f8 100644
--- a/content-assets/wai-aria-practices/patterns/menubar/examples/js/menubar-editor.js
+++ b/content-assets/wai-aria-practices/patterns/menubar/examples/js/menubar-editor.js
@@ -514,6 +514,8 @@ class MenubarEditor {
case ' ':
case 'Enter':
if (this.hasPopup(tgt)) {
+ // Need to update focus for the parent menu as well as the submenu
+ this.setFocusToMenuitem(menuId, tgt);
popupMenuId = this.openPopup(tgt);
this.setFocusToFirstMenuitem(popupMenuId);
} else {
@@ -553,6 +555,8 @@ class MenubarEditor {
flag = true;
} else {
if (this.hasPopup(tgt)) {
+ // Need to update focus for the parent menu as well as the submenu
+ this.setFocusToMenuitem(menuId, tgt);
popupMenuId = this.openPopup(tgt);
this.setFocusToFirstMenuitem(popupMenuId);
flag = true;
@@ -599,6 +603,8 @@ class MenubarEditor {
flag = true;
} else {
if (this.hasPopup(tgt)) {
+ // Need to update focus for the parent menu as well as the submenu
+ this.setFocusToMenuitem(menuId, tgt);
popupMenuId = this.openPopup(tgt);
this.setFocusToLastMenuitem(popupMenuId);
flag = true;
@@ -644,7 +650,9 @@ class MenubarEditor {
if (this.isOpen(tgt)) {
this.closePopup(tgt);
} else {
- var menuId = this.openPopup(tgt);
+ this.openPopup(tgt);
+ // Need to update focus for the parent menu, not the submenu
+ var menuId = this.getMenuId(tgt);
this.setFocusToMenuitem(menuId, tgt);
}
} else {
@@ -683,7 +691,8 @@ class MenubarEditor {
var tgt = event.currentTarget;
if (this.isAnyPopupOpen() && this.getMenu(tgt)) {
- this.setFocusToMenuitem(this.getMenu(tgt), tgt);
+ var menuId = this.getMenuId(tgt);
+ this.setFocusToMenuitem(menuId, tgt);
}
}
}
diff --git a/content-assets/wai-aria-practices/patterns/spinbutton/examples/css/quantity-spinbutton.css b/content-assets/wai-aria-practices/patterns/spinbutton/examples/css/quantity-spinbutton.css
new file mode 100644
index 000000000..f5aa0388e
--- /dev/null
+++ b/content-assets/wai-aria-practices/patterns/spinbutton/examples/css/quantity-spinbutton.css
@@ -0,0 +1,161 @@
+.spinners {
+ --length-s: 0.25rem;
+ --length-m: 0.5rem;
+ --color-field-background: white;
+ --color-button-background-idle: color-mix(in srgb, ghostwhite, darkblue 10%);
+ --color-button-background-hover: color-mix(in srgb, ghostwhite, darkblue 20%);
+ --color-interactive-focus: var(--wai-green, #005a6a);
+ --transition-duration-snappy: 0;
+ --transition-duration-leisurely: 0;
+
+ @media (prefers-reduced-motion: no-preference) and (forced-colors: none) {
+ --transition-duration-snappy: 0.15s;
+ --transition-duration-leisurely: 0.5s;
+ }
+
+ @media (forced-colors: active) {
+ --color-interactive-focus: Highlight;
+ }
+
+ display: inline-flex;
+ font-family: system-ui, sans-serif;
+ line-height: 1.4;
+ padding: 1rem;
+ background-color: color-mix(in srgb, ghostwhite, darkblue 1%);
+ border: 1px solid color-mix(in srgb, ghostwhite, darkblue 10%);
+ border-radius: 0.5rem;
+
+ *,
+ *::before,
+ *::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ }
+
+ .visually-hidden {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: auto;
+ margin: 0;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+ white-space: nowrap;
+ }
+
+ fieldset {
+ padding: 0.5rem;
+ border: 1px solid transparent;
+ }
+
+ legend {
+ font-size: 1.2rem;
+ font-weight: bold;
+ margin-block-end: 1rem;
+ }
+
+ .spinner-fields {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 2ch;
+ }
+
+ .spinner-field {
+ display: flex;
+ flex-direction: column;
+ align-items: start;
+ gap: 0.5rem;
+
+ label {
+ font-size: 1.2rem;
+ }
+
+ small {
+ font-size: 1rem;
+ }
+
+ [id^="error"] {
+ color: crimson;
+ }
+
+ [id^="error"],
+ &:has([aria-invalid="true"]) [id^="help"] {
+ display: none;
+ }
+
+ &:has([aria-invalid="true"]) [id^="error"] {
+ display: block;
+ }
+ }
+
+ .spinner {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0;
+ max-inline-size: calc(100vw - 6.4rem);
+ font-size: 1.4rem;
+ border: 1px solid color-mix(in srgb, ghostwhite, darkblue 60%);
+ border-radius: 0.25rem;
+ padding: 0.125em;
+ background-color: var(--color-field-background);
+ outline: 0 solid transparent;
+ outline-offset: 0;
+ transition:
+ outline-offset var(--transition-duration-snappy) ease,
+ outline-width var(--transition-duration-snappy) ease,
+ outline-color var(--transition-duration-snappy) ease,
+ border-color var(--transition-duration-snappy) ease;
+
+ &:focus-within {
+ outline: var(--length-s) solid var(--color-interactive-focus);
+ outline-offset: var(--length-s);
+ }
+
+ &:has([aria-invalid="true"]) {
+ border-color: crimson;
+ }
+
+ input,
+ button {
+ appearance: none;
+ font: inherit;
+ font-weight: bold;
+ color: inherit;
+ border: none;
+ background: transparent;
+ padding: 0.25em 0.5em;
+ margin: 0;
+ outline: none;
+ border-radius: 0;
+ }
+
+ [role="spinbutton"] {
+ text-align: center;
+ min-inline-size: 6ch;
+ font-variant-numeric: tabular-nums;
+
+ &,
+ &:hover,
+ &:focus {
+ border: none;
+ }
+ }
+
+ button {
+ min-inline-size: 3ch;
+ background-color: var(--color-button-background-idle);
+
+ &:hover {
+ background-color: var(--color-button-background-hover);
+ }
+
+ &[aria-disabled="true"] {
+ opacity: 0.25;
+ background-color: transparent;
+ cursor: not-allowed;
+ }
+ }
+ }
+}
diff --git a/content-assets/wai-aria-practices/patterns/spinbutton/examples/js/quantity-spinbutton.js b/content-assets/wai-aria-practices/patterns/spinbutton/examples/js/quantity-spinbutton.js
new file mode 100644
index 000000000..5a1013d16
--- /dev/null
+++ b/content-assets/wai-aria-practices/patterns/spinbutton/examples/js/quantity-spinbutton.js
@@ -0,0 +1,141 @@
+/*
+ * This content is licensed according to the W3C Software License at
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
+ *
+ * File: quantity-spinbutton.js
+ */
+
+'use strict';
+
+class SpinButton {
+ constructor(el) {
+ this.el = el;
+ this.id = el.id;
+ this.controls = Array.from(
+ document.querySelectorAll(`button[aria-controls="${this.id}"]`)
+ );
+ this.output = document.querySelector(`output[for="${this.id}"]`);
+ this.timer = null;
+ this.setBounds();
+ el.addEventListener('input', () => this.setValue(el.value, true));
+ el.addEventListener('blur', () => {
+ if (el.value === '' && this.hasMin) {
+ this.setValue(this.min, true);
+ }
+ });
+ el.addEventListener('keydown', (e) => this.handleKey(e));
+ this.controls.forEach((btn) =>
+ btn.addEventListener('click', () => this.handleClick(btn))
+ );
+ this.setValue(el.value);
+ }
+
+ clamp(n) {
+ return Math.min(Math.max(n, this.min), this.max);
+ }
+
+ parseValue(raw) {
+ const s = String(raw).trim();
+ if (!s) return null;
+ const n = parseInt(s.replace(/[^\d-]/g, ''), 10);
+ return isNaN(n) ? null : n;
+ }
+
+ setBounds() {
+ const el = this.el;
+ this.hasMin = el.hasAttribute('aria-valuemin');
+ this.hasMax = el.hasAttribute('aria-valuemax');
+ this.min = this.hasMin
+ ? +el.getAttribute('aria-valuemin')
+ : Number.MIN_SAFE_INTEGER;
+ this.max = this.hasMax
+ ? +el.getAttribute('aria-valuemax')
+ : Number.MAX_SAFE_INTEGER;
+ }
+
+ isValid(val) {
+ if (val === '' || val === null) return true;
+ const numVal = +val;
+ return !(
+ (this.hasMin && numVal < this.min) ||
+ (this.hasMax && numVal > this.max)
+ );
+ }
+
+ snapValue(val, currentVal) {
+ let result = val;
+
+ if (this.hasMin) result = Math.max(result, this.min);
+ if (this.hasMax) result = Math.min(result, this.max);
+ if (
+ (result < currentVal && val > currentVal) ||
+ (result > currentVal && val < currentVal)
+ ) {
+ return currentVal;
+ }
+ return result;
+ }
+
+ setValue(raw, fromInput = false) {
+ let val = typeof raw === 'number' ? raw : this.parseValue(raw);
+
+ if (!fromInput) {
+ const currentVal = +this.el.value || 0;
+ val = this.snapValue(val, currentVal);
+ }
+
+ this.el.value = val;
+ this.el.ariaValueNow = val;
+ this.el.ariaInvalid = !this.isValid(val) ? 'true' : null;
+ this.updateButtonStates();
+ }
+
+ updateButtonStates() {
+ const val = +this.el.value;
+ this.controls.forEach((btn) => {
+ const op = btn.getAttribute('data-spinbutton-operation');
+ btn.ariaDisabled = (
+ op === 'decrement' ? val <= this.min : val >= this.max
+ )
+ ? 'true'
+ : null;
+ });
+ }
+
+ announce() {
+ if (!this.output) return;
+ this.output.textContent = this.el.value;
+ clearTimeout(this.timer);
+ this.timer = setTimeout(() => {
+ this.output.textContent = '';
+ this.timer = null;
+ }, this.output.dataset.selfDestruct || 1000);
+ }
+
+ handleKey(e) {
+ let v = +this.el.value || 0;
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
+ e.preventDefault();
+ this.setValue(v + (e.key === 'ArrowUp' ? 1 : -1));
+ } else if (e.key === 'Home') {
+ e.preventDefault();
+ this.setValue(this.min);
+ } else if (e.key === 'End') {
+ e.preventDefault();
+ this.setValue(this.max);
+ }
+ }
+
+ handleClick(btn) {
+ const dir =
+ btn.getAttribute('data-spinbutton-operation') === 'decrement' ? -1 : 1;
+ this.setValue((+this.el.value || 0) + dir);
+ this.announce();
+ }
+}
+
+window.addEventListener('load', () =>
+ document
+ .querySelectorAll('[role="spinbutton"]')
+ .forEach((el) => new SpinButton(el))
+);
diff --git a/content-assets/wai-aria-practices/shared/js/read-this-first.js b/content-assets/wai-aria-practices/shared/js/read-this-first.js
new file mode 100644
index 000000000..dbf437001
--- /dev/null
+++ b/content-assets/wai-aria-practices/shared/js/read-this-first.js
@@ -0,0 +1,155 @@
+'use strict';
+
+/**
+ * Inserts the "Read This First" banner from /content/shared/templates/read-this-first.html into
+ * pages after the h1 element when the DOM is loaded. The banner is configured using data
+ * attributes on the script element.
+ *
+ * USAGE:
+ * Add this script to your HTML page with the appropriate data attributes:
+ *
+ *
+ *
+ * CONFIGURATION OPTIONS:
+ * - showImage: boolean (default: true) - Controls whether the illustration image is displayed
+ *
+ * BEHAVIOR:
+ * - Banner is inserted after the h1 element when DOM is loaded
+ * - If template file can't be fetched (e.g., CORS issues with file:// protocol), uses fallback
+ * - Paths are automatically adjusted based on script location
+ * - Image can be conditionally removed based on showImage setting
+ */
+(function () {
+ const defaultConfig = {
+ showImage: true,
+ };
+
+ // NOTE: If /content/shared/templates/read-this-first.html is ever changed, update this fallback banner to match
+ // MUST HAVE `div class="read-this-first"`
+ const fallbackBanner = `
+
+ `;
+
+ function getScriptBasePath() {
+ const scriptElement = document.querySelector(
+ 'script[src*="read-this-first.js"]'
+ );
+ if (!scriptElement) return '../../';
+
+ const scriptSrc = scriptElement.getAttribute('src');
+ // Extract the directory path from the script src
+ // e.g., "../../shared/js/read-this-first.js" gives "../../"
+ const match = scriptSrc.match(/^(.*\/)shared\/js\/read-this-first\.js$/);
+ return match ? match[1] : '../../';
+ }
+
+ function parseConfigFromDataAttribute() {
+ const config = { ...defaultConfig };
+ const configElem = document.querySelector('[data-read-this-first]');
+
+ if (configElem) {
+ const dataValue = configElem.getAttribute('data-read-this-first');
+ if (dataValue) {
+ const values = dataValue.split(';');
+ values.forEach((v) => {
+ let [prop, value] = v.split(':');
+ if (prop) {
+ prop = prop.trim();
+ }
+ if (value) {
+ value = value.trim();
+ }
+ if (prop && value) {
+ // Convert string values to appropriate types
+ if (value === 'true' || value === 'false') {
+ config[prop] = value === 'true';
+ } else {
+ config[prop] = value;
+ }
+ }
+ });
+ }
+ }
+
+ return config;
+ }
+
+ function removeImageIfNeeded(bannerElement, config) {
+ if (!config.showImage) {
+ const img = bannerElement.querySelector('img');
+ if (img) img.remove();
+ }
+ }
+
+ async function insertBanner(config) {
+ // Get the first found h1 element on page
+ const h1 = document.querySelector('h1');
+ if (!h1) return;
+
+ // Get the base path for relative URLs
+ const basePath = getScriptBasePath();
+
+ try {
+ // Fetch the banner HTML from the template file (will fail with file:// protocol)
+ const response = await fetch(
+ `${basePath}shared/templates/read-this-first.html`
+ );
+ const html = await response.text();
+
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, 'text/html');
+ // Get the read-this-first div
+ const bannerDiv = doc.querySelector('.read-this-first');
+
+ if (!bannerDiv) return;
+
+ const bannerElement = bannerDiv.cloneNode(true);
+ removeImageIfNeeded(bannerElement, config);
+ const img = bannerElement.querySelector('img'); // Line edited by pre-build script
+ if (img) img.setAttribute('src', `${basePath}../../content-images/wai-aria-practices/images/read-this-first.svg`); // Line edited by pre-build script
+ const a = bannerElement.querySelector('a'); // Line edited by pre-build script
+ if (a) a.setAttribute('href', `${basePath}../../ARIA/apg/practices/read-me-first/`); // Line edited by pre-build script
+
+ // Insert the banner after h1
+ h1.parentNode.insertBefore(bannerElement, h1.nextSibling);
+ } catch (error) {
+ // Fallback to static banner if fetch fails (CORS will fail with file:// protocol)
+ const tempBannerDiv = document.createElement('div');
+ // Adjust paths in the fallback banner based on script location
+ tempBannerDiv.innerHTML = fallbackBanner
+ .replace(/src="\.\.\/\.\.\//g, `src="${basePath}`)
+ .replace(/href="\.\.\/\.\.\//g, `href="${basePath}`);
+
+ const fallbackBannerElement = tempBannerDiv.firstElementChild;
+ removeImageIfNeeded(fallbackBannerElement, config);
+
+ // Insert the banner after h1
+ h1.parentNode.insertBefore(fallbackBannerElement, h1.nextSibling);
+ }
+ }
+
+ async function init() {
+ const config = parseConfigFromDataAttribute();
+ await insertBanner(config);
+ }
+
+ if (document.readyState === 'loading') {
+ // Initialize on DOMContentLoaded
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ // DOM is already loaded
+ init();
+ }
+})();
diff --git a/content-assets/wai-aria-practices/shared/templates/example-usage-warning.html b/content-assets/wai-aria-practices/shared/templates/example-usage-warning.html
index e081775b8..ec4eb86d0 100644
--- a/content-assets/wai-aria-practices/shared/templates/example-usage-warning.html
+++ b/content-assets/wai-aria-practices/shared/templates/example-usage-warning.html
@@ -3,26 +3,25 @@
Support Notice (Template)
+
-
- The code in this example is not intended for production environments.
- Before using it for any purpose, read this to understand why.
-
+ The code in this example is not intended for production environments.
+ Before using it for any purpose, read this to understand why.
This is an illustrative example of one way of using ARIA that conforms with the ARIA specification.
diff --git a/content-assets/wai-aria-practices/shared/templates/experimental-example-usage-warning.html b/content-assets/wai-aria-practices/shared/templates/experimental-example-usage-warning.html
new file mode 100644
index 000000000..3a60e6916
--- /dev/null
+++ b/content-assets/wai-aria-practices/shared/templates/experimental-example-usage-warning.html
@@ -0,0 +1,24 @@
+
+
+
+ Experimental Support Notice (Template)
+
+
+
+
+ Experimental content! Do not use except for new standards development purposes.
+ Please read below to understand why.
+
+ This is an experimental implementation of potential future techniques that may not yet be supported by web standards.
+
+ This example may include ARIA, HTML, CSS, or other code that is not yet included in a final web standard specification.
+ Experimental content is published in the APG only to facilitate discussion, gather feedback, and support testing of new features in browsers and assistive technologies.
+
+ There may be little or no support for this example in any or most
+ browser and assistive technology combinations .
+
+ The ARIA and Assistive Technologies Project is developing measurements of assistive technology support for APG examples.
+
+
+
+
diff --git a/content-assets/wai-aria-practices/shared/templates/read-this-first.html b/content-assets/wai-aria-practices/shared/templates/read-this-first.html
new file mode 100644
index 000000000..7879da749
--- /dev/null
+++ b/content-assets/wai-aria-practices/shared/templates/read-this-first.html
@@ -0,0 +1,21 @@
+
+
+
+ Read This First (Template)
+
+
+
+
+
diff --git a/content-assets/wai-aria-practices/styles.css b/content-assets/wai-aria-practices/styles.css
index 3971ff7be..986156659 100644
--- a/content-assets/wai-aria-practices/styles.css
+++ b/content-assets/wai-aria-practices/styles.css
@@ -1,7 +1,7 @@
/* Global Styles */
.page-practices .standalone-resource__main,
.default-grid.with-gap.leftcol .standalone-resource__prevnext,
-body:not(.has-sidebar) .leftcol .standalone-resource__main {
+body:not(.has-sidebar) .leftcol .standalone-resource__main {
grid-column: 2/10;
}
@@ -41,8 +41,8 @@ a.skip-main:focus, a.skip-main:active {
@media (min-width: 60em) {
.standalone-resource__sidebar {
- position: sticky;
- top: 16px;
+ position: sticky;
+ top: 16px;
}
}
@@ -473,7 +473,7 @@ table.def th {
width: 3em;
}
-table.def td,
+table.def td,
table.def th {
padding: 0.5em;
vertical-align: baseline;
@@ -483,13 +483,13 @@ table.def th {
background: #def;
}
-table.def > tbody > tr:last-child th,
+table.def > tbody > tr:last-child th,
table.def > tbody > tr:last-child td {
border-bottom: 0;
}
-table.data,
-table.index,
+table.data,
+table.index,
table.widget-features {
margin: 1em auto;
border-collapse: collapse;
@@ -497,14 +497,14 @@ table.widget-features {
width: 100%;
}
-table.data thead,
-table.index thead,
-table.data tbody,
+table.data thead,
+table.index thead,
+table.data tbody,
table.index tbody {
border-bottom: 2px solid;
}
-table.data td, table.data th,
+table.data td, table.data th,
table.index td, table.index th,
table.widget-features td, table.widget-features th {
padding: 0.5em 1em;
@@ -516,17 +516,17 @@ table.widget-features td, table.widget-features th {
color: inherit;
}
-table.data thead, table.index thead, table.widget-features thead,
+table.data thead, table.index thead, table.widget-features thead,
table.data tbody, table.index tbody, table.widget-features tbody {
border-bottom: 2px solid;
}
-table.data.attributes tbody th,
+table.data.attributes tbody th,
table.data.attributes tbody td {
border: 1px solid silver;
}
-table.data tbody th:first-child,
+table.data tbody th:first-child,
table.index tbody th:first-child,
table.widget-features tbody th:first-child {
border-top: 1px solid silver;
@@ -632,33 +632,33 @@ table :not(.highlight) code {
margin-left: 0;
}
-.note::before, .note >
+.note::before, .note >
.marker, details.note > summary {
color: hsl(120, 70%, 30%);
}
-.note > p:last-child,
-.issue > p:last-child,
-blockquote > :last-child,
-.amendment > p:last-child,
-.correction > p:last-child,
+.note > p:last-child,
+.issue > p:last-child,
+blockquote > :last-child,
+.amendment > p:last-child,
+.correction > p:last-child,
.addition > p:last-child {
margin-bottom: 0;
}
-.issue::before,
-.issue > .marker,
-.example::before,
-.example > .marker,
-.note::before,
-.note > .marker,
-details.note > summary > .marker,
-.amendment::before, .amendment > .marker,
-details.amendment > summary > .marker,
-.correction::before,
-.correction > .marker,
-details.correction > summary > .marker,
-.addition::before, .addition > .marker,
+.issue::before,
+.issue > .marker,
+.example::before,
+.example > .marker,
+.note::before,
+.note > .marker,
+details.note > summary > .marker,
+.amendment::before, .amendment > .marker,
+details.amendment > summary > .marker,
+.correction::before,
+.correction > .marker,
+details.correction > summary > .marker,
+.addition::before, .addition > .marker,
details.addition > summary > .marker {
text-transform: uppercase;
padding-right: 1em;
@@ -989,13 +989,19 @@ table.sortable th button:focus {
width: 50%;
}
+.read-this-first:not(:has(img)) .text {
+ margin-top: 0;
+ margin-left: 0;
+ margin-bottom: 0;
+}
+
@media screen and (min-width: 23em) {
.read-this-first img {
width: 178px;
}
}
-@media screen and (min-width: 35em) {
+@media screen and (min-width: 35em) {
.read-this-first {
overflow: visible;
}
@@ -1007,16 +1013,22 @@ table.sortable th button:focus {
margin-left: 14em;
margin-bottom: 0;
}
+ .read-this-first:not(:has(img)) .text {
+ margin-top: 0;
+ margin-left: 0;
+ }
}
-@media screen and (min-width: 35em) {
+@media screen and (min-width: 35em) {
.read-this-first {
margin-top: 3em;
}
-
+ .read-this-first:not(:has(img)) {
+ margin-top: 0;
+ }
}
-@media screen and (min-width: 60em) {
+@media screen and (min-width: 60em) {
.read-this-first {
margin-top: 4em;
padding: 1em;
@@ -1024,4 +1036,10 @@ table.sortable th button:focus {
.read-this-first .text {
margin-right: 2em;
}
-}
\ No newline at end of file
+ .read-this-first:not(:has(img)) {
+ margin-top: 0;
+ }
+ .read-this-first:not(:has(img)) .text {
+ margin-left: 0;
+ }
+}
diff --git a/scripts/pre-build/library/formatForJekyll.js b/scripts/pre-build/library/formatForJekyll.js
index b51492fea..99e2ec7a5 100644
--- a/scripts/pre-build/library/formatForJekyll.js
+++ b/scripts/pre-build/library/formatForJekyll.js
@@ -16,21 +16,43 @@ const formatForJekyll = ({
let skipToScriptTag = `';
+ readThisFirstScriptTag += '>';
// Must be formatted because html which is indented by 4 spaces
// will be interpreted as a code block by the markdown engine
const formattedHead = prettier.format(headContent, { parser: "html" });
const formattedSkipToScript = prettier.format(skipToScriptTag, { parser: "html" });
+ const formattedReadThisFirstScript = prettier.format(readThisFirstScriptTag, { parser: "html" });
return `---
# This file was generated by scripts/pre-build/library/formatForJekyll.js
@@ -92,7 +114,7 @@ ${
${content}
-${formattedSkipToScript}
+${formattedSkipToScript}${hasReadThisFirstScript ? formattedReadThisFirstScript : ''}
${/* `, { parser: "html" })} */ ""}`;
};
diff --git a/scripts/pre-build/library/rewritePath.js b/scripts/pre-build/library/rewritePath.js
index 3f632a35b..0ec1a1ace 100644
--- a/scripts/pre-build/library/rewritePath.js
+++ b/scripts/pre-build/library/rewritePath.js
@@ -27,7 +27,11 @@ const rewriteSourcePath = (sourcePath) => {
let buildRelative = githubPath.replace(/^content\//, "");
if (contentType === "template") {
- return { githubPath, buildPath: null, sitePath: null };
+ const buildPath = path.resolve(
+ projectRoot,
+ `content-assets/wai-aria-practices/${buildRelative}`
+ );
+ return { githubPath, buildPath, sitePath: buildRelative };
}
if (contentType !== "htmlAsset") {
buildRelative = buildRelative.replace(/\.html$/, ".md");
@@ -97,7 +101,7 @@ const getSitePath = (buildPath, contentType) => {
case "otherAsset":
return buildRelative;
case "template":
- return null;
+ return buildRelative;
default:
throw new Error(`Script did not recognize content type ${contentType}`);
}
diff --git a/scripts/pre-build/library/transformOtherAsset.js b/scripts/pre-build/library/transformOtherAsset.js
index 296f038fa..828e72210 100644
--- a/scripts/pre-build/library/transformOtherAsset.js
+++ b/scripts/pre-build/library/transformOtherAsset.js
@@ -18,6 +18,16 @@ const transformAsset = async (sourcePath, sourceContents) => {
"displayOption: 'popup', // Line edited by pre-build script"
);
}
+ if (sourcePath.endsWith("content/shared/js/read-this-first.js")) {
+ return sourceContents.replace(
+ "removeImageIfNeeded(bannerElement, config);",
+ "removeImageIfNeeded(bannerElement, config);\n" +
+ " const img = bannerElement.querySelector('img'); // Line edited by pre-build script\n" +
+ " if (img) img.setAttribute('src', `${basePath}../../content-images/wai-aria-practices/images/read-this-first.svg`); // Line edited by pre-build script\n" +
+ " const a = bannerElement.querySelector('a'); // Line edited by pre-build script\n" +
+ " if (a) a.setAttribute('href', `${basePath}../../ARIA/apg/practices/read-me-first/`); // Line edited by pre-build script"
+ )
+ }
return sourceContents;
};
diff --git a/scripts/pre-build/library/transformPatternIndex.js b/scripts/pre-build/library/transformPatternIndex.js
index 29166b0e4..16eab2938 100644
--- a/scripts/pre-build/library/transformPatternIndex.js
+++ b/scripts/pre-build/library/transformPatternIndex.js
@@ -1,26 +1,10 @@
-const path = require("path");
-const fs = require("fs/promises");
const { parse: parseHtml } = require("node-html-parser");
const formatForJekyll = require("./formatForJekyll");
-const { rewriteSourcePath, sourceRoot } = require("./rewritePath");
+const { rewriteSourcePath } = require("./rewritePath");
const removeConflictingCss = require("./removeConflictingCss");
const rewriteElementPaths = require("./rewriteElementPaths");
-const getReadThisFirst = async (sourcePath) => {
- const relativePath = "content/shared/templates/read-this-first.html";
- const filePath = path.resolve(sourceRoot, relativePath);
- const fileContent = await fs.readFile(filePath, { encoding: "utf8" });
- const html = parseHtml(fileContent);
- await rewriteElementPaths(html, {
- onSourcePath: sourcePath,
- optionalTemplateSourcePath: path.join(sourceRoot, relativePath),
- });
- return html.querySelector("body").innerHTML;
-};
-
const transformPatternIndex = async (sourcePath, sourceContents) => {
- const readThisFirst = await getReadThisFirst(sourcePath);
-
const { sitePath, githubPath } = rewriteSourcePath(sourcePath);
const html = parseHtml(sourceContents);
@@ -32,7 +16,6 @@ const transformPatternIndex = async (sourcePath, sourceContents) => {
await rewriteElementPaths(html, { onSourcePath: sourcePath });
const content = `
- ${readThisFirst}
${html.querySelector("body").innerHTML}
`
@@ -47,4 +30,3 @@ const transformPatternIndex = async (sourcePath, sourceContents) => {
};
module.exports = transformPatternIndex;
-module.exports.getReadThisFirst = getReadThisFirst
diff --git a/scripts/pre-build/library/transformPracticeIndex.js b/scripts/pre-build/library/transformPracticeIndex.js
index 56834fc60..8272991e5 100644
--- a/scripts/pre-build/library/transformPracticeIndex.js
+++ b/scripts/pre-build/library/transformPracticeIndex.js
@@ -3,11 +3,8 @@ const formatForJekyll = require("./formatForJekyll");
const { rewriteSourcePath } = require("./rewritePath");
const removeConflictingCss = require("./removeConflictingCss");
const rewriteElementPaths = require("./rewriteElementPaths");
-const { getReadThisFirst } = require("./transformPatternIndex");
-const transformPatternIndex = async (sourcePath, sourceContents) => {
- const readThisFirst = await getReadThisFirst(sourcePath);
-
+const transformPracticeIndex = async (sourcePath, sourceContents) => {
const { sitePath, githubPath } = rewriteSourcePath(sourcePath);
const html = parseHtml(sourceContents);
@@ -19,7 +16,6 @@ const transformPatternIndex = async (sourcePath, sourceContents) => {
await rewriteElementPaths(html, { onSourcePath: sourcePath });
const content = `
- ${readThisFirst}
${html.querySelector("body").innerHTML}
`
@@ -33,4 +29,4 @@ const transformPatternIndex = async (sourcePath, sourceContents) => {
});
};
-module.exports = transformPatternIndex;
+module.exports = transformPracticeIndex;
diff --git a/scripts/pre-build/library/transformTemplate.js b/scripts/pre-build/library/transformTemplate.js
new file mode 100644
index 000000000..e6f203176
--- /dev/null
+++ b/scripts/pre-build/library/transformTemplate.js
@@ -0,0 +1,5 @@
+const transformTemplate = async (sourcePath, sourceContents) => {
+ return sourceContents;
+};
+
+module.exports = transformTemplate;
diff --git a/scripts/pre-build/pre-build.js b/scripts/pre-build/pre-build.js
index 694de71c3..ac815b356 100644
--- a/scripts/pre-build/pre-build.js
+++ b/scripts/pre-build/pre-build.js
@@ -12,6 +12,7 @@ const transformPracticeIndex = require("./library/transformPracticeIndex");
const transformImageAsset = require("./library/transformImageAsset");
const transformHtmlAsset = require("./library/transformHtmlAsset");
const transformOtherAsset = require("./library/transformOtherAsset");
+const transformTemplate = require("./library/transformTemplate");
const preBuild = async () => {
await emptyBuildFolders();
@@ -46,7 +47,7 @@ const preBuild = async () => {
case "otherAsset":
return transformOtherAsset(sourcePath, sourceContents);
case "template":
- break;
+ return transformTemplate(sourcePath, sourceContents);
default:
throw new Error(
`Script did not recognize content type ${contentType}`