diff --git a/.changeset/a11y-wcag-fixes.md b/.changeset/a11y-wcag-fixes.md
new file mode 100644
index 000000000..e3a7fcea3
--- /dev/null
+++ b/.changeset/a11y-wcag-fixes.md
@@ -0,0 +1,7 @@
+---
+"@tabler/core": patch
+"@tabler/preview": patch
+"@tabler/docs": patch
+---
+
+Fixed accessibility issues for skip links, keyboard focus, `prefers-reduced-motion`, form labels, and action controls that incorrectly used links instead of `button` elements.
diff --git a/core/scss/_variables.scss b/core/scss/_variables.scss
index 0d8ad01a3..efeed4168 100644
--- a/core/scss/_variables.scss
+++ b/core/scss/_variables.scss
@@ -1006,7 +1006,7 @@ $box-shadow-inset: inset 0 1px 2px rgba($black, 0.075) !default;
$component-active-color: $white !default;
$component-active-bg: $primary !default;
-// Focus
+// Focus (WCAG 2.2 SC 1.4.11)
$focus-ring-width: 0.25rem !default;
$focus-ring-opacity: 0.25 !default;
$focus-ring-color: color-mix(in srgb, var(--primary) #{math.percentage($focus-ring-opacity)}, transparent) !default;
@@ -1355,7 +1355,7 @@ $carousel-indicators-vertical-margin-inline-end: 1rem !default;
// Close
$btn-close-width: 1em !default;
-$btn-close-padding-x: 0.25em !default;
+$btn-close-padding-x: 0.875em !default;
$btn-close-padding-y: $btn-close-padding-x !default;
$btn-close-color: $body-color !default;
$btn-close-bg: url("data:image/svg+xml,") !default;
diff --git a/core/scss/bootstrap/_placeholders.scss b/core/scss/bootstrap/_placeholders.scss
index d6121b5af..08b212469 100644
--- a/core/scss/bootstrap/_placeholders.scss
+++ b/core/scss/bootstrap/_placeholders.scss
@@ -31,6 +31,10 @@
.placeholder-glow {
.placeholder {
animation: placeholder-glow 2s ease-in-out infinite;
+
+ @media (prefers-reduced-motion: reduce) {
+ animation: none;
+ }
}
}
@@ -44,6 +48,10 @@
mask-image: linear-gradient(130deg, $black 55%, rgba(0, 0, 0, (1 - $placeholder-opacity-min)) 75%, $black 95%);
mask-size: 200% 100%;
animation: placeholder-wave 2s linear infinite;
+
+ @media (prefers-reduced-motion: reduce) {
+ animation: none;
+ }
}
@keyframes placeholder-wave {
diff --git a/docs/pages/ui/forms/form-validation.mdx b/docs/pages/ui/forms/form-validation.mdx
index db677bd6a..772dc0456 100644
--- a/docs/pages/ui/forms/form-validation.mdx
+++ b/docs/pages/ui/forms/form-validation.mdx
@@ -12,18 +12,18 @@ import CodeDocs from '@components/CodeDocs.astro';
To inform users whether the entered value is correct or not, use either of the validation states. Thanks to that, users will immediately know which form elements they need to correct and, if the state displays as invalid, why the value is incorrect.
-To apply the validation state to the form control, use the `.is-valid` and `.is-invalid` classes.
+To apply the validation state to the form control, use the `.is-valid` and `.is-invalid` classes. For assistive tech, also set `aria-invalid="true"` on invalid controls.
-
+
## Validation feedback
-To provide users with additional information about the validation state, you can use the `.valid-feedback` and `.invalid-feedback` classes.
+To provide users with additional information about the validation state, you can use the `.valid-feedback` and `.invalid-feedback` classes. Link the message with `aria-describedby` when you can.
-
Please provide a valid city.
+
Please provide a valid city.
You can also use the `.valid-feedback` to provide users with positive feedback.
diff --git a/preview/pages/all-elements.astro b/preview/pages/all-elements.astro
index e493679c5..2d55a0bdf 100644
--- a/preview/pages/all-elements.astro
+++ b/preview/pages/all-elements.astro
@@ -245,16 +245,16 @@ function greetUser(name) {
-
+
-
+
{/* Check.astro has no id prop to target with `for`; its own