diff --git a/src/components/experimental/IconButton/IconButton.spec.tsx b/src/components/experimental/IconButton/IconButton.spec.tsx
new file mode 100644
index 000000000..5d0ef759c
--- /dev/null
+++ b/src/components/experimental/IconButton/IconButton.spec.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react';
+import { render, screen } from '@testing-library/react';
+import { IconButton } from './IconButton';
+import { TrashIcon } from '../../../icons';
+
+describe('Experimental: IconButton', () => {
+ it('renders an icon button with the provided icon', () => {
+ const onPress = jest.fn();
+ render();
+ expect(screen.getByTestId('standard-icon-container')).toBeInTheDocument();
+ });
+
+ it('calls onPress when clicked', () => {
+ const onPress = jest.fn();
+ render();
+ screen.getByTestId('standard-icon-container').click();
+ expect(onPress).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not call onPress when disabled', () => {
+ const onPress = jest.fn();
+ render();
+ screen.getByTestId('standard-icon-container').click();
+ expect(onPress).toHaveBeenCalledTimes(0);
+ });
+
+ it('sets the right sizes for standard variant', () => {
+ const onPress = jest.fn();
+ render();
+ const iconContainerInstance = screen.getByTestId('standard-icon-container');
+ const containerStyle = window.getComputedStyle(iconContainerInstance);
+ expect(containerStyle.width).toBe('2.5rem');
+ expect(containerStyle.height).toBe('2.5rem');
+ expect(containerStyle.borderRadius).toBe('100%');
+ });
+
+ it('sets the right sizes for tonal variant', () => {
+ const onPress = jest.fn();
+ render();
+ const iconContainerInstance = screen.getByTestId('tonal-icon-container');
+ const containerStyle = window.getComputedStyle(iconContainerInstance);
+ expect(containerStyle.width).toBe('3.5rem');
+ expect(containerStyle.height).toBe('3.5rem');
+ expect(containerStyle.borderRadius).toBe('100%');
+ });
+});
diff --git a/src/components/experimental/IconButton/IconButton.tsx b/src/components/experimental/IconButton/IconButton.tsx
new file mode 100644
index 000000000..488c10039
--- /dev/null
+++ b/src/components/experimental/IconButton/IconButton.tsx
@@ -0,0 +1,121 @@
+import React from 'react';
+import styled from 'styled-components';
+import { ButtonProps, Button } from 'react-aria-components';
+import { IconProps } from '../../../icons';
+import { getSemanticValue } from '../../../essentials/experimental';
+
+export interface IconButtonProps extends ButtonProps {
+ isActive?: boolean;
+ variant?: 'standard' | 'tonal';
+ Icon: React.FC;
+ onPress: () => void;
+}
+
+const StandardIconContainer = styled(Button)>`
+ height: 2.5rem;
+ width: 2.5rem;
+ border-radius: 100%;
+ background-color: transparent;
+ border-color: transparent;
+
+ /* we create a before pseudo element to mess with the opacity (see the hovered state) */
+ &::before {
+ position: absolute;
+ content: '';
+ border-radius: inherit;
+ opacity: 0;
+ height: inherit;
+ width: inherit;
+ }
+
+ /* we want to change the opacity here but not affect the icon, so we have to use the before pseudo element */
+ &[data-hovered]::before {
+ opacity: 0.16;
+ background-color: ${getSemanticValue('on-surface')};
+ }
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:not([data-disabled]) {
+ color: ${props => (props.isActive ? getSemanticValue('interactive') : getSemanticValue('on-surface'))};
+ }
+
+ &[data-disabled] {
+ opacity: 0.38;
+ }
+`;
+
+const TonalIconContainer = styled(Button)>`
+ height: 3.5rem;
+ width: 3.5rem;
+ border-radius: 100%;
+ border-color: transparent;
+ background: none;
+
+ /* we create a before pseudo element to mess with the opacity (see the hovered state) */
+ &::before {
+ position: absolute;
+ content: '';
+ border-radius: inherit;
+ height: inherit;
+ width: inherit;
+ background-color: ${props =>
+ props.isActive && !props.isDisabled
+ ? getSemanticValue('interactive-container')
+ : getSemanticValue('surface')};
+ z-index: -1;
+ }
+
+ /* we want to change the opacity here but not affect the icon, so we have to use the before pseudo element */
+ &[data-hovered]::before {
+ background-color: color-mix(
+ in hsl,
+ ${getSemanticValue('on-surface')} 100%,
+ ${props => (props.isActive ? getSemanticValue('interactive-container') : getSemanticValue('on-surface'))}
+ 100%
+ );
+ opacity: 0.16;
+ }
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:not([data-disabled]) {
+ color: ${props =>
+ props.isActive ? getSemanticValue('on-interactive-container') : getSemanticValue('on-surface')};
+ }
+
+ &[data-disabled] {
+ opacity: 0.38;
+ }
+`;
+
+export const IconButton = ({
+ isDisabled = false,
+ isActive = false,
+ Icon,
+ variant = 'standard',
+ onPress
+}: IconButtonProps) =>
+ variant === 'standard' ? (
+
+
+
+ ) : (
+
+
+
+ );
diff --git a/src/components/experimental/IconButton/docs/IconButton.stories.tsx b/src/components/experimental/IconButton/docs/IconButton.stories.tsx
new file mode 100644
index 000000000..fc5ab0208
--- /dev/null
+++ b/src/components/experimental/IconButton/docs/IconButton.stories.tsx
@@ -0,0 +1,47 @@
+import { StoryObj, Meta } from '@storybook/react';
+import { IconButton } from '../IconButton';
+import { TrashIcon } from '../../../../icons';
+
+const meta: Meta = {
+ title: 'Experimental/Components/IconButton',
+ component: IconButton,
+ parameters: {
+ layout: 'centered'
+ },
+ args: {
+ Icon: TrashIcon,
+ onPress: () => alert('Clicked!'),
+ isDisabled: false
+ }
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {};
+
+export const Disabled: Story = {
+ args: {
+ isDisabled: true
+ }
+};
+
+export const Active: Story = {
+ args: {
+ isActive: true
+ }
+};
+
+export const Tonal: Story = {
+ args: {
+ variant: 'tonal'
+ }
+};
+
+export const TonalActive: Story = {
+ args: {
+ variant: 'tonal',
+ isActive: true
+ }
+};
diff --git a/src/components/experimental/index.ts b/src/components/experimental/index.ts
index 61be18907..873ce5800 100644
--- a/src/components/experimental/index.ts
+++ b/src/components/experimental/index.ts
@@ -4,6 +4,8 @@ export { Chip } from './Chip/Chip';
export { ComboBox } from './ComboBox/ComboBox';
export { DateField } from './DateField/DateField';
export { DatePicker } from './DatePicker/DatePicker';
+export { Divider } from './Divider/Divider';
+export { IconButton } from './IconButton/IconButton';
export { Label } from './Label/Label';
export { ListBox, ListBoxItem } from './ListBox/ListBox';
export { Popover } from './Popover/Popover';