Reputation: 925
I'm writing a custom input element called fancy-input
, which can participate in a form via the FormInternals
API. In particular, the ElementInternals.labels
property gives me a list of the label
elements associated, via their for
attributes, to an instance of fancy-input
.
But now let's say I also want to write a custom label element called fancy-label
. It renders a native label element in its shadow DOM with fancy styles, formatting, etc. How do make it known that an instance of fancy-label
is the "label" associated with an instance of fancy-input
. In particular, I'd like my fancy-label
node appear in the NodeList
returned by calling ElementInternals.labels
on the associated fancy-input
element.
One possible approach would be for fancy-label
to render a native label
into a slot, keeping it in the light DOM and, hence, visible to the form and the fancy-input
element. But is there a solution with the native label
in the shadow DOM of fancy-label
?
Upvotes: 3
Views: 225
Reputation: 5729
Today's API related to form-associated elements (FACE) doesn't allow you to create a custom label functioning just like the native label
. This topic has been discussed last year, and could be solved by delegating the label or cross-DOM references. However, you can still implement a working custom label fairly easily today. The feasibility will depend on your goal.
For example, instead of this requirement:
"I'd like my
fancy-label
node appear in theNodeList
returned by callingElementInternals.labels
on the associated fancy-input element."
Let's say this:
"I'd like my
fancy-label
node work with mouse, keyboard and assistive technologies like screen readers (SR) just as the nativelabel
does."
This requirement can be implemented with the aria-labelledby
attribute. Instead of having the label point to its field, you can have the field point to its label. You will need to assign a unique ID to the label instead of to the field:
<fancy-label id="text-label">
Name:
<input name="text" type="text" aria-labelledby="text-label">
</fancy-label>
<fancy-label id="text-label">Name:</fancy-label>
<input name="text" type="text" aria-labelledby="text-label">
You can reference any element with content by aria-labelledby
. SR will read its contents it just like the contents of a native label
. What remains is to handling clicks on the custom label, so that they'd perform clicks on the field. You'll need a little JavaScript for it:
class FancyLabel extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(document.createElement('slot'))
this.addEventListener('click', event => this.#handleClick(event))
}
#handleClick(event) {
const field = document.querySelector(`[aria-labelledby="${this.id}"]`)
// ignore the click if the field does not exist, or if it is disabled,
// or if the field is inside the label and this event bubbled from it
if (!field || field.disabled || event.target === field) return
field.focus()
field.click()
}
}
customElements.define('fancy-label', FancyLabel)
If you wanted to support the for
attribute known from the native label
element, you could add code for:
id
attribute on the custom labelfor
attribute on the custom labelaria-labelledby
attribute on the target field (pointed to by the for
attribute) to the id
of the custom label whenever its for
attribute changesYou have a look at piwo-label, which implements this.
Upvotes: 0