From ce83407e1debbe74f20d2d6dc2535a0ef3f974a0 Mon Sep 17 00:00:00 2001 From: OS-jacobbell <228905018+OS-jacobbell@users.noreply.github.com> Date: Thu, 12 Mar 2026 12:51:20 -0600 Subject: [PATCH] fix(checkbox): re-evaluate label visibility when label is updated (#30980) ## What is the current behavior? Checkbox's render function applies the `label-text-wrapper-hidden` css class when there is no label text to prevent extra margin from being added. The render function is not re-evaluated if the label is updated. This causes a problem in Angular where dynamic variables get applied after the page is loaded, and a checkbox using a variable as a label gets stuck with its label hidden until something else triggers a re-render, e.g. ticking the box. ## What is the new behavior? - The checkbox will be re-rendered, and css classes will be updated, when the label text is changed. - Updated tests to check that the label is visible after changing from blank to having content. ## Does this introduce a breaking change? - [ ] Yes - [X] No --- core/src/components/checkbox/checkbox.tsx | 13 +++++++++---- .../e2e/src/standalone/value-accessors.spec.ts | 14 ++++++++++---- .../checkbox/checkbox.component.html | 3 ++- .../value-accessors/checkbox/checkbox.component.ts | 5 +++++ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/core/src/components/checkbox/checkbox.tsx b/core/src/components/checkbox/checkbox.tsx index abd289c263..8a25b9200d 100644 --- a/core/src/components/checkbox/checkbox.tsx +++ b/core/src/components/checkbox/checkbox.tsx @@ -127,6 +127,8 @@ export class Checkbox implements ComponentInterface { */ @State() isInvalid = false; + @State() private hasLabelContent = false; + @State() private hintTextId?: string; /** @@ -265,6 +267,10 @@ export class Checkbox implements ComponentInterface { ev.stopPropagation(); }; + private onSlotChange = () => { + this.hasLabelContent = this.el.textContent !== ''; + }; + private getHintTextId(): string | undefined { const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this; @@ -326,7 +332,6 @@ export class Checkbox implements ComponentInterface { } = this; const mode = getIonMode(this); const path = getSVGPath(mode, indeterminate); - const hasLabelContent = el.textContent !== ''; renderHiddenInput(true, el, name, checked ? value : '', disabled); @@ -338,7 +343,7 @@ export class Checkbox implements ComponentInterface { aria-checked={indeterminate ? 'mixed' : `${checked}`} aria-describedby={this.hintTextId} aria-invalid={this.isInvalid ? 'true' : undefined} - aria-labelledby={hasLabelContent ? this.inputLabelId : null} + aria-labelledby={this.hasLabelContent ? this.inputLabelId : null} aria-label={inheritedAttributes['aria-label'] || null} aria-disabled={disabled ? 'true' : null} aria-required={required ? 'true' : undefined} @@ -376,13 +381,13 @@ export class Checkbox implements ComponentInterface {
- + {this.renderHintText()}
diff --git a/packages/angular/test/base/e2e/src/standalone/value-accessors.spec.ts b/packages/angular/test/base/e2e/src/standalone/value-accessors.spec.ts index 44a85cadf1..dc4c7b92d4 100644 --- a/packages/angular/test/base/e2e/src/standalone/value-accessors.spec.ts +++ b/packages/angular/test/base/e2e/src/standalone/value-accessors.spec.ts @@ -8,13 +8,19 @@ test.describe('Value Accessors', () => { test('should update the form value', async ({ page }) => { await expect(page.locator('#formValue')).toHaveText(JSON.stringify({ checkbox: false }, null, 2)); - await expect(page.locator('ion-checkbox')).toHaveClass(/ion-pristine/); + await expect(page.getByRole('checkbox', { name: 'Static Checkbox Label' })).toHaveClass(/ion-pristine/); - await page.locator('ion-checkbox').click(); + await page.getByRole('checkbox', { name: 'Static Checkbox Label' }).click(); await expect(page.locator('#formValue')).toHaveText(JSON.stringify({ checkbox: true }, null, 2)); - await expect(page.locator('ion-checkbox')).toHaveClass(/ion-dirty/); - await expect(page.locator('ion-checkbox')).toHaveClass(/ion-valid/); + await expect(page.getByRole('checkbox', { name: 'Static Checkbox Label' })).toHaveClass(/ion-dirty/); + await expect(page.getByRole('checkbox', { name: 'Static Checkbox Label' })).toHaveClass(/ion-valid/); + + await expect(page.getByRole('checkbox', { name: 'Static Checkbox Label' })).toBeVisible(); + }); + + test('should display dynamically set label', async ({ page }) => { + await expect(page.getByRole('checkbox', { name: 'Dynamic Checkbox Label' })).toBeVisible(); }); }); diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.html b/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.html index c121dab37a..0f264add5b 100644 --- a/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.html +++ b/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.html @@ -6,6 +6,7 @@

- Checkbox + Static Checkbox Label + {{dynamicLabel}}
diff --git a/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.ts b/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.ts index d58672d954..edf8d16e37 100644 --- a/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.ts +++ b/packages/angular/test/base/src/app/standalone/value-accessors/checkbox/checkbox.component.ts @@ -15,6 +15,11 @@ import { ValueAccessorTestComponent } from "../value-accessor-test/value-accesso ] }) export class CheckboxComponent { + dynamicLabel = ''; + + ngAfterViewInit(): void { + this.dynamicLabel = 'Dynamic Checkbox Label'; + } form = this.fb.group({ checkbox: [false, Validators.required],