diff --git a/packages/web-react/src/components/SplitButton/README.md b/packages/web-react/src/components/SplitButton/README.md
index 5a2d9978bc..903f0a5a4a 100644
--- a/packages/web-react/src/components/SplitButton/README.md
+++ b/packages/web-react/src/components/SplitButton/README.md
@@ -104,7 +104,7 @@ const onDropdownToggle = () => setIsOpen(!isOpen);
;
```
-## API
+### API
| Name | Type | Default | Required | Description |
| ------- | ------------------------------------------ | --------- | -------- | ------------- |
@@ -115,7 +115,62 @@ On top of the API options, the components accept [additional attributes][readme-
If you need more control over the styling of a component, you can use [style props][readme-style-props]
and [escape hatches][readme-escape-hatches].
+## Uncontrolled Split Button
+
+Uncontrolled Split Button is combination of Button component and Dropdown component.
+It is used when you want to have a button with additional actions in a dropdown menu.
+
+Simple variant:
+
+```jsx
+ alert('Button clicked')}
+>
+ {/* Dropdown content */}
+
+```
+
+Full example:
+
+```jsx
+ alert('Button clicked')}
+ color="secondary"
+ dropdownIconName="more"
+ dropdownLabel="More"
+ dropdownPlacement="bottom-start"
+ id="uncontrolled-split-button"
+ isDisabled={false}
+ size="large"
+>
+ {/* Dropdown content */}
+
+```
+
+### API
+
+| Name | Type | Default | Required | Description |
+| ------------------- | -------------------------------------------- | -------------- | -------- | -------------------------------------------------------- |
+| `buttonIconName` | `string` | - | ✕ \* | Name of the icon to be displayed in the Button |
+| `buttonLabel` | `string` | - | ✕ \* | Label of the Button |
+| `buttonOnClick` | `function` | - | ✓ | Function to be called when the Button is clicked |
+| `children` | `ReactNode` | - | ✓ | Dropdown content |
+| `color` | \[`primary` \| `secondary` \| `tertiary` ] | `primary` | ✕ | Color variant |
+| `dropdownIconName` | `string` | `chevron-down` | ✕ | Name of the icon to be displayed in the Dropdown Trigger |
+| `dropdownLabel` | `string` | - | ✕ | Label of the Dropdown Trigger |
+| `dropdownPlacement` | [Placement dictionary][dictionary-placement] | `bottom-end` | ✕ | Placement of the Dropdown |
+| `id` | `string` | - | ✓ | Id of the Split Button and part of Dropdown id |
+| `isDisabled` | `boolean` | `false` | ✕ | Disables the Split Button |
+| `size` | [Size dictionary][dictionary-size] | `medium` | ✕ | Size variant |
+
+(\*) Conditionally required: either `buttonIconName` or `buttonLabel` must be provided.
+
[dictionary-size]: https://github.com/lmc-eu/spirit-design-system/tree/main/docs/DICTIONARIES.md#size
+[dictionary-placement]: https://github.com/lmc-eu/spirit-design-system/tree/main/docs/DICTIONARIES.md#placement
[readme-additional-attributes]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#additional-attributes
[readme-button]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/components/Button/README.md
[readme-dropdown]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/components/Dropdown/README.md
diff --git a/packages/web-react/src/components/SplitButton/UncontrolledSplitButton.tsx b/packages/web-react/src/components/SplitButton/UncontrolledSplitButton.tsx
new file mode 100644
index 0000000000..3fb2a0eb93
--- /dev/null
+++ b/packages/web-react/src/components/SplitButton/UncontrolledSplitButton.tsx
@@ -0,0 +1,53 @@
+import React, { useState } from 'react';
+import { UncontrolledSplitButtonProps } from '../../types';
+import { Button } from '../Button';
+import { Dropdown, DropdownPopover, DropdownTrigger } from '../Dropdown';
+import { Icon } from '../Icon';
+import { VisuallyHidden } from '../VisuallyHidden';
+import SplitButton from './SplitButton';
+
+const defaultProps: Partial = {
+ dropdownPlacement: 'bottom-end',
+ dropdownIconName: 'chevron-down',
+};
+
+const UncontrolledSplitButton = (props: UncontrolledSplitButtonProps) => {
+ const propsWithDefaults = { ...defaultProps, ...props };
+ const {
+ children,
+ dropdownIconName,
+ dropdownLabel,
+ dropdownPlacement,
+ id,
+ isDisabled,
+ buttonLabel,
+ buttonOnClick,
+ buttonIconName,
+ ...restProps
+ } = propsWithDefaults;
+ const [openDropdownStates, setOpenDropdownStates] = useState(false);
+
+ return (
+
+
+ setOpenDropdownStates(!openDropdownStates)}
+ placement={dropdownPlacement}
+ >
+
+ {!dropdownLabel && {dropdownIconName}}
+ {dropdownLabel && dropdownLabel}
+
+
+ {children}
+
+
+ );
+};
+
+export default UncontrolledSplitButton;
diff --git a/packages/web-react/src/components/SplitButton/__tests__/UncontrolledSplitButton.test.tsx b/packages/web-react/src/components/SplitButton/__tests__/UncontrolledSplitButton.test.tsx
new file mode 100644
index 0000000000..70c39d3ce8
--- /dev/null
+++ b/packages/web-react/src/components/SplitButton/__tests__/UncontrolledSplitButton.test.tsx
@@ -0,0 +1,92 @@
+import '@testing-library/jest-dom';
+import { render, screen } from '@testing-library/react';
+import React from 'react';
+import { classNamePrefixProviderTest, restPropsTest, stylePropsTest } from '@local/tests';
+import { ComponentButtonColors, Sizes } from '../../../constants';
+import UncontrolledSplitButton from '../UncontrolledSplitButton';
+
+describe('SplitButton', () => {
+ const splitButtonColors = Object.values(ComponentButtonColors).filter(
+ (color) => color !== ComponentButtonColors.PLAIN,
+ );
+
+ const onClick = jest.fn();
+
+ classNamePrefixProviderTest(UncontrolledSplitButton, 'SplitButton');
+
+ stylePropsTest(UncontrolledSplitButton);
+
+ restPropsTest(UncontrolledSplitButton, 'div');
+
+ it('should have default classname', () => {
+ render(
+
+ Content
+ ,
+ );
+
+ expect(screen.getByTestId('test')).toHaveClass('SplitButton');
+ });
+
+ it('should render dropdown content', () => {
+ render(
+
+ Content
+ ,
+ );
+
+ expect(screen.getByText('Content')).toHaveClass('DropdownPopover');
+ });
+
+ it.each(Object.values(Sizes))('should render size %s on buttons', (size) => {
+ render(
+
+ Content
+ ,
+ );
+
+ expect(screen.getByText('Button')).toHaveClass(`Button--${size}`);
+ });
+
+ it.each(splitButtonColors)('should render color %s on buttons', (color) => {
+ render(
+
+ Content
+ ,
+ );
+
+ expect(screen.getByText('Button')).toHaveClass(`Button--${color}`);
+ });
+
+ it('should render color and size on buttons', () => {
+ render(
+
+ Content
+ ,
+ );
+
+ expect(screen.getByText('Button')).toHaveClass('Button--secondary');
+ expect(screen.getByText('Button')).toHaveClass('Button--small');
+ });
+});
diff --git a/packages/web-react/src/components/SplitButton/demo/UncontrolledSplitButton.tsx b/packages/web-react/src/components/SplitButton/demo/UncontrolledSplitButton.tsx
new file mode 100644
index 0000000000..aa01398884
--- /dev/null
+++ b/packages/web-react/src/components/SplitButton/demo/UncontrolledSplitButton.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { Item } from '../../Item';
+import UncontrolledSplitButton from '../UncontrolledSplitButton';
+
+const UncontrolledSplitButtonDemo = () => {
+ return (
+ alert('Button clicked')}
+ color="secondary"
+ dropdownIconName="more"
+ dropdownLabel="More"
+ dropdownPlacement="top-end"
+ id="uncontrolled-split-button"
+ isDisabled={false}
+ size="large"
+ >
+
+
+
+ );
+};
+
+export default UncontrolledSplitButtonDemo;
diff --git a/packages/web-react/src/components/SplitButton/index.ts b/packages/web-react/src/components/SplitButton/index.ts
index 63df5c084f..cbcd1daba4 100644
--- a/packages/web-react/src/components/SplitButton/index.ts
+++ b/packages/web-react/src/components/SplitButton/index.ts
@@ -1,4 +1,5 @@
'use client';
export { default as SplitButton } from './SplitButton';
+export { default as UncontrolledSplitButton } from './UncontrolledSplitButton';
export * from './useSplitButtonStyleProps';
diff --git a/packages/web-react/src/components/SplitButton/stories/SplitButtonParts.tsx b/packages/web-react/src/components/SplitButton/stories/SplitButtonParts.tsx
index 5b7d60c7c9..2569afff97 100644
--- a/packages/web-react/src/components/SplitButton/stories/SplitButtonParts.tsx
+++ b/packages/web-react/src/components/SplitButton/stories/SplitButtonParts.tsx
@@ -7,7 +7,7 @@ import { Tooltip, TooltipPopover, TooltipTrigger } from '../../Tooltip';
import { VisuallyHidden } from '../../VisuallyHidden';
import { dropdownContent } from '../demo/constants';
-const DropdownContent = () => {
+export const DropdownContent = () => {
return (
<>
{dropdownContent.map(({ icon, text }) => (
diff --git a/packages/web-react/src/components/SplitButton/stories/UncontrolledSplitButton.stories.tsx b/packages/web-react/src/components/SplitButton/stories/UncontrolledSplitButton.stories.tsx
new file mode 100644
index 0000000000..7a285992da
--- /dev/null
+++ b/packages/web-react/src/components/SplitButton/stories/UncontrolledSplitButton.stories.tsx
@@ -0,0 +1,126 @@
+import { Markdown } from '@storybook/blocks';
+import type { Meta, StoryObj } from '@storybook/react';
+import React from 'react';
+import { ComponentButtonColors, Placements, Sizes } from '../../../constants';
+import ReadMe from '../README.md';
+import { UncontrolledSplitButton } from '..';
+import { DropdownContent } from './SplitButtonParts';
+
+const meta: Meta = {
+ title: 'Components/SplitButton',
+ component: UncontrolledSplitButton,
+ parameters: {
+ docs: {
+ page: () => {ReadMe},
+ },
+ controls: { exclude: ['ElementTag', 'UNSAFE_className', 'UNSAFE_style', 'transferClassName'] },
+ },
+ argTypes: {
+ id: {
+ control: 'text',
+ description: 'The ID of the Uncontrolled Split Button.',
+ table: {
+ type: { summary: 'string' },
+ },
+ },
+ size: {
+ control: 'select',
+ options: [...Object.values(Sizes)],
+ description: 'Size of the button.',
+ table: {
+ defaultValue: { summary: Sizes.MEDIUM },
+ type: { summary: 'ButtonSize' },
+ },
+ },
+ color: {
+ control: 'select',
+ options: [...Object.values(ComponentButtonColors).filter((color) => color !== ComponentButtonColors.PLAIN)],
+ description: 'Color of the button.',
+ table: {
+ defaultValue: { summary: ComponentButtonColors.PRIMARY },
+ type: { summary: 'ButtonColor' },
+ },
+ },
+ buttonLabel: {
+ control: 'text',
+ description: 'The label for the button.',
+ table: {
+ type: { summary: 'string' },
+ },
+ },
+ buttonIconName: {
+ control: 'text',
+ description: 'The name of the icon to display on the button.',
+ table: {
+ type: { summary: 'string' },
+ },
+ },
+ dropdownLabel: {
+ control: 'text',
+ description: 'The label for the dropdown button.',
+ table: {
+ type: { summary: 'string' },
+ },
+ },
+ children: {
+ control: 'text',
+ description: 'The content of the dropdown.',
+ table: {
+ type: { summary: 'ReactNode' },
+ },
+ },
+ dropdownPlacement: {
+ control: 'select',
+ options: Object.values(Placements),
+ table: {
+ defaultValue: { summary: Placements.BOTTOM_END },
+ },
+ description: 'The placement of the dropdown.',
+ },
+ isDisabled: {
+ control: 'boolean',
+ description: 'Whether the button is disabled.',
+ table: {
+ type: { summary: 'boolean' },
+ },
+ },
+ dropdownIconName: {
+ control: 'text',
+ description: 'The name of the icon to display on the dropdown button.',
+ table: {
+ type: { summary: 'string' },
+ },
+ },
+ buttonOnClick: {
+ description: 'Function to call when the button is clicked.',
+ table: {
+ type: { summary: '() => void' },
+ },
+ },
+ },
+ args: {
+ buttonIconName: undefined,
+ buttonLabel: 'Button',
+ buttonOnClick: () => {},
+ children: ,
+ color: ComponentButtonColors.PRIMARY,
+ dropdownIconName: 'chevron-down',
+ dropdownLabel: undefined,
+ dropdownPlacement: Placements.BOTTOM_END,
+ id: 'uncontrolled-split-button',
+ isDisabled: false,
+ size: Sizes.MEDIUM,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const UncontrolledSplitButtonPlayground: Story = {
+ name: 'UncontrolledSplitButton',
+ render: (args) => {
+ const { children, ...rest } = args;
+
+ return {children};
+ },
+};
diff --git a/packages/web-react/src/types/splitButton.ts b/packages/web-react/src/types/splitButton.ts
index 810ca25db6..c8fc314281 100644
--- a/packages/web-react/src/types/splitButton.ts
+++ b/packages/web-react/src/types/splitButton.ts
@@ -1,5 +1,6 @@
+import { ReactNode } from 'react';
import { ButtonColor, ButtonSize } from './button';
-import { ChildrenProps, StyleProps, TransferProps } from './shared';
+import { ChildrenProps, PlacementDictionaryType, StyleProps, TransferProps } from './shared';
export interface SplitButtonProps extends TransferProps, StyleProps, ChildrenProps {}
@@ -7,3 +8,23 @@ export interface SpiritSplitButtonProps extends SplitButtonP
color?: ButtonColor;
size?: ButtonSize;
}
+
+export type UncontrolledSplitButtonProps = {
+ buttonOnClick: () => void;
+ children: ReactNode;
+ dropdownIconName?: string;
+ dropdownLabel?: string;
+ dropdownPlacement?: PlacementDictionaryType;
+ id: string;
+ isDisabled?: boolean;
+} & (
+ | {
+ buttonLabel?: string;
+ buttonIconName: string;
+ }
+ | {
+ buttonLabel: string;
+ buttonIconName?: string;
+ }
+) &
+ SpiritSplitButtonProps;