Skip to content

feat(checkbox radio-group): add description and tooltip support - #3605

Draft
michal-sanoma wants to merge 27 commits into
mainfrom
feat/2903_description_for_checkbox_and_radio_group
Draft

feat(checkbox radio-group): add description and tooltip support#3605
michal-sanoma wants to merge 27 commits into
mainfrom
feat/2903_description_for_checkbox_and_radio_group

Conversation

@michal-sanoma

@michal-sanoma michal-sanoma commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR extends both sl-checkbox and sl-radio components with description and tooltip functionality matching the Figma design specifications.

Changes

  • description Support (sl-checkbox & sl-radio):

    • Added @property() description?: string and <slot name="description"> to allow providing helper/description text either via property or slot.
    • Added part="content" container grouping part="label" and part="description".
    • Added has-description host attribute reflecting whether a description is present.
    • Added styling using design tokens: --sl-color-foreground-subtlest and --sl-text-size-body-sm (with size-adjusted variants).
    • Ensured full accessibility (a11y) by linking the description element to the control via aria-describedby.
  • tooltip Support (sl-checkbox & sl-radio):

    • Registered scoped sl-tooltip via ScopedElementsMixin.
    • Added @property() tooltip?: string rendering <sl-tooltip for="wrapper" part="tooltip" type="description">

    AFTER

Screenshot 2026-08-17 at 11 35 01 Screenshot 2026-08-17 at 11 35 33

@changeset-bot

changeset-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 016e659

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@sl-design-system/radio-group Minor
@sl-design-system/checkbox Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds description and tooltip support to checkbox and radio components.

Changes:

  • Adds description properties, slots, styling, and ARIA relationships.
  • Adds scoped tooltip rendering.
  • Adds tests, stories, dependencies, and release metadata.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
.changeset/social-lies-tell.md Documents minor feature releases.
packages/components/checkbox/package.json Adds tooltip and scoped-elements dependencies.
packages/components/checkbox/src/checkbox-group.stories.ts Demonstrates group descriptions and tooltips.
packages/components/checkbox/src/checkbox.scss Styles description content.
packages/components/checkbox/src/checkbox.spec.ts Tests descriptions and tooltips.
packages/components/checkbox/src/checkbox.stories.ts Adds feature examples.
packages/components/checkbox/src/checkbox.ts Implements checkbox descriptions and tooltips.
packages/components/radio-group/package.json Adds tooltip and scoped-elements dependencies.
packages/components/radio-group/src/radio-group.stories.ts Demonstrates radio descriptions and tooltips.
packages/components/radio-group/src/radio.scss Styles description content.
packages/components/radio-group/src/radio.spec.ts Tests descriptions and tooltips.
packages/components/radio-group/src/radio.ts Implements radio descriptions and tooltips.
yarn.lock Locks added dependencies.
Suppressed comments (3)

packages/components/radio-group/src/radio.ts:342

  • This IDREF is set on an element in the shadow root while #description is in the host's light DOM, so attribute-based ID resolution cannot establish the intended accessible description across the tree boundary. It also only appends IDs, leaving stale references when descriptions are removed or replaced. Use wrapper.ariaDescribedByElements with the description element and track/remove the component-owned reference while preserving other descriptions.
        const describedBy = this.wrapper.getAttribute('aria-describedby');
        const ids = new Set(describedBy ? describedBy.split(' ') : []);
        ids.add(this.#description.id);
        this.wrapper.setAttribute('aria-describedby', Array.from(ids).join(' '));

packages/components/checkbox/src/checkbox.ts:456

  • If both the property and a slotted description are present, then the slotted element is removed, this branch is skipped because this.description is still truthy. #description consequently keeps pointing at the detached slotted element: the property fallback becomes visible, but the input is not validly described by it. Recreate the synthesized property-backed description when no assigned description remains.
    } else if (!this.description && this.#description) {

packages/components/checkbox/src/checkbox.ts:502

  • When a description is removed or replaced, this logic only adds the current ID and never removes the previous component-owned ID. Toggling description repeatedly therefore accumulates references to detached elements in the input's aria-describedby. Track the previously owned ID/reference and remove it during synchronization, without removing consumer-provided descriptions.
        const describedBy = this.input.getAttribute('aria-describedby');
        const ids = new Set(describedBy ? describedBy.split(' ') : []);
        ids.add(this.#description.id);
        this.input.setAttribute('aria-describedby', Array.from(ids).join(' '));

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/components/checkbox/src/checkbox.ts Outdated
Comment thread packages/components/radio-group/src/radio.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (3)

packages/components/checkbox/src/checkbox.ts:545

  • This attribute write replaces the ariaDescribedByElements list assembled above. As a result, consumer-provided descriptions preserved by ForwardAriaMixin and the shadow-root tooltip reference are discarded whenever an internal description is present. Keep these relationships in ariaDescribedByElements and remove only references owned by this component.
    if (this.#description?.id && this.input) {
      const describedBy = this.input.getAttribute('aria-describedby');
      const ids = new Set(describedBy ? describedBy.split(' ') : []);
      ids.add(this.#description.id);
      this.input.setAttribute('aria-describedby', Array.from(ids).join(' '));

packages/components/checkbox/src/checkbox.ts:556

  • A string aria-describedby ID on the light-DOM input cannot resolve this tooltip inside the checkbox shadow root, so the tooltip is not exposed as the input's accessible description. Add it through input.ariaDescribedByElements and remove that owned reference when tooltip is cleared; checking only for a current tooltip also leaves stale references behind.
      tooltip.id ||= `sl-checkbox-tooltip-${nextUniqueId++}`;
      const describedBy = this.input.getAttribute('aria-describedby');
      const ids = new Set(describedBy ? describedBy.split(' ') : []);
      ids.add(tooltip.id);
      this.input.setAttribute('aria-describedby', Array.from(ids).join(' '));

packages/components/radio-group/src/radio.ts:362

  • The radio wrapper is in the shadow root, while #description is a light-DOM node, so this string IDREF cannot resolve across the shadow boundary. Calling setAttribute also replaces any ariaDescribedByElements relation installed by the tooltip. Link the description through wrapper.ariaDescribedByElements instead, preserving the tooltip reference and removing the previously managed description when it changes.
        const describedBy = this.wrapper.getAttribute('aria-describedby');
        const ids = new Set(describedBy ? describedBy.split(' ') : []);
        ids.add(this.#description.id);
        this.wrapper.setAttribute('aria-describedby', Array.from(ids).join(' '));

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (3)

packages/components/checkbox/src/checkbox.ts:531

  • #syncAria() replaces the input's entire described-by list with only the component description. This drops consumer-provided aria-describedby references forwarded by ForwardAriaMixin, and it never associates the new tooltip with the actual checkbox input—the tooltip only describes the non-semantic wrapper. Manage the component-owned description/tooltip references while preserving existing input references, and assert the tooltip relation on el.input.
    this.input.ariaDescribedByElements = elements.length > 0 ? elements : null;

packages/components/checkbox/src/checkbox.ts:476

  • This permanently sets aria-hidden on a caller-owned slotted element. If the consumer later removes or changes its slot attribute, the element remains hidden from assistive technology in its new context. Preserve the prior value and restore it when this element stops being the active description.
      slottedDescription.id ||= `sl-checkbox-description-${nextUniqueId++}`;
      slottedDescription.setAttribute('aria-hidden', 'true');

packages/components/radio-group/src/radio.ts:316

  • This permanently sets aria-hidden on a caller-owned slotted element. If the consumer later removes or changes its slot attribute, the element remains hidden from assistive technology in its new context. Preserve the prior value and restore it when this element stops being the active description.
      slottedDescription.id ||= `sl-radio-description-${nextUniqueId++}`;
      slottedDescription.setAttribute('aria-hidden', 'true');

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (7)

packages/components/checkbox/src/checkbox.scss:203

  • These custom properties are not generated by the repository's token set, so font-size is discarded and the description inherits the label size while line-height always falls back to 16px. Use the available composite body-small typography token instead.
  font-size: var(--sl-text-size-body-sm);
  line-height: var(--sl-text-typeset-lineHeight-200, 16px);

packages/components/radio-group/src/radio.scss:152

  • These custom properties are not generated by the repository's token set, so font-size is discarded and the description inherits the label size while line-height always falls back to 16px. Use the available composite body-small typography token instead.
  font-size: var(--sl-text-size-body-sm);
  line-height: var(--sl-text-typeset-lineHeight-200, 16px);

packages/components/checkbox/src/checkbox.scss:138

  • --sl-text-size-body-md is not a generated token, so large checkboxes always use the hard-coded 14px fallback rather than the theme's body-medium size. Use the generated font-size token.
    font-size: var(--sl-text-size-body-md, 14px);

packages/components/radio-group/src/radio.scss:110

  • --sl-text-size-body-md is not a generated token, so large radios always use the hard-coded 14px fallback rather than the theme's body-medium size. Use the generated font-size token.
    font-size: var(--sl-text-size-body-md, 14px);

packages/components/checkbox/src/checkbox.ts:285

  • slotchange only fires when assigned nodes change, not when the text or descendants of an already assigned element mutate. A slotted description initialized empty and populated reactively therefore remains hidden by the has-description CSS (and clearing existing text leaves stale state). Observe assigned-content mutations and resync this attribute, including observer cleanup on disconnect.
            <slot name="description" @slotchange=${() => this.#onDescriptionSlotChange()}

packages/components/radio-group/src/radio.ts:218

  • slotchange only fires when assigned nodes change, not when the text or descendants of an already assigned element mutate. A slotted description initialized empty and populated reactively therefore remains hidden by the has-description CSS (and clearing existing text leaves stale state). Observe assigned-content mutations and resync this attribute, including observer cleanup on disconnect.
            <slot name="description" @slotchange=${() => this.#onDescriptionSlotChange()}

packages/components/checkbox/src/checkbox.spec.ts:220

  • This assertion only checks the wrapper relation that <sl-tooltip for="wrapper"> creates itself, so it does not exercise the new manual relationship to the actual checkbox input and would pass if that accessibility fix regressed. Assert the input relationship here instead.
      expect(wrapper?.ariaDescribedByElements).to.include(tooltip as HTMLElement);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

packages/components/checkbox/src/checkbox.ts:602

  • This relationship is not reapplied when #onInputSlotChange replaces this.input. The component explicitly supports a custom input being slotted after connection, but if a description or tooltip already exists, the new control receives neither reference and is announced without that description. Re-run this synchronization after switching the input target.
    this.input.ariaDescribedByElements = nextRefs.length > 0 ? nextRefs : null;

Comment thread packages/components/checkbox/src/checkbox.ts Outdated
Comment thread packages/components/radio-group/src/radio.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (5)

packages/components/checkbox/src/checkbox.ts:268

  • A whitespace-only property is truthy here, so has-description remains set even though #descriptionText() trims it to empty. This displays an empty description row and differs from the empty slotted-description behavior. Base the state solely on normalized text.
      this.toggleAttribute(
        'has-description',
        !!this.description || this.#descriptionText().length > 0
      );

packages/components/radio-group/src/radio.ts:212

  • A whitespace-only property is truthy here, so has-description remains set even though #descriptionText() trims it to empty. This displays an empty description row and differs from the empty slotted-description behavior. Base the state solely on normalized text.
      this.toggleAttribute(
        'has-description',
        !!this.description || this.#descriptionText().length > 0
      );

packages/components/checkbox/src/checkbox.ts:232

  • Disconnecting stops the observer but leaves a consumer-provided description with the component-owned aria-hidden="true". If that node is later moved out while the checkbox remains disconnected, it stays hidden from assistive technology. Restore its recorded value before disconnecting; reconnection will apply the component state again.

This issue also appears on line 265 of the same file.

  override disconnectedCallback(): void {
    this.#mutationObserver.disconnect();

    super.disconnectedCallback();

packages/components/radio-group/src/radio.ts:174

  • Disconnecting stops the observer but leaves a consumer-provided description with the component-owned aria-hidden="true". If that node is later moved out while the radio remains disconnected, it stays hidden from assistive technology. Restore its recorded value before disconnecting; reconnection will apply the component state again.

This issue also appears on line 209 of the same file.

  override disconnectedCallback(): void {
    this.#mutationObserver.disconnect();

    super.disconnectedCallback();

packages/components/checkbox/src/checkbox.spec.ts:245

  • This only verifies the tooltip's built-in for="wrapper" relation, so it would still pass if the checkbox input—the actual control—lost its accessible description. Assert el.input.ariaDescribedByElements to cover the new #syncAria() behavior.
      const wrapper = el.renderRoot.querySelector<HTMLElement>('#wrapper');
      expect(wrapper?.ariaDescribedByElements).to.include(tooltip as HTMLElement);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/components/checkbox/src/checkbox.ts:764

  • This unconditionally copies every ARIA property from the old input and then clears it. When a late custom input already has its own aria-label, aria-controls, or element-reference properties, null/empty values from the synthesized input overwrite them; when a custom input is removed for reuse, its own ARIA state is likewise stripped. Preserve consumer-owned ARIA on both inputs and transfer only state known to have been forwarded or generated by this component.
    forwardedAriaValueProperties.forEach(prop => {
      to[prop] = from[prop];
      from[prop] = null;
    });
    forwardedAriaElementProperties.forEach(prop => {

packages/components/checkbox/src/checkbox.ts:536

  • Switching the active input here leaves the FormControlMixin invalid listener attached to every previous input: setFormControlElement() only adds the listener, while disconnection removes it only from the current element (form-control-mixin.ts:303,519-522). If a removed custom input is reused, its invalid events are still prevented and update this checkbox's validity state. Detach the old form-control element before registering the replacement, preferably in setFormControlElement() itself.

This issue also appears on line 760 of the same file.

      this.setFormControlElement(this.input);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/components/checkbox/src/checkbox.ts:561

  • Restoring the fallback with captureFromOldInput set to false loses host-forwarded ARIA that was applied while the custom input was active (and all forwarded ARIA when the component started with a custom input). Those host attributes have already been removed by ForwardAriaMixin, while #forwardedAria*State only captures the synthesized input during the earlier switch, so setProxyTarget() cannot replay the current label/description/controls onto the fallback. Track the ARIA owned by the forwarding path as it changes and migrate that subset when restoring the fallback; simply capturing every property here would incorrectly steal consumer-owned ARIA from the custom input.
      this.#syncForwardedAria(this.input, input, false);

packages/components/form/src/form-control-mixin.ts:524

  • disconnectedCallback() removes the invalid listener but retains #formControlElement. When a control is reconnected, it calls this method with the same element, so this early return prevents the listener from being registered again and subsequent native invalid events no longer update the component's validity UI. Remove the shortcut (removing and re-adding the same listener is safe), or clear the stored element during disconnect.
      if (this.#formControlElement === element) {
        return;
      }

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/components/shared/src/mixins/forward-aria-mixin.ts:145

  • Retargeting preserves an attribute already owned by the new target, but the storage still treats that attribute as forwarded. For example, if the old target received aria-label="host" and the new target already has aria-label="custom", this branch preserves custom; a later host.removeAttribute('aria-label') nevertheless removes custom via the cleanup path. Element-reference properties have the same ownership loss when the new target already contains one of refs. Track only the values/references actually contributed to each target so later cleanup cannot remove consumer-owned ARIA.
      forwardedAttributesStorage.get(this)?.forEach((value, name) => {
        if (!to.hasAttribute(name)) {
          to.setAttribute(name, value);
        }

packages/components/checkbox/src/checkbox.ts:825

  • This independently cached ARIA state becomes stale while a custom input is active. For example: forward aria-label="A", switch to a custom input (capturing A), remove the host ARIA label, then remove the custom input; #applyForwardedAria replays A onto the fallback even though the host cleared it. Changed element references similarly accumulate obsolete values. Use ForwardAriaMixin's proxy retargeting as the source of truth, or update this cache whenever forwarded state changes or is removed.
  #applyForwardedAria(input: HTMLInputElement): void {
    forwardedAriaValueProperties.forEach(prop => {
      const value = this.#forwardedAriaValueState.get(prop);
      if (value !== undefined && input[prop] === null) {
        input[prop] = value;

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated no new comments.

Suppressed comments (3)

packages/components/shared/src/mixins/forward-aria-mixin.ts:430

  • When the forwarded IDs resolve to no elements, assigning [] replaces the target's entire reference list and deletes references owned by the target or another feature. This is a regression from the merge behavior: after removing this mixin's prior contribution, preserve any nonempty references that remain and only normalize to [] when the target has none.
            if (!elements.length) {
              (targetElement as unknown as Record<string, Element[] | null>)[elementsProp] = [];
              forwarded.set(elementsProp, elements);
              elementsForTarget(this, targetElement).delete(elementsProp);
              continue;

packages/components/shared/src/mixins/forward-aria-mixin.ts:246

  • This migration only replays forwardedElementsStorage, but attribute forwarding populates that storage only for plural properties. Consequently an aria-activedescendant forwarded through ariaActiveDescendantElement is neither copied to the new proxy nor removed from the old one. Track and migrate singular attribute-owned references as well.
      forwardedElementsStorage.get(this)?.forEach((refs, prop) => {
        const contributed = applyElementReference(to, prop, refs);

packages/components/shared/src/mixins/forward-aria-mixin.ts:281

  • Equality with the stored value does not establish ownership here. If the previous proxy already had consumer-owned aria-disabled="true" before the host's ariaDisabled property was forwarded, retargeting clears that consumer value. Record per-target ownership for ariaDisabled, as is done for other forwarded attributes, and only clean up values actually contributed by this mixin.
      if (ariaDisabledStorage.has(this)) {
        const value = ariaDisabledStorage.get(this) ?? null;
        if (to.ariaDisabled === null) {
          setAriaDisabled(to, value);
        }
        if (from.ariaDisabled === value) {
          setAriaDisabled(from, null);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/components/checkbox/src/checkbox.ts:491

  • Restoring the fallback input drops scalar ARIA attributes that were already forwarded to the removed custom input. ForwardAriaMixin removes attributes such as aria-label from the host after forwarding them, and setProxyTarget() cannot replay those plain attributes to this new input. For example, removing the input from <sl-checkbox aria-label="Terms"><input slot="input"></sl-checkbox> leaves the synthesized checkbox unnamed. Preserve/replay the forwarded ARIA state when changing proxy targets.
    } else if (!this.#isSynthesizedInput) {
      const customValidity = this.input.validity.customError ? this.input.validationMessage : '';
      const input = this.#synthesizedInput ?? this.#createInput();
      this.input = input;
      this.#syncInput(this.input, customValidity);

packages/components/checkbox/src/checkbox.ts:496

  • The removed custom input still retains the invalid listener installed by FormControlMixin. setFormControlElement() only adds the listener to the replacement and never detaches it from the previous control, so reusing that custom input elsewhere will still suppress its native validation UI and update this checkbox's validity state. Detach the old form-control listener when replacing the target.
      this.setFormControlElement(this.input);

@michal-sanoma

Copy link
Copy Markdown
Collaborator Author

For Checkbox group aria-describedby is not passed to input, and screen readers only read the label of checkbox without information from the tooltip.
This should work like it works in Radio group where the value for aria-describedby is added correctly:

Should be better now

Tooltips should be moved from being above/below each checkbox/radio to being on the right side of each checkbox/radio.

Now they are above/below:
When keyboard is used to go from checkbox Option 1 to Option 2 the box of Option 2 is being displayed on top of tooltip for Option 1 for a quick moment.
This will be fixed by changing tooltips placement to the right side, but may cause visual issues when CFA decide to have tooltips displayed below each option.
This is only for Checkbox Group and is not present for Radio Group

Fixed

@michal-sanoma
michal-sanoma requested a review from a11ymiko August 18, 2026 11:22
@a11ymiko

Copy link
Copy Markdown
Contributor

For Checkbox group aria-describedby is not passed to input, and screen readers only read the label of checkbox without information from the tooltip.

Image This should work like it works in Radio group where the value for `aria-describedby` is added correctly: Image

aria-describedby is added the correct way, but in Chrome and Edge (on both macOS and Win11) the description from the tooltip is not announced by VO or NVDA. Both screen readers correctly read tooltip's text in Firefox and Safari but fail to read it in Chrome and Edge.

I don't have idea why, because when I add reference to different text as aria-described then it's being announced. I've tried to change and delete tooltip's and checkbox's attributes to see if maybe there are some conflicts between them that cancel announcement of aria-describedby but that changed nothing.

I didn't find any active issue for problems of reading aria-describedby for checkboxes on Chrome issues page.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/components/radio-group/src/radio.ts:99

  • If the synthesized tooltip-description node is removed while tooltip remains unchanged, the mutation observer only resyncs labels/descriptions, so the visual tooltip survives while the radio's accessible description points at a detached element. Recreate the tooltip description during external child-mutation handling as well.
    this.#onLabelSlotChange();
    this.#onDescriptionSlotChange();

packages/components/checkbox/src/checkbox.ts:133

  • If the synthesized tooltip-description node is removed while tooltip remains unchanged (for example, by light-DOM reconciliation), this observer never recreates it. The visual tooltip continues to render, but the input keeps a detached description reference and loses its accessible tooltip text. Resync the tooltip node when external child mutations are handled, just as is done for the property-backed description.
    this.#onLabelSlotChange();
    this.#onDescriptionSlotChange();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Radio and checkbox] Description per item

3 participants