Skip to content

Commit 77a2abc

Browse files
authored
fix(core): properly render 'for' attribute for labelable elements (#228)
1 parent f8222de commit 77a2abc

File tree

3 files changed

+34
-4
lines changed

3 files changed

+34
-4
lines changed

.changeset/tangy-plums-boil.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@formwerk/core': patch
3+
---
4+
5+
fix(core): properly render 'for' attribute for labelable elements

packages/core/src/a11y/useLabel.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { MaybeRefOrGetter, computed, toValue, shallowRef } from 'vue';
22
import { Maybe, AriaLabelProps, AriaLabelableProps } from '../types';
3-
import { createRefCapture, isInputElement, isLabelElement } from '../utils/common';
3+
import { createRefCapture, isLabelableElement, isLabelElement, warn } from '../utils/common';
44

55
interface LabelProps {
66
for: MaybeRefOrGetter<string>;
@@ -13,17 +13,29 @@ export function useLabel(props: LabelProps) {
1313
const labelRef = shallowRef<HTMLElement>();
1414
const refCapture = createRefCapture(labelRef);
1515

16+
const shouldRenderFor = () => isLabelElement(labelRef.value) && isLabelableElement(toValue(props.targetRef));
17+
1618
const labelProps = computed<AriaLabelProps>(() => {
19+
const hasFor = shouldRenderFor();
20+
21+
if (__DEV__) {
22+
if (!hasFor && labelRef.value && isLabelElement(labelRef.value)) {
23+
warn(
24+
'Skipping `for` attribute on <label /> element because it is not associated with a labelable element.\nhttps://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Content_categories#labelable',
25+
);
26+
}
27+
}
28+
1729
return {
1830
ref: refCapture,
1931
id: `${toValue(props.for)}-l`,
20-
for: isLabelElement(labelRef.value) && isInputElement(toValue(props.targetRef)) ? toValue(props.for) : undefined,
32+
for: hasFor ? toValue(props.for) : undefined,
2133
onClick: props.handleClick || undefined,
2234
} as AriaLabelProps;
2335
});
2436

2537
const labelledByProps = computed<AriaLabelableProps>(() => {
26-
if (isLabelElement(labelRef.value) && isInputElement(toValue(props.targetRef))) {
38+
if (shouldRenderFor()) {
2739
return {};
2840
}
2941

packages/core/src/utils/common.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,19 @@ export function isInputElement(el: Maybe<HTMLElement>): el is HTMLInputElement {
304304
return isComponent(el) || ['INPUT', 'TEXTAREA', 'SELECT'].includes(el.tagName);
305305
}
306306

307+
/**
308+
* Checks if an element is labelable.
309+
* https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Content_categories#labelable
310+
*/
311+
export function isLabelableElement(el: Maybe<HTMLElement | ComponentPublicInstance>): boolean {
312+
return (
313+
isComponent(el) ||
314+
isInputElement(el) ||
315+
isButtonElement(el) ||
316+
['METER', 'OUTPUT', 'PROGRESS'].includes(el?.tagName ?? '')
317+
);
318+
}
319+
307320
export function isLabelElement(el: Maybe<HTMLElement | ComponentPublicInstance>): el is HTMLLabelElement {
308321
if (!el) {
309322
return false;
@@ -320,7 +333,7 @@ export function isComponent(ref: Maybe<HTMLElement | ComponentPublicInstance>):
320333
return '$' in ref;
321334
}
322335

323-
export function isButtonElement(el: Maybe<HTMLElement>): el is HTMLButtonElement {
336+
export function isButtonElement(el: Maybe<HTMLElement | ComponentPublicInstance>): el is HTMLButtonElement {
324337
return isComponent(el) || el?.tagName === 'BUTTON';
325338
}
326339

0 commit comments

Comments
 (0)