Skip to content

fix(ui5-button): announce accessible name #11396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions packages/main/cypress/specs/Button.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import download from "@ui5/webcomponents-icons/dist/download.js";
import employee from "@ui5/webcomponents-icons/dist/employee.js";

import {
BUTTON_ARIA_TYPE_REJECT,
BUTTON_ARIA_TYPE_EMPHASIZED,
} from "../../src/generated/i18n/i18n-defaults.js";

describe("Button general interaction", () => {
Expand Down Expand Up @@ -304,28 +304,47 @@ describe("Accessibility", () => {
.should("have.attr", "role", "button");
});

it("aria-describedby properly applied on the button tag", () => {
cy.mount(<Button design="Attention">Content</Button>);
it("accessibleDescription in combination with design property applied on the button tag", () => {
cy.mount(<Button design="Negative" accessibleDescription="Decline">Content</Button>);

cy.get("[ui5-button]")
.shadow()
.find("button")
.as("button");

cy.get("@button")
.shadow()
.find("button")
.should("have.attr", "aria-description", "Warning");
.should("have.attr", "aria-description", "Decline");
});

it("accessibleDescription in combination with design property applied on the button tag", () => {
cy.mount(<Button design="Negative" accessibleDescription="Decline">Content</Button>);

it("accessibleName when the button has a ui5-button-badge with more than 1 items", () => {
cy.mount(
<Button accessibleName="Download">
<ButtonBadge design="OverlayText" text="999+" slot="badge"></ButtonBadge>
</Button>
);
cy.get("[ui5-button]")
.shadow()
.find("button")
.as("button");

cy.get("@button")
.should("have.attr", "aria-label", `Download 999+ items`);
});

it("accessibleName when the button has a ui5-button-badge with 1 item", () => {
cy.mount(
<Button design="Emphasized" accessibleName="Download">
<ButtonBadge text="1" design="InlineText" slot="badge"></ButtonBadge>
</Button>
);
cy.get("[ui5-button]")
.shadow()
.find("button")
.should("have.attr", "aria-description", `${BUTTON_ARIA_TYPE_REJECT.defaultText} Decline`);
.as("button");

cy.get("@button")
.should("have.attr", "aria-label", `Download ${BUTTON_ARIA_TYPE_EMPHASIZED.defaultText} 1 item`);
});

it("setting accessible-name-ref on the host is reflected on the button tag", () => {
Expand Down
33 changes: 28 additions & 5 deletions packages/main/src/Button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
BUTTON_ARIA_TYPE_REJECT,
BUTTON_ARIA_TYPE_EMPHASIZED,
BUTTON_ARIA_TYPE_ATTENTION,
BUTTON_BADGE_ONE_ITEM,
BUTTON_BADGE_MANY_ITEMS,
} from "./generated/i18n/i18n-defaults.js";

// Styles
Expand Down Expand Up @@ -522,15 +524,36 @@ class Button extends UI5Element implements IButton {
}

get ariaLabelText() {
return getEffectiveAriaLabelText(this);
const ariaLabelText = getEffectiveAriaLabelText(this) || "";
const typeLabelText = this.hasButtonType ? this.buttonTypeText : "";
const internalLabelText = this.effectiveBadgeDescriptionText || "";

const labelParts = [ariaLabelText, typeLabelText, internalLabelText].filter(part => part);
return labelParts.join(" ");
}

get ariaDescriptionText() {
const ariaDescribedByText = this.hasButtonType ? this.buttonTypeText : "";
const accessibleDescription = this.accessibleDescription || "";
const ariaDescriptionText = `${ariaDescribedByText} ${accessibleDescription}`.trim();
return this.accessibleDescription === "" ? undefined : this.accessibleDescription;
}

return ariaDescriptionText || undefined;
get effectiveBadgeDescriptionText() {
if (!this.shouldRenderBadge) {
return "";
}

const badgeEffectiveText = this.badge[0].effectiveText;

// Use distinct i18n keys for singular and plural badge values to ensure proper localization.
// Some languages have different grammatical rules for singular and plural forms,
// so separate keys (BUTTON_BADGE_ONE_ITEM and BUTTON_BADGE_MANY_ITEMS) are necessary.
switch (badgeEffectiveText) {
case "":
return badgeEffectiveText;
case "1":
return Button.i18nBundle.getText(BUTTON_BADGE_ONE_ITEM, badgeEffectiveText);
default:
return Button.i18nBundle.getText(BUTTON_BADGE_MANY_ITEMS, badgeEffectiveText);
}
}

get _isSubmit() {
Expand Down
6 changes: 6 additions & 0 deletions packages/main/src/i18n/messagebundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ BUTTON_ARIA_TYPE_EMPHASIZED=Emphasized
#XACT: ARIA announcement for the warning button
BUTTON_ARIA_TYPE_ATTENTION=Warning

#XACT: ARIA announcement for the Button Badge with value 1 - "{0} item"
BUTTON_BADGE_ONE_ITEM={0} item

#XACT: ARIA announcement for the Button Badge with value more than 1 - "{0} items"
BUTTON_BADGE_MANY_ITEMS={0} items

#XACT: Text for Today item in CalendarLegend
CAL_LEGEND_TODAY_TEXT=Today

Expand Down
Loading