+
+
+`;
+
+exports[`EuiButtonSplit props text color styling renders non-text color with fill=false (has margin, has left border) 1`] = `
+
+
+
+
+
+
+
+
+`;
+
+exports[`EuiButtonSplit props text color styling renders text color with fill=false (no margin, no left border) 1`] = `
+
+
+
+
+
+
+
+
+`;
+
+exports[`EuiButtonSplit props text color styling renders text color with fill=true (has margin, has left border) 1`] = `
+
+
+
+
+
+
+
+
+`;
diff --git a/packages/eui/src/components/button/button_split/button_split.stories.tsx b/packages/eui/src/components/button/button_split/button_split.stories.tsx
new file mode 100644
index 00000000000..982131c5f5f
--- /dev/null
+++ b/packages/eui/src/components/button/button_split/button_split.stories.tsx
@@ -0,0 +1,93 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { EuiButtonSplit, EuiButtonSplitProps } from './button_split';
+import { EuiListGroup } from '../../list_group';
+
+const meta: Meta = {
+ title: 'Navigation/EuiButtonSplit',
+ component: EuiButtonSplit,
+ args: {
+ color: 'text',
+ fill: false,
+ size: 's',
+ isDisabled: false,
+ buttonProps: {
+ children: 'Add panel',
+ onClick: () => alert('Main button clicked!'),
+ },
+ iconButtonProps: {
+ iconType: 'arrowDown',
+ 'aria-label': 'More actions',
+ },
+ popoverMenu: (closePopover) => (
+ {
+ alert('Open Lens clicked!');
+ closePopover();
+ },
+ iconType: 'visualizeApp',
+ },
+ {
+ label: 'Open Maps',
+ onClick: () => {
+ alert('Open Maps clicked!');
+ closePopover();
+ },
+ iconType: 'gisApp',
+ },
+ ]}
+ showToolTips={false}
+ />
+ ),
+ // panelPaddingSize intentionally omitted to show default
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {},
+ parameters: {
+ docs: {
+ description: {
+ story: `
+This split button demonstrates custom border radius and spacing:
+- The right edge of the left button and the left edge of the right button have zero border radius, making them join seamlessly.
+- There is a 1px space between the two buttons for visual clarity.
+
+**panelPaddingSize** can be set to control the popover's padding. The default is 'm'. Example with custom padding:
+
+\`\`\`tsx
+}
+ panelPaddingSize="l"
+/>
+\`\`\`
+ `,
+ },
+ },
+ },
+ render: (args) => (
+
+
+
+ ),
+};
diff --git a/packages/eui/src/components/button/button_split/button_split.styles.ts b/packages/eui/src/components/button/button_split/button_split.styles.ts
new file mode 100644
index 00000000000..979ab67426e
--- /dev/null
+++ b/packages/eui/src/components/button/button_split/button_split.styles.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+import { logicalCSS } from '../../../global_styling/functions';
+
+export const euiButtonSplitStyles = () => {
+ return {
+ euiButtonSplit: css`
+ display: inline-flex;
+ align-items: stretch;
+ `,
+ leftButton: css`
+ ${logicalCSS('border-top-right-radius', '0 !important')}
+ ${logicalCSS('border-bottom-right-radius', '0 !important')}
+ `,
+ rightSpan: (color: string, fill?: boolean) => css`
+ display: flex;
+ align-items: stretch;
+ ${color !== 'text' || fill ? 'margin-left: 1px;' : ''}
+ `,
+ iconButton: (color: string, fill?: boolean) => css`
+ ${logicalCSS('border-top-left-radius', '0 !important')}
+ ${logicalCSS('border-bottom-left-radius', '0 !important')}
+ ${color === 'text' && !fill
+ ? logicalCSS('border-left', 'none !important')
+ : ''}
+ `,
+ };
+};
diff --git a/packages/eui/src/components/button/button_split/button_split.test.tsx b/packages/eui/src/components/button/button_split/button_split.test.tsx
new file mode 100644
index 00000000000..d0ddf20493e
--- /dev/null
+++ b/packages/eui/src/components/button/button_split/button_split.test.tsx
@@ -0,0 +1,164 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { fireEvent, waitFor } from '@testing-library/react';
+import { render } from '../../../test/rtl';
+import { requiredProps } from '../../../test/required_props';
+import { shouldRenderCustomStyles } from '../../../test/internal';
+
+import { EuiButtonSplit } from './button_split';
+
+const defaultButtonProps = {
+ children: 'Main',
+ onClick: jest.fn(),
+};
+const defaultIconButtonProps = {
+ iconType: 'arrowDown',
+ 'aria-label': 'More actions',
+};
+const popoverMenu = jest.fn((_closePopover) =>