Repository: grommet/grommet
Pull request #7571: Tag - add aria label for prop to remove button
Author: login: britt6612
Created: 2025-04-03T03:00:47Z | Merged: 2025-04-11T20:11:33Z

<!--- Provide a general summary of the PR in the Title above -->

#### What does this PR do?
adds a prop for `aria-label` to be passed in
#### Where should the reviewer start?
tag.js
#### What testing has been done on this PR?
locally
#### How should this be manually tested?
locally
#### Do Jest tests follow these best practices?
I didnt add one since it got added to other snapshots
- [ ] `screen` is used for querying.
- [ ] The correct query is used. (Refer to [this list of queries](https://testing-library.com/docs/queries/about/#priority))
- [ ] `asFragment()` is used for snapshot testing.

#### Any background context you want to provide?
On the Tag component there is no way for the implementer to provide an aria-label
#### What are the relevant issues?
closes #7555 
#### Screenshots (if appropriate)

#### Do the grommet docs need to be updated?
yes
#### Should this PR be mentioned in the release notes?
yes
#### Is this change backwards compatible or is it a breaking change?
backwards compatible

Linked issue #7555: Tag - close button aria-label
On the Tag component there is no aria-label on the close icon button and no way for the implementer to provide one.

This relates to WCAG rule 1.1.1 and 1.3.6
Linked issue comments: user: login: MikeKingdom
created_at: 2025-03-26T15:39:34Z
body: The svg child of the button has `aria-label="Close"` Wouldn't that support the screen reader? If not I would think we have a common problem where most icon buttons have an issue.
author_association: COLLABORATOR
user: login: britt6612
created_at: 2025-03-26T15:52:57Z
body: think I was also along the same thoughts here 

https://github.com/grommet/hpe-design-system/pull/4959#discussion_r2012990790

maybe just an enhancement for users to pass their own?
author_association: COLLABORATOR
user: login: MikeKingdom
created_at: 2025-03-26T18:51:44Z
body: Good question. Might need some more thought here. I just realized there's not really a way to localize these either in case where we have embedded components/icons which have some form of aria label built in
author_association: COLLABORATOR
user: login: jcfilben
created_at: 2025-03-27T20:30:26Z
body: Switching this from bug to enhancement. Feels like its not a wcag violation but there is room for improvement here
author_association: COLLABORATOR

Diff:
diff --git a/package.json b/package.json
index 610f489698f..a20adbfe94e 100644
--- a/package.json
+++ b/package.json
@@ -213,7 +213,7 @@
   "bundlesize": [
     {
       "path": "./dist/grommet.min.js",
-      "maxSize": "176 kB"
+      "maxSize": "177 kB"
     }
   ],
   "keywords": [
diff --git a/src/js/components/Grommet/index.d.ts b/src/js/components/Grommet/index.d.ts
index 318e1cd42ed..6547a04ab43 100644
--- a/src/js/components/Grommet/index.d.ts
+++ b/src/js/components/Grommet/index.d.ts
@@ -146,6 +146,12 @@ export interface GrommetProps {
         lower?: string;
         upper?: string;
       };
+      tag?: {
+        removeLabel?: {
+          nameAndValue?: string;
+          valueOnly?: string;
+        };
+      };
       select?: {
         multiple?: string;
         selected?: string;
diff --git a/src/js/components/Grommet/propTypes.js b/src/js/components/Grommet/propTypes.js
index 35df8c40556..33649f8d2bd 100644
--- a/src/js/components/Grommet/propTypes.js
+++ b/src/js/components/Grommet/propTypes.js
@@ -171,6 +171,12 @@ if (process.env.NODE_ENV !== 'production') {
         tabs: PropTypes.shape({
           tabContents: PropTypes.string,
         }),
+        tag: PropTypes.shape({
+          removeLabel: PropTypes.shape({
+            nameAndValue: PropTypes.string,
+            valueOnly: PropTypes.string,
+          }),
+        }),
         textInput: PropTypes.shape({
           enterSelect: PropTypes.string,
           suggestionsCount: PropTypes.string,
diff --git a/src/js/components/Tag/Tag.js b/src/js/components/Tag/Tag.js
index b74d31131cb..24a2c084ca7 100644
--- a/src/js/components/Tag/Tag.js
+++ b/src/js/components/Tag/Tag.js
@@ -1,4 +1,4 @@
-import React, { forwardRef } from 'react';
+import React, { useContext, forwardRef } from 'react';
 import { FormClose } from 'grommet-icons/icons/FormClose';
 
 import { TagPropTypes } from './propTypes';
@@ -7,12 +7,27 @@ import { Text } from '../Text';
 
 import { StyledRemoveButton, StyledTagButton } from './StyledTag';
 import { useThemeValue } from '../../utils/useThemeValue';
+import { MessageContext } from '../../contexts/MessageContext';
 
 const Tag = forwardRef(
-  ({ name, value, size = 'medium', onRemove, onClick, ...rest }, ref) => {
+  (
+    { name, value, size = 'medium', onRemove, onClick, messages, ...rest },
+    ref,
+  ) => {
+    const { format } = useContext(MessageContext);
     const { theme, passThemeFlag } = useThemeValue();
     const RemoveIcon = theme.tag.icons?.remove || FormClose;
 
+    const removeLabelId = name
+      ? 'tag.removeLabel.nameAndValue'
+      : 'tag.removeLabel.valueOnly';
+
+    const removeLabel = format({
+      id: removeLabelId,
+      messages,
+      values: { name, value },
+    });
+
     const containerProps = {
       ref,
       align: 'center',
@@ -67,6 +82,7 @@ const Tag = forwardRef(
           <StyledRemoveButton
             onClick={onRemove}
             {...removeProps}
+            aria-label={removeLabel}
             icon={<RemoveIcon {...theme.tag.size?.[size]?.icon} />}
             round={theme.tag.size?.[size]?.round || theme.tag.round}
             {...theme.tag.remove}
diff --git a/src/js/components/Tag/__tests__/Tag-test.tsx b/src/js/components/Tag/__tests__/Tag-test.tsx
index 5cf0e14b021..8ae124465f6 100644
--- a/src/js/components/Tag/__tests__/Tag-test.tsx
+++ b/src/js/components/Tag/__tests__/Tag-test.tsx
@@ -169,4 +169,70 @@ describe('Tag', () => {
     );
     expect(asFragment()).toMatchSnapshot();
   });
+
+  test('renders aria-label from messages (name and value)', () => {
+    const messages = {
+      removeLabel: {
+        nameAndValue: 'Remove {name}: {value}',
+      },
+    };
+
+    render(
+      <Grommet>
+        <Tag
+          name="Category"
+          value="Fruits"
+          onRemove={() => {}}
+          messages={messages}
+        />
+      </Grommet>,
+    );
+
+    expect(screen.getByRole('button')).toHaveAttribute(
+      'aria-label',
+      'Remove Category: Fruits',
+    );
+  });
+
+  test('renders aria-label from grommet messages (name and value)', () => {
+    render(
+      <Grommet
+        messages={{
+          messages: {
+            tag: {
+              removeLabel: {
+                nameAndValue: 'Remove {name}: {value}',
+              },
+            },
+          },
+        }}
+      >
+        <Tag name="Category" value="Fruits" onRemove={() => {}} />
+      </Grommet>,
+    );
+
+    expect(screen.getByRole('button')).toHaveAttribute(
+      'aria-label',
+      'Remove Category: Fruits',
+    );
+  });
+
+  test('renders aria-label from messages (value only)', () => {
+    const messages = {
+      removeLabel: {
+        valueOnly: 'Remove {value}',
+      },
+    };
+
+    render(
+      <Grommet>
+        <Tag value="Fruits" onRemove={() => {}} messages={messages} />
+      </Grommet>,
+    );
+
+    expect(screen.getByRole('button')).toHaveAttribute(
+      'aria-label',
+      'Remove Fruits',
+    );
+  });
 });
diff --git a/src/js/components/Tag/__tests__/__snapshots__/Tag-test.tsx.snap b/src/js/components/Tag/__tests__/__snapshots__/Tag-test.tsx.snap
index 3cb519ed6f0..f589a7a3d0b 100644
--- a/src/js/components/Tag/__tests__/__snapshots__/Tag-test.tsx.snap
+++ b/src/js/components/Tag/__tests__/__snapshots__/Tag-test.tsx.snap
@@ -551,6 +551,7 @@ exports[`Tag onRemove 1`] = `
       </span>
     </div>
     <button
+      aria-label="Remove Test"
       class="c5 c6"
       type="button"
     >
@@ -1062,6 +1063,7 @@ exports[`Tag renders custom remove button kind and size 1`] = `
         </span>
       </div>
       <button
+        aria-label="Remove Value"
         class="c5 c6"
         type="button"
       >
@@ -1096,6 +1098,7 @@ exports[`Tag renders custom remove button kind and size 1`] = `
         </span>
       </div>
       <button
+        aria-label="Remove Value"
         class="c5 c6"
         type="button"
       >
@@ -1130,6 +1133,7 @@ exports[`Tag renders custom remove button kind and size 1`] = `
         </span>
       </div>
       <button
+        aria-label="Remove Value"
         class="c11 c6"
         type="button"
       >
@@ -1164,6 +1168,7 @@ exports[`Tag renders custom remove button kind and size 1`] = `
         </span>
       </div>
       <button
+        aria-label="Remove Value"
         class="c16 c6"
         type="button"
       >
@@ -1355,6 +1360,7 @@ exports[`Tag renders custom remove icon 1`] = `
       </span>
     </div>
     <button
+      aria-label="Remove Value"
       class="c5 c6"
       type="button"
     >
@@ -1556,6 +1562,7 @@ exports[`Tag renders default remove icon 1`] = `
       </span>
     </div>
     <button
+      aria-label="Remove Value"
       class="c4 c5"
       type="button"
     >
diff --git a/src/js/components/Tag/index.d.ts b/src/js/components/Tag/index.d.ts
index f1e3aeff46c..6e4029815bf 100644
--- a/src/js/components/Tag/index.d.ts
+++ b/src/js/components/Tag/index.d.ts
@@ -5,6 +5,12 @@ export interface TagProps {
   name?: string;
   onClick?: (...args: any[]) => any;
   onRemove?: (...args: any[]) => any;
+  messages?: {
+    removeLabel?: {
+      nameAndValue?: string;
+      valueOnly?: string;
+    };
+  };
   size?:
     | 'xsmall'
     | 'small'
diff --git a/src/js/components/Tag/propTypes.js b/src/js/components/Tag/propTypes.js
index 1fbd3dcf46f..5eb0a30f274 100644
--- a/src/js/components/Tag/propTypes.js
+++ b/src/js/components/Tag/propTypes.js
@@ -7,6 +7,12 @@ if (process.env.NODE_ENV !== 'production') {
     value: PropTypes.string.isRequired,
     onClick: PropTypes.func,
     onRemove: PropTypes.func,
+    messages: PropTypes.shape({
+      removeLabel: PropTypes.shape({
+        nameAndValue: PropTypes.string,
+        valueOnly: PropTypes.string,
+      }),
+    }),
     size: PropTypes.oneOfType([
       PropTypes.oneOf(['xsmall', 'small', 'medium', 'large', 'xlarge']),
       PropTypes.string,
diff --git a/src/js/components/__tests__/__snapshots__/components-test.js.snap b/src/js/components/__tests__/__snapshots__/components-test.js.snap
index 8cac117e839..de4e8814733 100644
--- a/src/js/components/__tests__/__snapshots__/components-test.js.snap
+++ b/src/js/components/__tests__/__snapshots__/components-test.js.snap
@@ -1037,6 +1037,7 @@ exports[`Components loads 1`] = `
   "Tag": {
     "$$typeof": Symbol(react.forward_ref),
     "prototype": {
+      "messages": [Function],
       "name": [Function],
       "onClick": [Function],
       "onRemove": [Function],
diff --git a/src/js/languages/default.json b/src/js/languages/default.json
index 3d72c3a5724..cdffa436dd5 100644
--- a/src/js/languages/default.json
+++ b/src/js/languages/default.json
@@ -161,6 +161,12 @@
   "tabs": {
     "tabContents": "Tab Contents"
   },
+  "tag": {
+    "removeLabel": {
+      "nameAndValue": "Remove {name}: {value}",
+      "valueOnly": "Remove {value}"
+    }
+  },
   "textInput": {
     "enterSelect": "(Press Enter to Select)",
     "suggestionsCount": "suggestions available",


Reviews:
user: login: taysea
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T17:18:11Z
user: login: taysea
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T17:18:33Z
user: login: taysea
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T17:19:07Z
user: login: britt6612
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T17:37:17Z
user: login: MikeKingdom
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T17:51:50Z
user: login: MikeKingdom
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T18:01:14Z
user: login: MikeKingdom
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T22:15:41Z
user: login: britt6612
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T22:18:37Z
user: login: britt6612
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T22:59:03Z
user: login: taysea
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T20:34:40Z
user: login: britt6612
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T21:19:16Z
user: login: taysea
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T22:25:08Z
user: login: taysea
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T22:26:08Z
user: login: taysea
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T22:26:38Z
user: login: britt6612
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T17:03:36Z
user: login: britt6612
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T18:16:43Z
user: login: taysea
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T18:49:06Z
user: login: MikeKingdom
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T19:14:43Z
user: login: MikeKingdom
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T19:19:27Z
user: login: MikeKingdom
body: Thanks Brittany. Looks like it needs some types updates.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-08T14:27:56Z
user: login: britt6612
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-08T14:59:38Z
user: login: MikeKingdom
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-08T17:45:46Z
user: login: taysea
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-08T21:34:32Z
user: login: jcfilben
body: Looks good!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-08T21:50:27Z

Review comments:
diff_hunk: @@ -9,7 +9,10 @@ import { StyledRemoveButton, StyledTagButton } from './StyledTag';
 import { useThemeValue } from '../../utils/useThemeValue';
 
 const Tag = forwardRef(
-  ({ name, value, size = 'medium', onRemove, onClick, ...rest }, ref) => {
+  (
+    { name, value, size = 'medium', onRemove, onClick, ariaLabel, ...rest },
path: src/js/components/Tag/Tag.js
user: login: taysea
body: This approach seems like it would apply to the tag, not the subcomponent. Wondering if we should support

```
removeProps= { 'aria-label': '' }
```

For now I'd say we only let aria-label be the prop in there.
created_at: 2025-04-03T17:18:11Z
author_association: COLLABORATOR
diff_hunk: @@ -66,6 +69,7 @@ const Tag = forwardRef(
         {onRemove && (
           <StyledRemoveButton
             onClick={onRemove}
+            aria-label={ariaLabel || 'Remove tag'}
path: src/js/components/Tag/Tag.js
user: login: taysea
body: If we add "Remove tag" as a string, we should include it in default.json message and allow for internationalization.
created_at: 2025-04-03T17:18:33Z
author_association: COLLABORATOR
diff_hunk: @@ -66,6 +69,7 @@ const Tag = forwardRef(
         {onRemove && (
           <StyledRemoveButton
             onClick={onRemove}
+            aria-label={ariaLabel || 'Remove tag'}
path: src/js/components/Tag/Tag.js
user: login: taysea
body: I also wonder if instead of "Remove tag" as the fallback if it should be `Remove ${name} ${value}`
created_at: 2025-04-03T17:19:06Z
author_association: COLLABORATOR
diff_hunk: @@ -66,6 +69,7 @@ const Tag = forwardRef(
         {onRemove && (
           <StyledRemoveButton
             onClick={onRemove}
+            aria-label={ariaLabel || 'Remove tag'}
path: src/js/components/Tag/Tag.js
user: login: britt6612
body: good thinking yeah I can do that 
created_at: 2025-04-03T17:37:17Z
author_association: COLLABORATOR
diff_hunk: @@ -9,7 +9,10 @@ import { StyledRemoveButton, StyledTagButton } from './StyledTag';
 import { useThemeValue } from '../../utils/useThemeValue';
 
 const Tag = forwardRef(
-  ({ name, value, size = 'medium', onRemove, onClick, ...rest }, ref) => {
+  (
+    { name, value, size = 'medium', onRemove, onClick, ariaLabel, ...rest },
path: src/js/components/Tag/Tag.js
user: login: MikeKingdom
body: I would agree with this and in fact given how `rest` is put on the container you'd potentially break existing usage of ariaLabel by apps.
created_at: 2025-04-03T17:51:49Z
author_association: COLLABORATOR
diff_hunk: @@ -66,6 +69,7 @@ const Tag = forwardRef(
         {onRemove && (
           <StyledRemoveButton
             onClick={onRemove}
+            aria-label={ariaLabel || 'Remove tag'}
path: src/js/components/Tag/Tag.js
user: login: MikeKingdom
body: Note that to properly localize this it would be no `$` in default.json and the format call would be passed the values:
In default.json
```json
"tag": {
  "removeLabel": "Remove {name} {value}"
},
```
And in Tag.js something like:
```jsx
format({
  id: 'tag.removeLabel',
  messages,
  values: {
    name,
    value
  },
})
```
And you have to document it for Grommet's index.d.ts/prop-types.

Typically we've also added a messages prop to the component so that it can be locally set/overridden (not just globally set in Grommet.) Not sure that's always required.
created_at: 2025-04-03T18:01:13Z
author_association: COLLABORATOR
diff_hunk: @@ -47,7 +67,7 @@ const Tag = forwardRef(
       console.warn('Tag cannot combine "onClick" and "onRemove".');
     }
 
-    const removeProps = !theme.tag.remove.kind
+    const computedRemoveProps = !theme.tag.remove.kind
path: src/js/components/Tag/Tag.js
user: login: MikeKingdom
body: seems like  better name for this would have "theme" in it.
created_at: 2025-04-03T22:15:41Z
author_association: COLLABORATOR
diff_hunk: @@ -47,7 +67,7 @@ const Tag = forwardRef(
       console.warn('Tag cannot combine "onClick" and "onRemove".');
     }
 
-    const removeProps = !theme.tag.remove.kind
+    const computedRemoveProps = !theme.tag.remove.kind
path: src/js/components/Tag/Tag.js
user: login: britt6612
body: yeah I was wondering on the naming thank you!
created_at: 2025-04-03T22:18:37Z
author_association: COLLABORATOR
diff_hunk: @@ -47,7 +67,7 @@ const Tag = forwardRef(
       console.warn('Tag cannot combine "onClick" and "onRemove".');
     }
 
-    const removeProps = !theme.tag.remove.kind
+    const computedRemoveProps = !theme.tag.remove.kind
path: src/js/components/Tag/Tag.js
user: login: britt6612
body: I changed to `themeTagRemoveProps`
created_at: 2025-04-03T22:59:03Z
author_association: COLLABORATOR
diff_hunk: @@ -7,12 +7,32 @@ import { Text } from '../Text';
 
 import { StyledRemoveButton, StyledTagButton } from './StyledTag';
 import { useThemeValue } from '../../utils/useThemeValue';
+import { MessageContext } from '../../contexts/MessageContext';
 
 const Tag = forwardRef(
-  ({ name, value, size = 'medium', onRemove, onClick, ...rest }, ref) => {
+  (
+    {
+      name,
+      value,
+      size = 'medium',
+      onRemove,
+      onClick,
+      messages,
+      removeProps = { 'aria-label': '' },
path: src/js/components/Tag/Tag.js
user: login: taysea
body: ```suggestion
```
Do we even need this anymore since it's handled my `messages`?
created_at: 2025-04-04T20:34:40Z
author_association: COLLABORATOR
diff_hunk: @@ -7,12 +7,32 @@ import { Text } from '../Text';
 
 import { StyledRemoveButton, StyledTagButton } from './StyledTag';
 import { useThemeValue } from '../../utils/useThemeValue';
+import { MessageContext } from '../../contexts/MessageContext';
 
 const Tag = forwardRef(
-  ({ name, value, size = 'medium', onRemove, onClick, ...rest }, ref) => {
+  (
+    {
+      name,
+      value,
+      size = 'medium',
+      onRemove,
+      onClick,
+      messages,
+      removeProps = { 'aria-label': '' },
path: src/js/components/Tag/Tag.js
user: login: britt6612
body: aw yes good catch
created_at: 2025-04-04T21:19:16Z
author_association: COLLABORATOR
diff_hunk: @@ -47,7 +66,7 @@ const Tag = forwardRef(
       console.warn('Tag cannot combine "onClick" and "onRemove".');
     }
 
-    const removeProps = !theme.tag.remove.kind
+    const themeTagRemoveProps = !theme.tag.remove.kind
path: src/js/components/Tag/Tag.js
user: login: taysea
body: I don't think we need to rename this anymore
created_at: 2025-04-04T22:25:08Z
author_association: COLLABORATOR
diff_hunk: @@ -66,7 +85,9 @@ const Tag = forwardRef(
         {onRemove && (
           <StyledRemoveButton
             onClick={onRemove}
+            {...themeTagRemoveProps}
             {...removeProps}
path: src/js/components/Tag/Tag.js
user: login: taysea
body: Related to above comment, but `removeProps` was renamed and now no longer exists. Getting an error locally for missing variable.

I'd say we don't even need to renamed `removeProps` anymore, but can delete "themeTagRemoveProps"
created_at: 2025-04-04T22:26:08Z
author_association: COLLABORATOR
diff_hunk: @@ -551,6 +551,7 @@ exports[`Tag onRemove 1`] = `
       </span>
     </div>
     <button
+      aria-label="Remove undefined Test"
path: src/js/components/Tag/__tests__/__snapshots__/Tag-test.tsx.snap
user: login: taysea
body: If name doesn't exist, I wouldn't expect it to include "undefined" in the aria-label. I'd expect this to read "Remove Test"
created_at: 2025-04-04T22:26:38Z
author_association: COLLABORATOR
diff_hunk: @@ -47,7 +66,7 @@ const Tag = forwardRef(
       console.warn('Tag cannot combine "onClick" and "onRemove".');
     }
 
-    const removeProps = !theme.tag.remove.kind
+    const themeTagRemoveProps = !theme.tag.remove.kind
path: src/js/components/Tag/Tag.js
user: login: britt6612
body: aw yes will revert
created_at: 2025-04-07T17:03:36Z
author_association: COLLABORATOR
diff_hunk: @@ -551,6 +551,7 @@ exports[`Tag onRemove 1`] = `
       </span>
     </div>
     <button
+      aria-label="Remove undefined Test"
path: src/js/components/Tag/__tests__/__snapshots__/Tag-test.tsx.snap
user: login: britt6612
body: I added a check in our messages context for this 
created_at: 2025-04-07T18:16:42Z
author_association: COLLABORATOR
diff_hunk: @@ -400,7 +400,7 @@ exports[`Data controlled search 1`] = `
     <span
       class="c11"
     >
-      4 results of undefined items
+      4 results of  items
path: src/js/components/Data/__tests__/__snapshots__/Data-test.tsx.snap
user: login: taysea
body: This read a little odd. It feels like ideally if the total wasn't provided it'd read either:
- "4 results" OR
- "4 results of unknown total items" (or something along those lines)

@MikeKingdom you're more expert in localization -- is this something we should worry about or accept this change?
created_at: 2025-04-07T18:49:06Z
author_association: COLLABORATOR
diff_hunk: @@ -400,7 +400,7 @@ exports[`Data controlled search 1`] = `
     <span
       class="c11"
     >
-      4 results of undefined items
+      4 results of  items
path: src/js/components/Data/__tests__/__snapshots__/Data-test.tsx.snap
user: login: MikeKingdom
body: Yeah. In general we should never put "undefined" or "null" in a message. But we should also make sure we're not creating a message that isn't "friendly". Typically in this situation the guidance is if you don't know the total then only say "4 results".
created_at: 2025-04-07T19:14:43Z
author_association: COLLABORATOR
diff_hunk: @@ -161,6 +161,9 @@
   "tabs": {
     "tabContents": "Tab Contents"
   },
+  "tag": {
+    "removeLabel": "Remove {name} {value}"
path: src/js/languages/default.json
user: login: MikeKingdom
body: This probably is a bit simplistic. I think we probably need 3 variations of this message based on whether name or value is defined or both. And the ':' is there in the visible rendering for a reason. Without it these can get confusing.
created_at: 2025-04-07T19:19:27Z
author_association: COLLABORATOR
diff_hunk: @@ -551,6 +551,7 @@ exports[`Tag onRemove 1`] = `
       </span>
     </div>
     <button
+      aria-label="Remove Test"
path: src/js/components/Tag/__tests__/__snapshots__/Tag-test.tsx.snap
user: login: MikeKingdom
body: We should probably have a test case for the other 2 variations
created_at: 2025-04-08T14:24:28Z
author_association: COLLABORATOR
diff_hunk: @@ -5,6 +5,9 @@ export interface TagProps {
   name?: string;
   onClick?: (...args: any[]) => any;
   onRemove?: (...args: any[]) => any;
+  messages?: {
+    removeLabel?: string;
path: src/js/components/Tag/index.d.ts
user: login: MikeKingdom
body: this needs to be updated for the new keys
created_at: 2025-04-08T14:26:46Z
author_association: COLLABORATOR
diff_hunk: @@ -7,6 +7,9 @@ if (process.env.NODE_ENV !== 'production') {
     value: PropTypes.string.isRequired,
     onClick: PropTypes.func,
     onRemove: PropTypes.func,
+    messages: PropTypes.shape({
+      removeLabel: PropTypes.string,
path: src/js/components/Tag/propTypes.js
user: login: MikeKingdom
body: same
created_at: 2025-04-08T14:26:59Z
author_association: COLLABORATOR
diff_hunk: @@ -551,6 +551,7 @@ exports[`Tag onRemove 1`] = `
       </span>
     </div>
     <button
+      aria-label="Remove Test"
path: src/js/components/Tag/__tests__/__snapshots__/Tag-test.tsx.snap
user: login: britt6612
body: will do 
created_at: 2025-04-08T14:59:38Z
author_association: COLLABORATOR

PR comments:
user: login: britt6612
created_at: 2025-04-08T16:06:28Z
body: @MikeKingdom 

glad I added the tests. I saw that `value` is actually required so I changed the json file to just be 2 use cases. 
```
  "tag": {
    "removeLabel": {
      "nameAndValue": "Remove {name}: {value}",
      "valueOnly": "Remove {value}"
    }
  },
```
since `value` is always required we only need `valueOnly` and `nameAndValue` So updates propTypes, TS and tests.
author_association: COLLABORATOR
user: login: MikeKingdom
created_at: 2025-04-08T17:41:57Z
body: 
> since `value` is always required we only need `valueOnly` and `nameAndValue` So updates propTypes, TS and tests.

Fair enough although I think that's a silly requirement and people tend to pick one or the other based on whether or not they want it to be bold.
author_association: COLLABORATOR
user: login: jcfilben
created_at: 2025-04-08T21:50:48Z
body: Holding off on merging until after the upcoming patch release
author_association: COLLABORATOR
user: login: jcfilben
created_at: 2025-04-11T16:55:23Z
body: Updated with master but looks like we need to bump the bundlesize up
author_association: COLLABORATOR