> = {
+ ArrowDown: (currentOptionIndex + 1) % enabledOptions.length,
+ ArrowLeft:
+ currentOptionIndex === 0 ? lastOptionIndex : currentOptionIndex - 1,
+ ArrowRight: (currentOptionIndex + 1) % enabledOptions.length,
+ ArrowUp:
+ currentOptionIndex === 0 ? lastOptionIndex : currentOptionIndex - 1,
+ End: lastOptionIndex,
+ Home: 0,
+ };
+
+ const nextOptionIndex = optionIndexByKey[event.key];
+
+ if (!isDefined(nextOptionIndex)) {
+ return;
+ }
+
+ event.preventDefault();
+ focusAndSelectOption({
+ event,
+ optionIndex: nextOptionIndex,
+ optionValue: enabledOptions[nextOptionIndex].value,
+ });
+ };
+
+ return (
+
+ {options.map(
+ ({
+ Icon,
+ ariaLabel: itemAriaLabel,
+ disabled,
+ label,
+ value: optionValue,
+ }: SegmentedControlOptionButtonProps) => {
+ const isSelected = optionValue === value;
+ const inferredAriaLabel =
+ itemAriaLabel ?? (typeof label === 'string' ? label : undefined);
+ const hasLabel = isDefined(label);
+
+ return (
+
+ );
+ },
+ )}
+
+ );
+};
diff --git a/packages/twenty-ui/src/input/SegmentedControl/__stories__/SegmentedControl.stories.tsx b/packages/twenty-ui/src/input/SegmentedControl/__stories__/SegmentedControl.stories.tsx
new file mode 100644
index 0000000000..6e9bb2606d
--- /dev/null
+++ b/packages/twenty-ui/src/input/SegmentedControl/__stories__/SegmentedControl.stories.tsx
@@ -0,0 +1,76 @@
+import { type Meta, type StoryObj } from '@storybook/react-vite';
+import { IconComment, IconHome } from '@ui/icon';
+import { ComponentDecorator } from '@ui/testing';
+import { useState } from 'react';
+
+import {
+ SegmentedControl,
+ type SegmentedControlOption,
+} from '@ui/input/SegmentedControl/SegmentedControl';
+
+type InteractiveSegmentedControlProps = {
+ initialValue: string;
+ options: SegmentedControlOption[];
+ role?: 'group' | 'tablist';
+};
+
+const InteractiveSegmentedControl = ({
+ initialValue,
+ options,
+ role,
+}: InteractiveSegmentedControlProps) => {
+ const [value, setValue] = useState(initialValue);
+
+ return (
+
+ );
+};
+
+const meta: Meta = {
+ title: 'UI/Input/SegmentedControl',
+ component: InteractiveSegmentedControl,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ initialValue: 'annual',
+ options: [
+ { label: 'Annual', value: 'annual' },
+ { label: 'Monthly', value: 'monthly' },
+ ],
+ },
+ decorators: [ComponentDecorator],
+};
+
+export const IconOnlyTabList: Story = {
+ args: {
+ initialValue: 'home',
+ options: [
+ { Icon: IconHome, ariaLabel: 'Home', value: 'home' },
+ { Icon: IconComment, ariaLabel: 'Chat', value: 'chat' },
+ ],
+ role: 'tablist',
+ },
+ decorators: [ComponentDecorator],
+};
+
+export const WithDisabledOption: Story = {
+ args: {
+ initialValue: 'annual',
+ options: [
+ { label: 'Annual', value: 'annual' },
+ { label: 'Monthly', value: 'monthly' },
+ { disabled: true, label: 'Weekly', value: 'weekly' },
+ ],
+ },
+ decorators: [ComponentDecorator],
+};
diff --git a/packages/twenty-ui/src/input/index.ts b/packages/twenty-ui/src/input/index.ts
index 1e28c2cdf7..9cff442961 100644
--- a/packages/twenty-ui/src/input/index.ts
+++ b/packages/twenty-ui/src/input/index.ts
@@ -95,6 +95,11 @@ export type { RoundedIconButtonSize } from './RoundedIconButton/RoundedIconButto
export { RoundedIconButton } from './RoundedIconButton/RoundedIconButton';
export type { SearchInputProps } from './SearchInput/SearchInput';
export { SearchInput } from './SearchInput/SearchInput';
+export type {
+ SegmentedControlOption,
+ SegmentedControlProps,
+} from './SegmentedControl/SegmentedControl';
+export { SegmentedControl } from './SegmentedControl/SegmentedControl';
export type { SliderColor, SliderProps } from './Slider/Slider';
export { Slider } from './Slider/Slider';
export type { TabContentProps } from './TabButton/TabButton';