diff --git a/src/button-dropdown/__tests__/data-attributes.test.tsx b/src/button-dropdown/__tests__/data-attributes.test.tsx
new file mode 100644
index 0000000000..2b815bbc44
--- /dev/null
+++ b/src/button-dropdown/__tests__/data-attributes.test.tsx
@@ -0,0 +1,149 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import React from 'react';
+import { render } from '@testing-library/react';
+import ButtonDropdown from '../../../lib/components/button-dropdown';
+
+describe('ButtonDropdown data attributes', () => {
+ test('renders custom data attributes on items', () => {
+ const { getByText } = render(
+
+ );
+
+ const item = getByText('Edit').closest('li');
+ expect(item).toHaveAttribute('data-analytics-action', 'edit-product');
+ expect(item).toHaveAttribute('data-item-key', 'product-123');
+ });
+
+ test('automatically prepends data- prefix', () => {
+ const { getByText } = render(
+
+ );
+
+ const item = getByText('Delete').closest('li');
+ expect(item).toHaveAttribute('data-custom-attr', 'value');
+ });
+
+ test('excludes testid from dataAttributes', () => {
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
+
+ const { getByText } = render(
+
+ );
+
+ const item = getByText('Action').closest('li');
+ expect(item).toHaveAttribute('data-testid', 'original-id');
+ expect(consoleSpy).toHaveBeenCalledWith(
+ 'ButtonDropdown: "testid" key is reserved and cannot be overridden via dataAttributes'
+ );
+
+ consoleSpy.mockRestore();
+ });
+
+ test('handles undefined values', () => {
+ const { getByText } = render(
+
+ );
+
+ const item = getByText('Action').closest('li');
+ expect(item).toHaveAttribute('data-defined', 'value');
+ expect(item).not.toHaveAttribute('data-undefined');
+ });
+
+ test('works with multiple items', () => {
+ const { getByText } = render(
+
+ );
+
+ expect(getByText('Edit').closest('li')).toHaveAttribute('data-action', 'edit');
+ expect(getByText('Delete').closest('li')).toHaveAttribute('data-action', 'delete');
+ });
+
+ test('works with disabled items', () => {
+ const { getByText } = render(
+
+ );
+
+ const item = getByText('Disabled').closest('li');
+ expect(item).toHaveAttribute('data-state', 'disabled');
+ });
+
+ test('works with checkbox items', () => {
+ const { getByText } = render(
+
+ );
+
+ const item = getByText('Checkbox').closest('li');
+ expect(item).toHaveAttribute('data-checkbox-id', 'cb-1');
+ });
+});
diff --git a/src/button-dropdown/interfaces.ts b/src/button-dropdown/interfaces.ts
index 95d93c06ac..2e70536a21 100644
--- a/src/button-dropdown/interfaces.ts
+++ b/src/button-dropdown/interfaces.ts
@@ -282,6 +282,27 @@ export namespace ButtonDropdownProps {
iconUrl?: string;
iconSvg?: React.ReactNode;
labelTag?: string;
+ /**
+ * Custom data attributes to add to the dropdown item element.
+ * Attribute names will automatically be prefixed with "data-".
+ * The "testid" key is reserved and cannot be overridden.
+ *
+ * Use this for analytics tracking, testing selectors, or other metadata.
+ *
+ * @example
+ * items={[
+ * {
+ * id: 'edit',
+ * text: 'Edit',
+ * dataAttributes: {
+ * 'analytics-action': 'edit-product',
+ * 'item-key': 'product-123'
+ * }
+ * }
+ * ]}
+ * // Renders as: data-analytics-action="edit-product" data-item-key="product-123"
+ */
+ dataAttributes?: Record;
}
export interface CheckboxItem
diff --git a/src/button-dropdown/item-element/index.tsx b/src/button-dropdown/item-element/index.tsx
index 5ee7e11f3a..af68c352aa 100644
--- a/src/button-dropdown/item-element/index.tsx
+++ b/src/button-dropdown/item-element/index.tsx
@@ -22,6 +22,32 @@ import { getItemTarget } from '../utils/utils';
import analyticsLabels from '../analytics-metadata/styles.css.js';
import styles from './styles.css.js';
+/**
+ * Converts dataAttributes object to DOM data-* attributes.
+ * - Automatically prepends 'data-' prefix if not present
+ * - Excludes 'testid' to prevent overriding existing behavior
+ * - Filters out undefined values
+ */
+const getDataAttributes = (dataAttributes?: Record): Record => {
+ if (!dataAttributes) return {};
+
+ return Object.entries(dataAttributes).reduce((acc, [key, value]) => {
+ // Exclude 'testid' to prevent overriding existing data-testid behavior
+ if (key === 'testid' || key === 'data-testid') {
+ console.warn('ButtonDropdown: "testid" key is reserved and cannot be overridden via dataAttributes');
+ return acc;
+ }
+
+ // Skip undefined values
+ if (value === undefined) return acc;
+
+ // Add 'data-' prefix if not already present
+ const attrKey = key.startsWith('data-') ? key : `data-${key}`;
+ acc[attrKey] = value;
+ return acc;
+ }, {} as Record);
+};
+
const ItemElement = ({
position = '1',
index,
@@ -73,6 +99,7 @@ const ItemElement = ({
role="presentation"
data-testid={item.id}
data-description={item.description}
+ {...getDataAttributes(item.dataAttributes)}
onClick={onClick}
onMouseEnter={onHover}
onTouchStart={onHover}