diff --git a/apps/store/src/components/ShopBreakdown/DiscountField.tsx b/apps/store/src/components/ShopBreakdown/DiscountField.tsx
index 98158e7d82..ad810eb7a9 100644
--- a/apps/store/src/components/ShopBreakdown/DiscountField.tsx
+++ b/apps/store/src/components/ShopBreakdown/DiscountField.tsx
@@ -1,8 +1,7 @@
import { useTranslation } from 'next-i18next'
import { useState } from 'react'
-import { sprinkles, Text, xStack } from 'ui'
+import { sprinkles, Switch, Text, xStack } from 'ui'
import Collapsible from '@/components/Collapsible/Collapsible'
-import { Switch } from '@/components/Switch'
import { AddCampaignForm } from './AddCampaignForm/AddCampaignForm'
import { AddedCampaignForm } from './AddedCampaignForm'
diff --git a/apps/store/src/components/Switch.tsx b/apps/store/src/components/Switch.tsx
deleted file mode 100644
index 05d4c6e772..0000000000
--- a/apps/store/src/components/Switch.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import styled from '@emotion/styled'
-import * as RadixSwitch from '@radix-ui/react-switch'
-import { theme } from 'ui'
-
-export type SwitchProps = RadixSwitch.SwitchProps
-
-export const Switch = (props: SwitchProps) => {
- return (
-
-
-
- )
-}
-
-export const SwitchWrapper = styled(RadixSwitch.Root)({
- boxSizing: 'border-box',
- width: '1.75rem',
- borderRadius: 9999,
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'flex-start',
-
- backgroundColor: theme.colors.opaque3,
- borderWidth: 1,
- borderStyle: 'solid',
- borderColor: 'transparent',
-
- ':focus-visible': {
- borderColor: theme.colors.gray1000,
- },
-
- '&[data-state=checked], &[data-state=open]': {
- backgroundColor: theme.colors.signalGreenElement,
- },
-
- '&[disabled]': {
- backgroundColor: theme.colors.opaque3,
- cursor: 'not-allowed',
- },
-})
-
-const SwitchHandle = styled(RadixSwitch.Thumb)({
- width: '1rem',
- height: '1rem',
- borderRadius: '100%',
- backgroundColor: theme.colors.textNegative,
-
- transition: 'transform 100ms',
- transform: 'translateX(0)',
- willChange: 'transform',
-
- '&[data-state=checked]': {
- transform: 'translateX(0.6rem)',
- },
-})
diff --git a/apps/store/src/components/ToggleCard/ToggleCard.tsx b/apps/store/src/components/ToggleCard/ToggleCard.tsx
index 801695b1b2..38d3134f73 100644
--- a/apps/store/src/components/ToggleCard/ToggleCard.tsx
+++ b/apps/store/src/components/ToggleCard/ToggleCard.tsx
@@ -1,11 +1,10 @@
-import type { ReactNode } from 'react'
+import type { ComponentProps, ReactNode } from 'react'
import { useId } from 'react'
-import { Space, useHighlightAnimation } from 'ui'
+import { Space, Switch, useHighlightAnimation } from 'ui'
import { SpaceFlex } from '@/components/SpaceFlex/SpaceFlex'
-import { Switch, type SwitchProps } from '@/components/Switch'
import { wrapper, checkboxHeader, labelText } from './ToggleCard.css'
-type Props = SwitchProps & {
+type Props = ComponentProps & {
label: string
Icon?: ReactNode
}
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 51fc309058..6a81eac8b8 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -54,8 +54,10 @@
"@emotion/styled": "11.13.0",
"@radix-ui/react-dialog": "1.1.1",
"@radix-ui/react-slot": "1.1.0",
+ "@radix-ui/react-switch": "1.1.0",
"@radix-ui/react-tabs": "1.1.0",
"@radix-ui/react-tooltip": "1.1.2",
+ "@storybook/addon-actions": "8.3.1",
"@vanilla-extract/css": "1.15.5",
"@vanilla-extract/dynamic": "2.1.2",
"@vanilla-extract/recipes": "0.5.5",
diff --git a/packages/ui/src/components/Switch/Switch.css.ts b/packages/ui/src/components/Switch/Switch.css.ts
new file mode 100644
index 0000000000..6ab9bee50a
--- /dev/null
+++ b/packages/ui/src/components/Switch/Switch.css.ts
@@ -0,0 +1,48 @@
+import { style } from '@vanilla-extract/css'
+import { tokens } from '../../theme'
+
+export const switchStyles = style({
+ boxSizing: 'border-box',
+ width: '1.75rem',
+ borderRadius: 9999,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'flex-start',
+
+ backgroundColor: tokens.colors.opaque3,
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderColor: 'transparent',
+
+ ':focus-visible': {
+ borderColor: tokens.colors.gray1000,
+ },
+
+ selectors: {
+ '&[data-state=checked], &[data-state=open]': {
+ backgroundColor: tokens.colors.signalGreenElement,
+ },
+
+ '&[disabled]': {
+ backgroundColor: tokens.colors.opaque3,
+ cursor: 'not-allowed',
+ },
+ },
+})
+
+export const thumbStyles = style({
+ width: '1rem',
+ height: '1rem',
+ borderRadius: '100%',
+ backgroundColor: tokens.colors.textNegative,
+
+ transition: 'transform 100ms',
+ transform: 'translateX(0)',
+ willChange: 'transform',
+
+ selectors: {
+ '&[data-state=checked]': {
+ transform: 'translateX(0.6rem)',
+ },
+ },
+})
diff --git a/packages/ui/src/components/Switch/Switch.stories.tsx b/packages/ui/src/components/Switch/Switch.stories.tsx
new file mode 100644
index 0000000000..b6644cb58e
--- /dev/null
+++ b/packages/ui/src/components/Switch/Switch.stories.tsx
@@ -0,0 +1,41 @@
+import { action } from '@storybook/addon-actions'
+import type { Meta, StoryObj } from '@storybook/react'
+import { useState, type ComponentProps } from 'react'
+import { Switch } from './Switch'
+
+type Controls = ComponentProps
+
+const meta: Meta = {
+ title: 'Switch',
+ component: Switch,
+}
+export default meta
+
+type Story = StoryObj
+
+export const Controlled: Story = {
+ render: () => {
+ const [checked, setIsChecked] = useState(true)
+
+ const toggle = () => setIsChecked((isChecked) => !isChecked)
+
+ return (
+
+
+
+ )
+ },
+}
+
+export const Uncontrolled: Story = {
+ render: (args: Controls) => {
+ return (
+
+
+
+ )
+ },
+ args: {
+ onCheckedChange: action('onCheckedChange'),
+ },
+}
diff --git a/packages/ui/src/components/Switch/Switch.tsx b/packages/ui/src/components/Switch/Switch.tsx
new file mode 100644
index 0000000000..611474ff98
--- /dev/null
+++ b/packages/ui/src/components/Switch/Switch.tsx
@@ -0,0 +1,13 @@
+import * as RadixSwitch from '@radix-ui/react-switch'
+import clsx from 'clsx'
+import { switchStyles, thumbStyles } from './Switch.css'
+
+export type SwitchProps = RadixSwitch.SwitchProps
+
+export const Switch = ({ className, ...props }: SwitchProps) => {
+ return (
+
+
+
+ )
+}
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 6a7dd33e0e..4b6425368b 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -9,6 +9,7 @@ export { IconButton } from './components/Button/IconButton'
export { Tooltip } from './components/Tooltip/Tooltip'
export { Card } from './components/Card/Card'
export { InputBase } from './components/InputBase'
+export { Switch } from './components/Switch/Switch'
export type { InputBaseProps } from './components/InputBase'
export { Heading } from './components/Heading/Heading'
export type { HeadingProps, PossibleHeadingVariant } from './components/Heading/Heading'
diff --git a/yarn.lock b/yarn.lock
index b2891c3f37..b1012390f4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8563,6 +8563,21 @@ __metadata:
languageName: node
linkType: hard
+"@storybook/addon-actions@npm:8.3.1":
+ version: 8.3.1
+ resolution: "@storybook/addon-actions@npm:8.3.1"
+ dependencies:
+ "@storybook/global": "npm:^5.0.0"
+ "@types/uuid": "npm:^9.0.1"
+ dequal: "npm:^2.0.2"
+ polished: "npm:^4.2.2"
+ uuid: "npm:^9.0.0"
+ peerDependencies:
+ storybook: ^8.3.1
+ checksum: 10c0/f8b763e5133e9f01be41f351d69b5638b9f62be58ac51c100c753bd9b66d0d42242672fd062bcf5924b9186192b6e2c7f8dfd2dd5b36799d18dcc0a1ba52ef40
+ languageName: node
+ linkType: hard
+
"@storybook/addon-backgrounds@npm:8.2.9":
version: 8.2.9
resolution: "@storybook/addon-backgrounds@npm:8.2.9"
@@ -24336,8 +24351,10 @@ __metadata:
"@emotion/styled": "npm:11.13.0"
"@radix-ui/react-dialog": "npm:1.1.1"
"@radix-ui/react-slot": "npm:1.1.0"
+ "@radix-ui/react-switch": "npm:1.1.0"
"@radix-ui/react-tabs": "npm:1.1.0"
"@radix-ui/react-tooltip": "npm:1.1.2"
+ "@storybook/addon-actions": "npm:8.3.1"
"@storybook/react": "npm:8.2.9"
"@testing-library/dom": "npm:10.4.0"
"@testing-library/jest-dom": "npm:6.5.0"